diff --git a/.editorconfig b/.editorconfig index 69f4250be..3a188f337 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,16 @@ insert_final_newline = false indent_style=tab tab_width=4 +[*.{kt, kts}] +indent_style=space +tab_width=4 +continuation_indent_size=4 + +[*.gradle] +indent_style=space +tab_width=4 +continuation_indent_size=4 + [*.rb] indent_style=space -indent_size=2 \ No newline at end of file +indent_size=2 diff --git a/.gitignore b/.gitignore index 1b37aca00..4f11942ef 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,10 @@ *.class *.iml -/data/rsa.pem -/data/savedGames +/game/data/rsa.pem +/game/data/savedGames /lib/ */target/ */build/ +**/build/ +**/out/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9bcf99945..000000000 --- a/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -language: java -jdk: - - oraclejdk8 diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 000000000..7fb4e676b --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,14 @@ +node { + stage 'Stage Checkout' + checkout scm + + stage 'Stage Build' + gradle 'clean assemble' + + stage 'Stage Test' + gradle 'check' +} + +def gradle(command) { + sh "${tool name: 'gradle', type: 'hudson.plugins.gradle.GradleInstallation'}/bin/gradle ${command}" +} \ No newline at end of file diff --git a/README.md b/README.md index 208f7ba20..fca367c15 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Apollo is a high-performance, modular RuneScape emulator with a collection of ut ### Developer information -Most discussion related to the development of Apollo happens on our [Slack team](https://join.slack.com/t/apollo-rsps/shared_invite/enQtMjQ0NTYwNzkwMjExLTI5NGVmOWZjZGRkYzY4NjM1MjgxNjYyYmEyZWQxMzcxZTA5NDM1MGJkNmRkMjc2ZDQ2NjUwMjAzOGI1NjY1Zjk). If you have a problem and can't get in touch with anyone, create a GitHub issue. If making a pull request, please make sure all tests are still passing after making your changes, and that your code style is consistent with the rest of Apollo. +Most discussion related to the development of Apollo happens on our [Discord](https://discord.gg/Fuft67P). If you have a problem and can't get in touch with anyone, create a GitHub issue. If making a pull request, please make sure all tests are still passing after making your changes, and that your code style is consistent with the rest of Apollo. ### Getting started diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..2167045f3 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,47 @@ +pool: + vmImage: 'ubuntu-latest' + +variables: + GRADLE_USER_HOME: $(Pipeline.Workspace)/.gradle + +steps: + - task: CacheBeta@0 + inputs: + key: $(Agent.OS) + path: $(GRADLE_USER_HOME) + displayName: "Gradle: setup build cache" + + - task: SonarCloudPrepare@1 + inputs: + SonarCloud: 'apollo-rsps-sonarcloud' + organization: 'apollo-rsps' + scannerMode: 'Other' + displayName: "SonarCloud: prepare analysis" + + - task: Gradle@2 + displayName: "Gradle: build" + inputs: + workingDirectory: '' + gradleWrapperFile: 'gradlew' + gradleOptions: '-Xmx3072m -Dorg.gradle.parallel=true -Dorg.gradle.caching=true -Dsonar.host.url=https://sonarcloud.io' + javaHomeOption: 'JDKVersion' + jdkVersionOption: '1.8' + jdkArchitectureOption: 'x64' + publishJUnitResults: true + testResultsFiles: '**/TEST-*.xml' + tasks: 'check jacocoTestReport sonarqube' + + - script: | + ./gradlew --stop + displayName: "Gradle: stop daemon" + + - task: SonarCloudPublish@1 + inputs: + pollingTimeoutSec: '300' + displayName: "SonarCloud: publish quality gate" + + - script: | + bash <(curl -s https://codecov.io/bash) -t "${CODECOV_TOKEN}" + env: + CODECOV_TOKEN: $(CODECOV_TOKEN) + displayName: "Codecov: publish coverage" \ No newline at end of file diff --git a/build.gradle b/build.gradle index bdaa13e15..c1756fcf0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,51 +1,34 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.3.41' apply(false) + id 'org.jetbrains.intellij' version '0.4.9' apply(false) + id 'org.jmailen.kotlinter' version '1.26.0' apply(false) + id 'org.sonarqube' version '2.7.1' + id "io.gitlab.arturbosch.detekt" version '1.0.0-RC16' +} + allprojects { group = 'apollo' version = '0.0.1' - apply plugin: 'java' -} - -subprojects { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 repositories { mavenLocal() maven { url "https://repo.maven.apache.org/maven2" } + maven { url "https://dl.bintray.com/kotlin/kotlinx/" } } +} - dependencies { - compile group: 'org.apache.commons', name: 'commons-compress', version: '1.10' - compile group: 'org.jruby', name: 'jruby-complete', version: '9.0.5.0' - compile group: 'com.google.guava', name: 'guava', version: '19.0' - compile group: 'io.netty', name: 'netty-all', version: '4.0.34.Final' - compile group: 'com.lambdaworks', name: 'scrypt', version: '1.4.0' - compile group: 'com.mchange', name: 'c3p0', version: '0.9.5.2' - compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.54' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.powermock', name: 'powermock-module-junit4', version: '1.6.4' - testCompile group: 'org.powermock', name: 'powermock-api-mockito', version: '1.6.4' - } +apply from: 'gradle/properties.gradle' +apply from: 'gradle/code-quality.gradle' +apply from: 'gradle/testing.gradle' +apply from: 'gradle/wrapper.gradle' - sourceSets { - main { - java { - srcDirs = ['src/main'] - } - } +gradle.projectsEvaluated { + task check { + def deps = [] + deps += getTasksByName("check", true).findAll { it.project != rootProject } + deps += "detekt" + deps += jacocoReport - test { - java { - srcDirs = ['src/test'] - } - } + dependsOn(deps) } -} - -task(run, dependsOn: classes, type: JavaExec) { - def gameSubproject = project(':game') - def gameClasspath = gameSubproject.sourceSets.main.runtimeClasspath - - main = 'org.apollo.Server' - classpath = gameClasspath - jvmArgs = ['-Xmx1750M'] -} +} \ No newline at end of file diff --git a/cache/build.gradle b/cache/build.gradle index 47511913d..af79e12dd 100644 --- a/cache/build.gradle +++ b/cache/build.gradle @@ -1,5 +1,14 @@ +apply plugin: 'java-library' + description = 'Apollo Cache' dependencies { - compile project(':util') + implementation project(':util') + implementation group: 'com.google.guava', name: 'guava', version: guavaVersion + + test.useJUnitPlatform() + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junitJupiterVersion + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junitJupiterVersion + testImplementation group: 'org.junit.vintage', name: 'junit-vintage-engine', version: junitVintageVersion + testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: junitPlatformVersion } diff --git a/cache/src/main/org/apollo/cache/FileDescriptor.java b/cache/src/main/java/org/apollo/cache/FileDescriptor.java similarity index 100% rename from cache/src/main/org/apollo/cache/FileDescriptor.java rename to cache/src/main/java/org/apollo/cache/FileDescriptor.java diff --git a/cache/src/main/org/apollo/cache/FileSystemConstants.java b/cache/src/main/java/org/apollo/cache/FileSystemConstants.java similarity index 100% rename from cache/src/main/org/apollo/cache/FileSystemConstants.java rename to cache/src/main/java/org/apollo/cache/FileSystemConstants.java diff --git a/cache/src/main/org/apollo/cache/Index.java b/cache/src/main/java/org/apollo/cache/Index.java similarity index 100% rename from cache/src/main/org/apollo/cache/Index.java rename to cache/src/main/java/org/apollo/cache/Index.java diff --git a/cache/src/main/org/apollo/cache/IndexedFileSystem.java b/cache/src/main/java/org/apollo/cache/IndexedFileSystem.java similarity index 100% rename from cache/src/main/org/apollo/cache/IndexedFileSystem.java rename to cache/src/main/java/org/apollo/cache/IndexedFileSystem.java diff --git a/cache/src/main/org/apollo/cache/archive/Archive.java b/cache/src/main/java/org/apollo/cache/archive/Archive.java similarity index 100% rename from cache/src/main/org/apollo/cache/archive/Archive.java rename to cache/src/main/java/org/apollo/cache/archive/Archive.java diff --git a/cache/src/main/org/apollo/cache/archive/ArchiveEntry.java b/cache/src/main/java/org/apollo/cache/archive/ArchiveEntry.java similarity index 100% rename from cache/src/main/org/apollo/cache/archive/ArchiveEntry.java rename to cache/src/main/java/org/apollo/cache/archive/ArchiveEntry.java diff --git a/cache/src/main/org/apollo/cache/archive/package-info.java b/cache/src/main/java/org/apollo/cache/archive/package-info.java similarity index 100% rename from cache/src/main/org/apollo/cache/archive/package-info.java rename to cache/src/main/java/org/apollo/cache/archive/package-info.java diff --git a/cache/src/main/org/apollo/cache/decoder/ItemDefinitionDecoder.java b/cache/src/main/java/org/apollo/cache/decoder/ItemDefinitionDecoder.java similarity index 100% rename from cache/src/main/org/apollo/cache/decoder/ItemDefinitionDecoder.java rename to cache/src/main/java/org/apollo/cache/decoder/ItemDefinitionDecoder.java diff --git a/cache/src/main/org/apollo/cache/decoder/NpcDefinitionDecoder.java b/cache/src/main/java/org/apollo/cache/decoder/NpcDefinitionDecoder.java similarity index 100% rename from cache/src/main/org/apollo/cache/decoder/NpcDefinitionDecoder.java rename to cache/src/main/java/org/apollo/cache/decoder/NpcDefinitionDecoder.java diff --git a/cache/src/main/org/apollo/cache/decoder/ObjectDefinitionDecoder.java b/cache/src/main/java/org/apollo/cache/decoder/ObjectDefinitionDecoder.java similarity index 100% rename from cache/src/main/org/apollo/cache/decoder/ObjectDefinitionDecoder.java rename to cache/src/main/java/org/apollo/cache/decoder/ObjectDefinitionDecoder.java diff --git a/cache/src/main/org/apollo/cache/decoder/package-info.java b/cache/src/main/java/org/apollo/cache/decoder/package-info.java similarity index 100% rename from cache/src/main/org/apollo/cache/decoder/package-info.java rename to cache/src/main/java/org/apollo/cache/decoder/package-info.java diff --git a/cache/src/main/org/apollo/cache/def/EquipmentDefinition.java b/cache/src/main/java/org/apollo/cache/def/EquipmentDefinition.java similarity index 100% rename from cache/src/main/org/apollo/cache/def/EquipmentDefinition.java rename to cache/src/main/java/org/apollo/cache/def/EquipmentDefinition.java diff --git a/cache/src/main/org/apollo/cache/def/ItemDefinition.java b/cache/src/main/java/org/apollo/cache/def/ItemDefinition.java similarity index 100% rename from cache/src/main/org/apollo/cache/def/ItemDefinition.java rename to cache/src/main/java/org/apollo/cache/def/ItemDefinition.java diff --git a/cache/src/main/org/apollo/cache/def/NpcDefinition.java b/cache/src/main/java/org/apollo/cache/def/NpcDefinition.java similarity index 100% rename from cache/src/main/org/apollo/cache/def/NpcDefinition.java rename to cache/src/main/java/org/apollo/cache/def/NpcDefinition.java diff --git a/cache/src/main/org/apollo/cache/def/ObjectDefinition.java b/cache/src/main/java/org/apollo/cache/def/ObjectDefinition.java similarity index 100% rename from cache/src/main/org/apollo/cache/def/ObjectDefinition.java rename to cache/src/main/java/org/apollo/cache/def/ObjectDefinition.java diff --git a/cache/src/main/org/apollo/cache/def/package-info.java b/cache/src/main/java/org/apollo/cache/def/package-info.java similarity index 100% rename from cache/src/main/org/apollo/cache/def/package-info.java rename to cache/src/main/java/org/apollo/cache/def/package-info.java diff --git a/cache/src/main/org/apollo/cache/map/MapConstants.java b/cache/src/main/java/org/apollo/cache/map/MapConstants.java similarity index 100% rename from cache/src/main/org/apollo/cache/map/MapConstants.java rename to cache/src/main/java/org/apollo/cache/map/MapConstants.java diff --git a/cache/src/main/org/apollo/cache/map/MapFile.java b/cache/src/main/java/org/apollo/cache/map/MapFile.java similarity index 100% rename from cache/src/main/org/apollo/cache/map/MapFile.java rename to cache/src/main/java/org/apollo/cache/map/MapFile.java diff --git a/cache/src/main/org/apollo/cache/map/MapFileDecoder.java b/cache/src/main/java/org/apollo/cache/map/MapFileDecoder.java similarity index 100% rename from cache/src/main/org/apollo/cache/map/MapFileDecoder.java rename to cache/src/main/java/org/apollo/cache/map/MapFileDecoder.java diff --git a/cache/src/main/org/apollo/cache/map/MapIndex.java b/cache/src/main/java/org/apollo/cache/map/MapIndex.java similarity index 100% rename from cache/src/main/org/apollo/cache/map/MapIndex.java rename to cache/src/main/java/org/apollo/cache/map/MapIndex.java diff --git a/cache/src/main/org/apollo/cache/map/MapIndexDecoder.java b/cache/src/main/java/org/apollo/cache/map/MapIndexDecoder.java similarity index 100% rename from cache/src/main/org/apollo/cache/map/MapIndexDecoder.java rename to cache/src/main/java/org/apollo/cache/map/MapIndexDecoder.java diff --git a/cache/src/main/org/apollo/cache/map/MapObject.java b/cache/src/main/java/org/apollo/cache/map/MapObject.java similarity index 100% rename from cache/src/main/org/apollo/cache/map/MapObject.java rename to cache/src/main/java/org/apollo/cache/map/MapObject.java diff --git a/cache/src/main/org/apollo/cache/map/MapObjectsDecoder.java b/cache/src/main/java/org/apollo/cache/map/MapObjectsDecoder.java similarity index 100% rename from cache/src/main/org/apollo/cache/map/MapObjectsDecoder.java rename to cache/src/main/java/org/apollo/cache/map/MapObjectsDecoder.java diff --git a/cache/src/main/org/apollo/cache/map/MapPlane.java b/cache/src/main/java/org/apollo/cache/map/MapPlane.java similarity index 100% rename from cache/src/main/org/apollo/cache/map/MapPlane.java rename to cache/src/main/java/org/apollo/cache/map/MapPlane.java diff --git a/cache/src/main/org/apollo/cache/map/Tile.java b/cache/src/main/java/org/apollo/cache/map/Tile.java similarity index 100% rename from cache/src/main/org/apollo/cache/map/Tile.java rename to cache/src/main/java/org/apollo/cache/map/Tile.java diff --git a/cache/src/main/org/apollo/cache/map/TileUtils.java b/cache/src/main/java/org/apollo/cache/map/TileUtils.java similarity index 100% rename from cache/src/main/org/apollo/cache/map/TileUtils.java rename to cache/src/main/java/org/apollo/cache/map/TileUtils.java diff --git a/cache/src/main/org/apollo/cache/package-info.java b/cache/src/main/java/org/apollo/cache/package-info.java similarity index 100% rename from cache/src/main/org/apollo/cache/package-info.java rename to cache/src/main/java/org/apollo/cache/package-info.java diff --git a/cache/src/main/org/apollo/cache/tools/EquipmentUpdater.java b/cache/src/main/java/org/apollo/cache/tools/EquipmentUpdater.java similarity index 100% rename from cache/src/main/org/apollo/cache/tools/EquipmentUpdater.java rename to cache/src/main/java/org/apollo/cache/tools/EquipmentUpdater.java diff --git a/cache/src/main/org/apollo/cache/tools/package-info.java b/cache/src/main/java/org/apollo/cache/tools/package-info.java similarity index 100% rename from cache/src/main/org/apollo/cache/tools/package-info.java rename to cache/src/main/java/org/apollo/cache/tools/package-info.java diff --git a/game/build.gradle b/game/build.gradle index a96d17a99..1a451eb99 100644 --- a/game/build.gradle +++ b/game/build.gradle @@ -1,7 +1,45 @@ +apply plugin: 'application' +apply plugin: 'org.jetbrains.kotlin.jvm' +apply from: "$rootDir/gradle/kotlin.gradle" + description = 'Apollo Game' +mainClassName = 'org.apollo.Server' dependencies { compile project(':cache') compile project(':net') compile project(':util') + + compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8' + compile group: 'org.jetbrains.kotlin', name: 'kotlin-scripting-common' + compile group: 'org.jetbrains.kotlin', name: 'kotlin-script-runtime' + compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-jdk8', version: kotlinxCoroutinesVersion + compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: kotlinxCoroutinesVersion + + implementation group: 'com.google.guava', name: 'guava', version: guavaVersion + implementation group: 'io.github.classgraph', name: 'classgraph', version: classGraphVersion + implementation group: 'com.lambdaworks', name: 'scrypt', version: scryptVersion + + test.useJUnitPlatform() + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junitJupiterVersion + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junitJupiterVersion + testImplementation group: 'org.junit.vintage', name: 'junit-vintage-engine', version: junitVintageVersion + testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: junitPlatformVersion + + testImplementation group: 'junit', name: 'junit', version: junitVersion + testImplementation group: 'org.powermock', name: 'powermock-module-junit4', version: powermockVersion + testImplementation group: 'org.powermock', name: 'powermock-api-mockito2', version: powermockVersion + testImplementation group: 'org.assertj', name: 'assertj-core', version: assertjVersion + + project(":game:plugin").subprojects { pluginProject -> + if (pluginProject.buildFile.exists()) { + runtimeClasspath pluginProject + } + } +} + +applicationDistribution.from("$rootDir/data") { + include '*.dat' + include '*.xml' + into "data/" } diff --git a/data/equipment-317.dat b/game/data/equipment-317.dat similarity index 100% rename from data/equipment-317.dat rename to game/data/equipment-317.dat diff --git a/data/equipment-377.dat b/game/data/equipment-377.dat similarity index 100% rename from data/equipment-377.dat rename to game/data/equipment-377.dat diff --git a/data/fs/.gitignore b/game/data/fs/.gitignore similarity index 100% rename from data/fs/.gitignore rename to game/data/fs/.gitignore diff --git a/data/login.xml b/game/data/login.xml similarity index 100% rename from data/login.xml rename to game/data/login.xml diff --git a/data/messages.xml b/game/data/messages.xml similarity index 95% rename from data/messages.xml rename to game/data/messages.xml index 910c4a15a..f4a5bec2e 100644 --- a/data/messages.xml +++ b/game/data/messages.xml @@ -71,6 +71,12 @@ org.apollo.game.message.handler.ItemVerificationHandler + + org.apollo.game.message.impl.MagicOnMobMessage + + org.apollo.game.message.handler.MagicOnMobVerificationHandler + + org.apollo.game.message.impl.NpcActionMessage diff --git a/data/net.xml b/game/data/net.xml similarity index 100% rename from data/net.xml rename to game/data/net.xml diff --git a/data/plugins/.rubocop.yml b/game/data/plugins/.rubocop.yml similarity index 100% rename from data/plugins/.rubocop.yml rename to game/data/plugins/.rubocop.yml diff --git a/data/plugins/areas/actions.rb b/game/data/plugins/areas/actions.rb similarity index 100% rename from data/plugins/areas/actions.rb rename to game/data/plugins/areas/actions.rb diff --git a/data/plugins/areas/areas.rb b/game/data/plugins/areas/areas.rb similarity index 100% rename from data/plugins/areas/areas.rb rename to game/data/plugins/areas/areas.rb diff --git a/data/plugins/areas/plugin.xml b/game/data/plugins/areas/plugin.xml similarity index 100% rename from data/plugins/areas/plugin.xml rename to game/data/plugins/areas/plugin.xml diff --git a/data/plugins/bank/bank.rb b/game/data/plugins/bank/bank.rb similarity index 100% rename from data/plugins/bank/bank.rb rename to game/data/plugins/bank/bank.rb diff --git a/data/plugins/bank/plugin.xml b/game/data/plugins/bank/plugin.xml similarity index 100% rename from data/plugins/bank/plugin.xml rename to game/data/plugins/bank/plugin.xml diff --git a/data/plugins/bootstrap.rb b/game/data/plugins/bootstrap.rb similarity index 100% rename from data/plugins/bootstrap.rb rename to game/data/plugins/bootstrap.rb diff --git a/data/plugins/chat/privacy/plugin.xml b/game/data/plugins/chat/privacy/plugin.xml similarity index 100% rename from data/plugins/chat/privacy/plugin.xml rename to game/data/plugins/chat/privacy/plugin.xml diff --git a/data/plugins/chat/privacy/privacy.rb b/game/data/plugins/chat/privacy/privacy.rb similarity index 100% rename from data/plugins/chat/privacy/privacy.rb rename to game/data/plugins/chat/privacy/privacy.rb diff --git a/data/plugins/chat/private-messaging/friend.rb b/game/data/plugins/chat/private-messaging/friend.rb similarity index 100% rename from data/plugins/chat/private-messaging/friend.rb rename to game/data/plugins/chat/private-messaging/friend.rb diff --git a/data/plugins/chat/private-messaging/ignore.rb b/game/data/plugins/chat/private-messaging/ignore.rb similarity index 100% rename from data/plugins/chat/private-messaging/ignore.rb rename to game/data/plugins/chat/private-messaging/ignore.rb diff --git a/data/plugins/chat/private-messaging/messaging.rb b/game/data/plugins/chat/private-messaging/messaging.rb similarity index 100% rename from data/plugins/chat/private-messaging/messaging.rb rename to game/data/plugins/chat/private-messaging/messaging.rb diff --git a/data/plugins/chat/private-messaging/plugin.xml b/game/data/plugins/chat/private-messaging/plugin.xml similarity index 100% rename from data/plugins/chat/private-messaging/plugin.xml rename to game/data/plugins/chat/private-messaging/plugin.xml diff --git a/data/plugins/cmd/animate/animate.rb b/game/data/plugins/cmd/animate/animate.rb similarity index 100% rename from data/plugins/cmd/animate/animate.rb rename to game/data/plugins/cmd/animate/animate.rb diff --git a/data/plugins/cmd/animate/plugin.xml b/game/data/plugins/cmd/animate/plugin.xml similarity index 100% rename from data/plugins/cmd/animate/plugin.xml rename to game/data/plugins/cmd/animate/plugin.xml diff --git a/data/plugins/cmd/bank/bank.rb b/game/data/plugins/cmd/bank/bank.rb similarity index 100% rename from data/plugins/cmd/bank/bank.rb rename to game/data/plugins/cmd/bank/bank.rb diff --git a/data/plugins/cmd/bank/plugin.xml b/game/data/plugins/cmd/bank/plugin.xml similarity index 100% rename from data/plugins/cmd/bank/plugin.xml rename to game/data/plugins/cmd/bank/plugin.xml diff --git a/data/plugins/cmd/item/item.rb b/game/data/plugins/cmd/item/item.rb similarity index 100% rename from data/plugins/cmd/item/item.rb rename to game/data/plugins/cmd/item/item.rb diff --git a/data/plugins/cmd/item/plugin.xml b/game/data/plugins/cmd/item/plugin.xml similarity index 100% rename from data/plugins/cmd/item/plugin.xml rename to game/data/plugins/cmd/item/plugin.xml diff --git a/data/plugins/cmd/lookup/lookup.rb b/game/data/plugins/cmd/lookup/lookup.rb similarity index 100% rename from data/plugins/cmd/lookup/lookup.rb rename to game/data/plugins/cmd/lookup/lookup.rb diff --git a/data/plugins/cmd/lookup/plugin.xml b/game/data/plugins/cmd/lookup/plugin.xml similarity index 100% rename from data/plugins/cmd/lookup/plugin.xml rename to game/data/plugins/cmd/lookup/plugin.xml diff --git a/data/plugins/cmd/messaging/broadcast.rb b/game/data/plugins/cmd/messaging/broadcast.rb similarity index 100% rename from data/plugins/cmd/messaging/broadcast.rb rename to game/data/plugins/cmd/messaging/broadcast.rb diff --git a/data/plugins/cmd/messaging/plugin.xml b/game/data/plugins/cmd/messaging/plugin.xml similarity index 100% rename from data/plugins/cmd/messaging/plugin.xml rename to game/data/plugins/cmd/messaging/plugin.xml diff --git a/data/plugins/cmd/npc/plugin.xml b/game/data/plugins/cmd/npc/plugin.xml similarity index 100% rename from data/plugins/cmd/npc/plugin.xml rename to game/data/plugins/cmd/npc/plugin.xml diff --git a/data/plugins/cmd/npc/spawn.rb b/game/data/plugins/cmd/npc/spawn.rb similarity index 100% rename from data/plugins/cmd/npc/spawn.rb rename to game/data/plugins/cmd/npc/spawn.rb diff --git a/data/plugins/cmd/punishment/plugin.xml b/game/data/plugins/cmd/punishment/plugin.xml similarity index 100% rename from data/plugins/cmd/punishment/plugin.xml rename to game/data/plugins/cmd/punishment/plugin.xml diff --git a/data/plugins/cmd/punishment/punish.rb b/game/data/plugins/cmd/punishment/punish.rb similarity index 100% rename from data/plugins/cmd/punishment/punish.rb rename to game/data/plugins/cmd/punishment/punish.rb diff --git a/data/plugins/cmd/skill/plugin.xml b/game/data/plugins/cmd/skill/plugin.xml similarity index 100% rename from data/plugins/cmd/skill/plugin.xml rename to game/data/plugins/cmd/skill/plugin.xml diff --git a/data/plugins/cmd/skill/skill.rb b/game/data/plugins/cmd/skill/skill.rb similarity index 100% rename from data/plugins/cmd/skill/skill.rb rename to game/data/plugins/cmd/skill/skill.rb diff --git a/data/plugins/cmd/teleport/plugin.xml b/game/data/plugins/cmd/teleport/plugin.xml similarity index 100% rename from data/plugins/cmd/teleport/plugin.xml rename to game/data/plugins/cmd/teleport/plugin.xml diff --git a/data/plugins/cmd/teleport/teleport.rb b/game/data/plugins/cmd/teleport/teleport.rb similarity index 100% rename from data/plugins/cmd/teleport/teleport.rb rename to game/data/plugins/cmd/teleport/teleport.rb diff --git a/data/plugins/combat/plugin.xml b/game/data/plugins/combat/plugin.xml similarity index 100% rename from data/plugins/combat/plugin.xml rename to game/data/plugins/combat/plugin.xml diff --git a/data/plugins/combat/wilderness.rb b/game/data/plugins/combat/wilderness.rb similarity index 100% rename from data/plugins/combat/wilderness.rb rename to game/data/plugins/combat/wilderness.rb diff --git a/data/plugins/consumables/consumable.rb b/game/data/plugins/consumables/consumable.rb similarity index 100% rename from data/plugins/consumables/consumable.rb rename to game/data/plugins/consumables/consumable.rb diff --git a/data/plugins/consumables/drink.rb b/game/data/plugins/consumables/drink.rb similarity index 100% rename from data/plugins/consumables/drink.rb rename to game/data/plugins/consumables/drink.rb diff --git a/data/plugins/consumables/food.rb b/game/data/plugins/consumables/food.rb similarity index 100% rename from data/plugins/consumables/food.rb rename to game/data/plugins/consumables/food.rb diff --git a/data/plugins/consumables/plugin.xml b/game/data/plugins/consumables/plugin.xml similarity index 100% rename from data/plugins/consumables/plugin.xml rename to game/data/plugins/consumables/plugin.xml diff --git a/data/plugins/consumables/potions.rb b/game/data/plugins/consumables/potions.rb similarity index 100% rename from data/plugins/consumables/potions.rb rename to game/data/plugins/consumables/potions.rb diff --git a/data/plugins/dialogue/dialogue.rb b/game/data/plugins/dialogue/dialogue.rb similarity index 100% rename from data/plugins/dialogue/dialogue.rb rename to game/data/plugins/dialogue/dialogue.rb diff --git a/data/plugins/dialogue/emotes.rb b/game/data/plugins/dialogue/emotes.rb similarity index 100% rename from data/plugins/dialogue/emotes.rb rename to game/data/plugins/dialogue/emotes.rb diff --git a/data/plugins/dialogue/plugin.xml b/game/data/plugins/dialogue/plugin.xml similarity index 100% rename from data/plugins/dialogue/plugin.xml rename to game/data/plugins/dialogue/plugin.xml diff --git a/data/plugins/dummy/dummy.rb b/game/data/plugins/dummy/dummy.rb similarity index 100% rename from data/plugins/dummy/dummy.rb rename to game/data/plugins/dummy/dummy.rb diff --git a/data/plugins/dummy/plugin.xml b/game/data/plugins/dummy/plugin.xml similarity index 100% rename from data/plugins/dummy/plugin.xml rename to game/data/plugins/dummy/plugin.xml diff --git a/data/plugins/emote-tab/emote_tab.rb b/game/data/plugins/emote-tab/emote_tab.rb similarity index 100% rename from data/plugins/emote-tab/emote_tab.rb rename to game/data/plugins/emote-tab/emote_tab.rb diff --git a/data/plugins/emote-tab/plugin.xml b/game/data/plugins/emote-tab/plugin.xml similarity index 100% rename from data/plugins/emote-tab/plugin.xml rename to game/data/plugins/emote-tab/plugin.xml diff --git a/data/plugins/entity/attributes/attributes.rb b/game/data/plugins/entity/attributes/attributes.rb similarity index 100% rename from data/plugins/entity/attributes/attributes.rb rename to game/data/plugins/entity/attributes/attributes.rb diff --git a/data/plugins/entity/attributes/plugin.xml b/game/data/plugins/entity/attributes/plugin.xml similarity index 100% rename from data/plugins/entity/attributes/plugin.xml rename to game/data/plugins/entity/attributes/plugin.xml diff --git a/data/plugins/entity/mob/extension/extension.rb b/game/data/plugins/entity/mob/extension/extension.rb similarity index 100% rename from data/plugins/entity/mob/extension/extension.rb rename to game/data/plugins/entity/mob/extension/extension.rb diff --git a/data/plugins/entity/mob/extension/plugin.xml b/game/data/plugins/entity/mob/extension/plugin.xml similarity index 100% rename from data/plugins/entity/mob/extension/plugin.xml rename to game/data/plugins/entity/mob/extension/plugin.xml diff --git a/data/plugins/entity/mob/following/following.rb b/game/data/plugins/entity/mob/following/following.rb similarity index 100% rename from data/plugins/entity/mob/following/following.rb rename to game/data/plugins/entity/mob/following/following.rb diff --git a/data/plugins/entity/mob/following/plugin.xml b/game/data/plugins/entity/mob/following/plugin.xml similarity index 100% rename from data/plugins/entity/mob/following/plugin.xml rename to game/data/plugins/entity/mob/following/plugin.xml diff --git a/data/plugins/entity/mob/walk-to/plugin.xml b/game/data/plugins/entity/mob/walk-to/plugin.xml similarity index 100% rename from data/plugins/entity/mob/walk-to/plugin.xml rename to game/data/plugins/entity/mob/walk-to/plugin.xml diff --git a/data/plugins/entity/mob/walk-to/walk_to.rb b/game/data/plugins/entity/mob/walk-to/walk_to.rb similarity index 100% rename from data/plugins/entity/mob/walk-to/walk_to.rb rename to game/data/plugins/entity/mob/walk-to/walk_to.rb diff --git a/data/plugins/entity/spawning/npc-spawn.rb b/game/data/plugins/entity/spawning/npc-spawn.rb similarity index 100% rename from data/plugins/entity/spawning/npc-spawn.rb rename to game/data/plugins/entity/spawning/npc-spawn.rb diff --git a/data/plugins/entity/spawning/plugin.xml b/game/data/plugins/entity/spawning/plugin.xml similarity index 100% rename from data/plugins/entity/spawning/plugin.xml rename to game/data/plugins/entity/spawning/plugin.xml diff --git a/data/plugins/location/al-kharid/npcs.rb b/game/data/plugins/location/al-kharid/npcs.rb similarity index 100% rename from data/plugins/location/al-kharid/npcs.rb rename to game/data/plugins/location/al-kharid/npcs.rb diff --git a/data/plugins/location/al-kharid/plugin.xml b/game/data/plugins/location/al-kharid/plugin.xml similarity index 100% rename from data/plugins/location/al-kharid/plugin.xml rename to game/data/plugins/location/al-kharid/plugin.xml diff --git a/data/plugins/location/edgeville/npcs.rb b/game/data/plugins/location/edgeville/npcs.rb similarity index 100% rename from data/plugins/location/edgeville/npcs.rb rename to game/data/plugins/location/edgeville/npcs.rb diff --git a/data/plugins/location/edgeville/plugin.xml b/game/data/plugins/location/edgeville/plugin.xml similarity index 100% rename from data/plugins/location/edgeville/plugin.xml rename to game/data/plugins/location/edgeville/plugin.xml diff --git a/data/plugins/location/falador/npcs.rb b/game/data/plugins/location/falador/npcs.rb similarity index 100% rename from data/plugins/location/falador/npcs.rb rename to game/data/plugins/location/falador/npcs.rb diff --git a/data/plugins/location/falador/plugin.xml b/game/data/plugins/location/falador/plugin.xml similarity index 100% rename from data/plugins/location/falador/plugin.xml rename to game/data/plugins/location/falador/plugin.xml diff --git a/data/plugins/location/lumbridge/npcs.rb b/game/data/plugins/location/lumbridge/npcs.rb similarity index 100% rename from data/plugins/location/lumbridge/npcs.rb rename to game/data/plugins/location/lumbridge/npcs.rb diff --git a/data/plugins/location/lumbridge/plugin.xml b/game/data/plugins/location/lumbridge/plugin.xml similarity index 100% rename from data/plugins/location/lumbridge/plugin.xml rename to game/data/plugins/location/lumbridge/plugin.xml diff --git a/data/plugins/location/tutorial-island/guide.rb b/game/data/plugins/location/tutorial-island/guide.rb similarity index 100% rename from data/plugins/location/tutorial-island/guide.rb rename to game/data/plugins/location/tutorial-island/guide.rb diff --git a/data/plugins/location/tutorial-island/instructions.rb b/game/data/plugins/location/tutorial-island/instructions.rb similarity index 100% rename from data/plugins/location/tutorial-island/instructions.rb rename to game/data/plugins/location/tutorial-island/instructions.rb diff --git a/data/plugins/location/tutorial-island/npcs.rb b/game/data/plugins/location/tutorial-island/npcs.rb similarity index 100% rename from data/plugins/location/tutorial-island/npcs.rb rename to game/data/plugins/location/tutorial-island/npcs.rb diff --git a/data/plugins/location/tutorial-island/plugin.xml b/game/data/plugins/location/tutorial-island/plugin.xml similarity index 100% rename from data/plugins/location/tutorial-island/plugin.xml rename to game/data/plugins/location/tutorial-island/plugin.xml diff --git a/data/plugins/location/tutorial-island/stages.rb b/game/data/plugins/location/tutorial-island/stages.rb similarity index 100% rename from data/plugins/location/tutorial-island/stages.rb rename to game/data/plugins/location/tutorial-island/stages.rb diff --git a/data/plugins/location/tutorial-island/survival.rb b/game/data/plugins/location/tutorial-island/survival.rb similarity index 100% rename from data/plugins/location/tutorial-island/survival.rb rename to game/data/plugins/location/tutorial-island/survival.rb diff --git a/data/plugins/location/tutorial-island/utils.rb b/game/data/plugins/location/tutorial-island/utils.rb similarity index 100% rename from data/plugins/location/tutorial-island/utils.rb rename to game/data/plugins/location/tutorial-island/utils.rb diff --git a/data/plugins/location/varrock/npcs.rb b/game/data/plugins/location/varrock/npcs.rb similarity index 100% rename from data/plugins/location/varrock/npcs.rb rename to game/data/plugins/location/varrock/npcs.rb diff --git a/data/plugins/location/varrock/plugin.xml b/game/data/plugins/location/varrock/plugin.xml similarity index 100% rename from data/plugins/location/varrock/plugin.xml rename to game/data/plugins/location/varrock/plugin.xml diff --git a/data/plugins/location/varrock/shops.rb b/game/data/plugins/location/varrock/shops.rb similarity index 100% rename from data/plugins/location/varrock/shops.rb rename to game/data/plugins/location/varrock/shops.rb diff --git a/data/plugins/logout/logout.rb b/game/data/plugins/logout/logout.rb similarity index 100% rename from data/plugins/logout/logout.rb rename to game/data/plugins/logout/logout.rb diff --git a/data/plugins/logout/plugin.xml b/game/data/plugins/logout/plugin.xml similarity index 100% rename from data/plugins/logout/plugin.xml rename to game/data/plugins/logout/plugin.xml diff --git a/data/plugins/navigation/door/constants.rb b/game/data/plugins/navigation/door/constants.rb similarity index 100% rename from data/plugins/navigation/door/constants.rb rename to game/data/plugins/navigation/door/constants.rb diff --git a/data/plugins/navigation/door/door.rb b/game/data/plugins/navigation/door/door.rb similarity index 100% rename from data/plugins/navigation/door/door.rb rename to game/data/plugins/navigation/door/door.rb diff --git a/data/plugins/navigation/door/plugin.xml b/game/data/plugins/navigation/door/plugin.xml similarity index 100% rename from data/plugins/navigation/door/plugin.xml rename to game/data/plugins/navigation/door/plugin.xml diff --git a/data/plugins/navigation/door/util.rb b/game/data/plugins/navigation/door/util.rb similarity index 100% rename from data/plugins/navigation/door/util.rb rename to game/data/plugins/navigation/door/util.rb diff --git a/data/plugins/player-action/action.rb b/game/data/plugins/player-action/action.rb similarity index 100% rename from data/plugins/player-action/action.rb rename to game/data/plugins/player-action/action.rb diff --git a/data/plugins/player-action/login.rb b/game/data/plugins/player-action/login.rb similarity index 100% rename from data/plugins/player-action/login.rb rename to game/data/plugins/player-action/login.rb diff --git a/data/plugins/player-action/plugin.xml b/game/data/plugins/player-action/plugin.xml similarity index 100% rename from data/plugins/player-action/plugin.xml rename to game/data/plugins/player-action/plugin.xml diff --git a/data/plugins/quest/plugin.xml b/game/data/plugins/quest/plugin.xml similarity index 100% rename from data/plugins/quest/plugin.xml rename to game/data/plugins/quest/plugin.xml diff --git a/data/plugins/quest/repository.rb b/game/data/plugins/quest/repository.rb similarity index 100% rename from data/plugins/quest/repository.rb rename to game/data/plugins/quest/repository.rb diff --git a/data/plugins/run/plugin.xml b/game/data/plugins/run/plugin.xml similarity index 100% rename from data/plugins/run/plugin.xml rename to game/data/plugins/run/plugin.xml diff --git a/data/plugins/run/run.rb b/game/data/plugins/run/run.rb similarity index 100% rename from data/plugins/run/run.rb rename to game/data/plugins/run/run.rb diff --git a/data/plugins/shops/currency.rb b/game/data/plugins/shops/currency.rb similarity index 100% rename from data/plugins/shops/currency.rb rename to game/data/plugins/shops/currency.rb diff --git a/data/plugins/shops/plugin.xml b/game/data/plugins/shops/plugin.xml similarity index 100% rename from data/plugins/shops/plugin.xml rename to game/data/plugins/shops/plugin.xml diff --git a/data/plugins/shops/shop.rb b/game/data/plugins/shops/shop.rb similarity index 100% rename from data/plugins/shops/shop.rb rename to game/data/plugins/shops/shop.rb diff --git a/data/plugins/shops/shop_item.rb b/game/data/plugins/shops/shop_item.rb similarity index 100% rename from data/plugins/shops/shop_item.rb rename to game/data/plugins/shops/shop_item.rb diff --git a/data/plugins/shops/shops.rb b/game/data/plugins/shops/shops.rb similarity index 100% rename from data/plugins/shops/shops.rb rename to game/data/plugins/shops/shops.rb diff --git a/data/plugins/skill/fishing/fish.rb b/game/data/plugins/skill/fishing/fish.rb similarity index 100% rename from data/plugins/skill/fishing/fish.rb rename to game/data/plugins/skill/fishing/fish.rb diff --git a/data/plugins/skill/fishing/fishing.rb b/game/data/plugins/skill/fishing/fishing.rb similarity index 100% rename from data/plugins/skill/fishing/fishing.rb rename to game/data/plugins/skill/fishing/fishing.rb diff --git a/data/plugins/skill/fishing/plugin.xml b/game/data/plugins/skill/fishing/plugin.xml similarity index 100% rename from data/plugins/skill/fishing/plugin.xml rename to game/data/plugins/skill/fishing/plugin.xml diff --git a/data/plugins/skill/fishing/spot.rb b/game/data/plugins/skill/fishing/spot.rb similarity index 100% rename from data/plugins/skill/fishing/spot.rb rename to game/data/plugins/skill/fishing/spot.rb diff --git a/data/plugins/skill/fishing/tool.rb b/game/data/plugins/skill/fishing/tool.rb similarity index 100% rename from data/plugins/skill/fishing/tool.rb rename to game/data/plugins/skill/fishing/tool.rb diff --git a/data/plugins/skill/herblore/herb.rb b/game/data/plugins/skill/herblore/herb.rb similarity index 100% rename from data/plugins/skill/herblore/herb.rb rename to game/data/plugins/skill/herblore/herb.rb diff --git a/data/plugins/skill/herblore/herblore.rb b/game/data/plugins/skill/herblore/herblore.rb similarity index 100% rename from data/plugins/skill/herblore/herblore.rb rename to game/data/plugins/skill/herblore/herblore.rb diff --git a/data/plugins/skill/herblore/ingredient.rb b/game/data/plugins/skill/herblore/ingredient.rb similarity index 100% rename from data/plugins/skill/herblore/ingredient.rb rename to game/data/plugins/skill/herblore/ingredient.rb diff --git a/data/plugins/skill/herblore/plugin.xml b/game/data/plugins/skill/herblore/plugin.xml similarity index 100% rename from data/plugins/skill/herblore/plugin.xml rename to game/data/plugins/skill/herblore/plugin.xml diff --git a/data/plugins/skill/herblore/potion.rb b/game/data/plugins/skill/herblore/potion.rb similarity index 100% rename from data/plugins/skill/herblore/potion.rb rename to game/data/plugins/skill/herblore/potion.rb diff --git a/data/plugins/skill/magic/alchemy.rb b/game/data/plugins/skill/magic/alchemy.rb similarity index 100% rename from data/plugins/skill/magic/alchemy.rb rename to game/data/plugins/skill/magic/alchemy.rb diff --git a/data/plugins/skill/magic/convert.rb b/game/data/plugins/skill/magic/convert.rb similarity index 100% rename from data/plugins/skill/magic/convert.rb rename to game/data/plugins/skill/magic/convert.rb diff --git a/data/plugins/skill/magic/element.rb b/game/data/plugins/skill/magic/element.rb similarity index 100% rename from data/plugins/skill/magic/element.rb rename to game/data/plugins/skill/magic/element.rb diff --git a/data/plugins/skill/magic/enchant.rb b/game/data/plugins/skill/magic/enchant.rb similarity index 100% rename from data/plugins/skill/magic/enchant.rb rename to game/data/plugins/skill/magic/enchant.rb diff --git a/data/plugins/skill/magic/magic.rb b/game/data/plugins/skill/magic/magic.rb similarity index 100% rename from data/plugins/skill/magic/magic.rb rename to game/data/plugins/skill/magic/magic.rb diff --git a/data/plugins/skill/magic/plugin.xml b/game/data/plugins/skill/magic/plugin.xml similarity index 100% rename from data/plugins/skill/magic/plugin.xml rename to game/data/plugins/skill/magic/plugin.xml diff --git a/data/plugins/skill/magic/teleport.rb b/game/data/plugins/skill/magic/teleport.rb similarity index 100% rename from data/plugins/skill/magic/teleport.rb rename to game/data/plugins/skill/magic/teleport.rb diff --git a/data/plugins/skill/mining/gem.rb b/game/data/plugins/skill/mining/gem.rb similarity index 100% rename from data/plugins/skill/mining/gem.rb rename to game/data/plugins/skill/mining/gem.rb diff --git a/data/plugins/skill/mining/mining.rb b/game/data/plugins/skill/mining/mining.rb similarity index 100% rename from data/plugins/skill/mining/mining.rb rename to game/data/plugins/skill/mining/mining.rb diff --git a/data/plugins/skill/mining/ore.rb b/game/data/plugins/skill/mining/ore.rb similarity index 100% rename from data/plugins/skill/mining/ore.rb rename to game/data/plugins/skill/mining/ore.rb diff --git a/data/plugins/skill/mining/pickaxe.rb b/game/data/plugins/skill/mining/pickaxe.rb similarity index 100% rename from data/plugins/skill/mining/pickaxe.rb rename to game/data/plugins/skill/mining/pickaxe.rb diff --git a/data/plugins/skill/mining/plugin.xml b/game/data/plugins/skill/mining/plugin.xml similarity index 100% rename from data/plugins/skill/mining/plugin.xml rename to game/data/plugins/skill/mining/plugin.xml diff --git a/data/plugins/skill/mining/respawn.rb b/game/data/plugins/skill/mining/respawn.rb similarity index 100% rename from data/plugins/skill/mining/respawn.rb rename to game/data/plugins/skill/mining/respawn.rb diff --git a/data/plugins/skill/prayer/bury.rb b/game/data/plugins/skill/prayer/bury.rb similarity index 100% rename from data/plugins/skill/prayer/bury.rb rename to game/data/plugins/skill/prayer/bury.rb diff --git a/data/plugins/skill/prayer/plugin.xml b/game/data/plugins/skill/prayer/plugin.xml similarity index 100% rename from data/plugins/skill/prayer/plugin.xml rename to game/data/plugins/skill/prayer/plugin.xml diff --git a/data/plugins/skill/prayer/prayers.rb b/game/data/plugins/skill/prayer/prayers.rb similarity index 100% rename from data/plugins/skill/prayer/prayers.rb rename to game/data/plugins/skill/prayer/prayers.rb diff --git a/data/plugins/skill/runecraft/altar.rb b/game/data/plugins/skill/runecraft/altar.rb similarity index 100% rename from data/plugins/skill/runecraft/altar.rb rename to game/data/plugins/skill/runecraft/altar.rb diff --git a/data/plugins/skill/runecraft/plugin.xml b/game/data/plugins/skill/runecraft/plugin.xml similarity index 100% rename from data/plugins/skill/runecraft/plugin.xml rename to game/data/plugins/skill/runecraft/plugin.xml diff --git a/data/plugins/skill/runecraft/rune.rb b/game/data/plugins/skill/runecraft/rune.rb similarity index 100% rename from data/plugins/skill/runecraft/rune.rb rename to game/data/plugins/skill/runecraft/rune.rb diff --git a/data/plugins/skill/runecraft/runecraft.rb b/game/data/plugins/skill/runecraft/runecraft.rb similarity index 100% rename from data/plugins/skill/runecraft/runecraft.rb rename to game/data/plugins/skill/runecraft/runecraft.rb diff --git a/data/plugins/skill/runecraft/talisman.rb b/game/data/plugins/skill/runecraft/talisman.rb similarity index 100% rename from data/plugins/skill/runecraft/talisman.rb rename to game/data/plugins/skill/runecraft/talisman.rb diff --git a/data/plugins/skill/runecraft/tiara.rb b/game/data/plugins/skill/runecraft/tiara.rb similarity index 100% rename from data/plugins/skill/runecraft/tiara.rb rename to game/data/plugins/skill/runecraft/tiara.rb diff --git a/data/plugins/util/command.rb b/game/data/plugins/util/command.rb similarity index 100% rename from data/plugins/util/command.rb rename to game/data/plugins/util/command.rb diff --git a/data/plugins/util/name_lookup.rb b/game/data/plugins/util/name_lookup.rb similarity index 100% rename from data/plugins/util/name_lookup.rb rename to game/data/plugins/util/name_lookup.rb diff --git a/data/plugins/util/plugin.xml b/game/data/plugins/util/plugin.xml similarity index 100% rename from data/plugins/util/plugin.xml rename to game/data/plugins/util/plugin.xml diff --git a/data/synchronizer.xml b/game/data/synchronizer.xml similarity index 100% rename from data/synchronizer.xml rename to game/data/synchronizer.xml diff --git a/game/plugin-detekt-rules/build.gradle b/game/plugin-detekt-rules/build.gradle new file mode 100644 index 000000000..2a459bb66 --- /dev/null +++ b/game/plugin-detekt-rules/build.gradle @@ -0,0 +1,16 @@ +apply plugin: 'java-library' +apply plugin: 'org.jetbrains.kotlin.jvm' +apply from: "$rootDir/gradle/kotlin.gradle" + +dependencies { + api group: 'io.gitlab.arturbosch.detekt', name: 'detekt-api', version: detektVersion + api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8' + + test.useJUnitPlatform() + testImplementation("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}") + testImplementation("org.junit.jupiter:junit-jupiter-params:${junitJupiterVersion}") + testImplementation("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}") + testImplementation("org.junit.platform:junit-platform-launcher:${junitPlatformVersion}") + + testImplementation group: 'io.gitlab.arturbosch.detekt', name: 'detekt-test', version: detektVersion +} diff --git a/game/plugin-detekt-rules/src/main/kotlin/org/apollo/game/plugin/detekt/ApolloPluginRuleSetProvider.kt b/game/plugin-detekt-rules/src/main/kotlin/org/apollo/game/plugin/detekt/ApolloPluginRuleSetProvider.kt new file mode 100644 index 000000000..c084d54b9 --- /dev/null +++ b/game/plugin-detekt-rules/src/main/kotlin/org/apollo/game/plugin/detekt/ApolloPluginRuleSetProvider.kt @@ -0,0 +1,16 @@ +package org.apollo.game.plugin.detekt + +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.RuleSet +import io.gitlab.arturbosch.detekt.api.RuleSetProvider +import org.apollo.game.plugin.detekt.rules.DeclarationInScriptRule + +class ApolloPluginRuleSetProvider : RuleSetProvider { + override val ruleSetId = "apollo-plugin" + + override fun instance(config: Config): RuleSet { + return RuleSet(ruleSetId, listOf( + DeclarationInScriptRule() + )) + } +} \ No newline at end of file diff --git a/game/plugin-detekt-rules/src/main/kotlin/org/apollo/game/plugin/detekt/rules/DeclarationInScriptRule.kt b/game/plugin-detekt-rules/src/main/kotlin/org/apollo/game/plugin/detekt/rules/DeclarationInScriptRule.kt new file mode 100644 index 000000000..055b5fd1d --- /dev/null +++ b/game/plugin-detekt-rules/src/main/kotlin/org/apollo/game/plugin/detekt/rules/DeclarationInScriptRule.kt @@ -0,0 +1,31 @@ +package org.apollo.game.plugin.detekt.rules + +import io.gitlab.arturbosch.detekt.api.* +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtObjectDeclaration + +class DeclarationInScriptRule : Rule() { + override val issue = Issue( + "DeclarationInScript", + Severity.CodeSmell, + "This rule reports a plugin file containing class or object declarations.", + Debt.FIVE_MINS + ) + + override fun visit(root: KtFile) { + super.visit(root) + + val script = root.script ?: return + val declarations = script.declarations.filter { it is KtClass || it is KtObjectDeclaration } + + declarations + .forEach { + report(CodeSmell( + issue, + Entity.from(it), + message = "Declaration of ${it.name} should live in a top-level file, not a script" + )) + } + } +} \ No newline at end of file diff --git a/game/plugin-detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider b/game/plugin-detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider new file mode 100644 index 000000000..3eb44c259 --- /dev/null +++ b/game/plugin-detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider @@ -0,0 +1 @@ +org.apollo.game.plugin.detekt.ApolloPluginRuleSetProvider \ No newline at end of file diff --git a/game/plugin-detekt-rules/src/test/kotlin/org/apollo/game/plugin/detekt/rules/DeclarationInScriptRuleTest.kt b/game/plugin-detekt-rules/src/test/kotlin/org/apollo/game/plugin/detekt/rules/DeclarationInScriptRuleTest.kt new file mode 100644 index 000000000..f6ca6daeb --- /dev/null +++ b/game/plugin-detekt-rules/src/test/kotlin/org/apollo/game/plugin/detekt/rules/DeclarationInScriptRuleTest.kt @@ -0,0 +1,19 @@ +package org.apollo.game.plugin.detekt.rules + +import io.gitlab.arturbosch.detekt.test.lint +import java.nio.file.Paths +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +internal class DeclarationInScriptRuleTest { + val rule = DeclarationInScriptRule() + + @Test + fun `Finds warning in script file`() { + val srcPath = Paths.get(this.javaClass.getResource("/testData/example.kts").toURI()) + val findings = rule.lint(srcPath) + + assertEquals(1, findings.size) + assertEquals("Declaration of ExampleDeclaration should live in a top-level file, not a script", findings[0].message) + } +} \ No newline at end of file diff --git a/game/plugin-detekt-rules/src/test/resources/testData/example.kts b/game/plugin-detekt-rules/src/test/resources/testData/example.kts new file mode 100644 index 000000000..0fb1729b7 --- /dev/null +++ b/game/plugin-detekt-rules/src/test/resources/testData/example.kts @@ -0,0 +1,3 @@ +class ExampleDeclaration { + +} \ No newline at end of file diff --git a/game/plugin-testing/build.gradle b/game/plugin-testing/build.gradle new file mode 100644 index 000000000..0f2dc14d1 --- /dev/null +++ b/game/plugin-testing/build.gradle @@ -0,0 +1,28 @@ +apply plugin: 'java-library' +apply plugin: 'org.jetbrains.kotlin.jvm' +apply from: "$rootDir/gradle/kotlin.gradle" + +dependencies { + test.useJUnitPlatform() + + api project(':game') + api project(':net') + + // JUnit Jupiter API and TestEngine implementation + api("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}") + api("org.junit.jupiter:junit-jupiter-params:${junitJupiterVersion}") + implementation("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}") + implementation("org.junit.platform:junit-platform-launcher:${junitPlatformVersion}") + + api group: 'io.mockk', name: 'mockk', version: mockkVersion + api group: 'org.assertj', name: 'assertj-core', version: assertjVersion + api group: 'com.willowtreeapps.assertk', name: 'assertk', version: assertkVersion + + implementation group: 'org.powermock', name: 'powermock-module-junit4', version: powermockVersion +} + +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions { + jvmTarget = "1.8" + } +} \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/assertions/StringAssertions.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/assertions/StringAssertions.kt new file mode 100644 index 000000000..c32e88419 --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/assertions/StringAssertions.kt @@ -0,0 +1,7 @@ +package org.apollo.game.plugin.testing.assertions + +import io.mockk.MockKMatcherScope + +fun MockKMatcherScope.contains(search: String) = match { it.contains(search) } +fun MockKMatcherScope.startsWith(search: String) = match { it.startsWith(search) } +fun MockKMatcherScope.endsWith(search: String) = match { it.endsWith(search) } \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/assertions/actionAsserts.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/assertions/actionAsserts.kt new file mode 100644 index 000000000..f514a0d5f --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/assertions/actionAsserts.kt @@ -0,0 +1,20 @@ +package org.apollo.game.plugin.testing.assertions + +import io.mockk.MockKVerificationScope +import io.mockk.verify +import org.apollo.game.plugin.testing.junit.api.ActionCaptureCallbackRegistration + +/** + * Verify some expectations on a [mock] after a delayed event (specified by [DelayMode]). + */ +fun verifyAfter(registration: ActionCaptureCallbackRegistration, description: String? = null, verifier: MockKVerificationScope.() -> Unit) { + after(registration, description) { verify(verifyBlock = verifier) } +} + +/** + * Run a [callback] after a given delay, specified by [DelayMode]. + */ +fun after(registration: ActionCaptureCallbackRegistration, description: String? = null, callback: () -> Unit) { + registration.function = callback + registration.description = description +} \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/fakes/FakePluginContextFactory.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/fakes/FakePluginContextFactory.kt new file mode 100644 index 000000000..701900478 --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/fakes/FakePluginContextFactory.kt @@ -0,0 +1,25 @@ +package org.apollo.game.plugin.testing.fakes + +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import org.apollo.game.message.handler.MessageHandler +import org.apollo.game.message.handler.MessageHandlerChainSet +import org.apollo.game.plugin.PluginContext +import org.apollo.net.message.Message + +object FakePluginContextFactory { + fun create(messageHandlers: MessageHandlerChainSet): PluginContext { + val ctx = mockk() + val typeCapture = slot>() + val handlerCapture = slot>() + + every { + ctx.addMessageHandler(capture(typeCapture), capture(handlerCapture)) + } answers { + messageHandlers.putHandler(typeCapture.captured, handlerCapture.captured) + } + + return ctx + } +} \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/ApolloTestState.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/ApolloTestState.kt new file mode 100644 index 000000000..cae992930 --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/ApolloTestState.kt @@ -0,0 +1,72 @@ +package org.apollo.game.plugin.testing.junit + +import io.mockk.every +import io.mockk.slot +import io.mockk.spyk +import kotlin.reflect.KClass +import org.apollo.game.action.Action +import org.apollo.game.message.handler.MessageHandlerChainSet +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.mocking.StubPrototype +import org.apollo.game.plugin.testing.junit.stubs.PlayerStubInfo +import org.apollo.net.message.Message +import org.apollo.util.security.PlayerCredentials + +data class ApolloTestState(val handlers: MessageHandlerChainSet, val world: World) { + val players = mutableListOf() + var actionCapture: ActionCapture? = null + + fun createActionCapture(type: KClass>): ActionCapture { + if (actionCapture != null) { + throw IllegalStateException("Cannot specify more than one ActionCapture") + } + + actionCapture = ActionCapture(type) + return actionCapture!! + } + + fun createStub(proto: StubPrototype): T { + val annotations = proto.annotations + + return when (proto.type) { + Player::class -> createPlayer(PlayerStubInfo.create(annotations)) as T + World::class -> world as T + ActionCapture::class -> createActionCapture(Action::class) as T + else -> throw IllegalArgumentException("Can't stub ${proto.type.qualifiedName}") + } + } + + fun createPlayer(info: PlayerStubInfo): Player { + val credentials = PlayerCredentials(info.name, "test", 1, 1, "0.0.0.0") + val region = world.regionRepository.fromPosition(info.position) + + val player = spyk(Player(world, credentials, info.position)) + + world.register(player) + region.addEntity(player) + players.add(player) + + val actionSlot = slot>() + val messageSlot = slot() + + every { player.send(capture(messageSlot)) } answers { handlers.notify(player, messageSlot.captured) } + every { player.startAction(capture(actionSlot)) } answers { + actionCapture?.capture(actionSlot.captured) + true + } + + return player + } + + fun reset() { + actionCapture = null + players.forEach { + it.stopAction() + world.unregister(it) + } + + players.clear() + } +} \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/ApolloTestingExtension.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/ApolloTestingExtension.kt new file mode 100644 index 000000000..1a6f5af2f --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/ApolloTestingExtension.kt @@ -0,0 +1,188 @@ +package org.apollo.game.plugin.testing.junit + +import io.mockk.every +import io.mockk.slot +import io.mockk.spyk +import io.mockk.staticMockk +import kotlin.reflect.KCallable +import kotlin.reflect.KMutableProperty +import kotlin.reflect.full.companionObject +import kotlin.reflect.full.createType +import kotlin.reflect.full.declaredMemberFunctions +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.jvm.isAccessible +import kotlin.reflect.jvm.jvmErasure +import org.apollo.cache.def.ItemDefinition +import org.apollo.cache.def.NpcDefinition +import org.apollo.cache.def.ObjectDefinition +import org.apollo.game.message.handler.MessageHandlerChainSet +import org.apollo.game.model.World +import org.apollo.game.model.entity.Npc +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.obj.GameObject +import org.apollo.game.plugin.KotlinPluginEnvironment +import org.apollo.game.plugin.testing.fakes.FakePluginContextFactory +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.NpcDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.ObjectDefinitions +import org.apollo.game.plugin.testing.junit.mocking.StubPrototype +import org.junit.jupiter.api.extension.* + +class ApolloTestingExtension : + AfterTestExecutionCallback, + BeforeAllCallback, + AfterAllCallback, + BeforeEachCallback, + AfterEachCallback, + ParameterResolver { + + private val namespace = ExtensionContext.Namespace.create("apollo") + + private fun cleanup(context: ExtensionContext) { + val store = context.getStore(namespace) + val state = store[ApolloTestState::class] as ApolloTestState + + try { + state.actionCapture?.runAction() + } finally { + state.reset() + } + } + + override fun afterAll(context: ExtensionContext) { + val store = context.getStore(namespace) + store.remove(ApolloTestState::class) + } + + override fun afterEach(context: ExtensionContext) = cleanup(context) + + override fun afterTestExecution(context: ExtensionContext) = cleanup(context) + + override fun beforeAll(context: ExtensionContext) { + val stubHandlers = MessageHandlerChainSet() + val stubWorld = spyk(World()) + + context.testClass // This _must_ come before plugin environment initialisation + .map { it.kotlin.companionObject } + .ifPresent { companion -> + val companionInstance = companion.objectInstance!! + val callables: List> = companion.declaredMemberFunctions + companion.declaredMemberProperties + + createTestDefinitions( + callables, companionInstance, ItemDefinition::getId, ItemDefinition::lookup, + ItemDefinition::getDefinitions, ItemDefinition::count + ) + + createTestDefinitions( + callables, companionInstance, NpcDefinition::getId, NpcDefinition::lookup, + NpcDefinition::getDefinitions, NpcDefinition::count + ) + + createTestDefinitions( + callables, companionInstance, ObjectDefinition::getId, ObjectDefinition::lookup, + ObjectDefinition::getDefinitions, ObjectDefinition::count + ) + } + + KotlinPluginEnvironment(stubWorld).apply { + setContext(FakePluginContextFactory.create(stubHandlers)) + load(emptyList()) + } + + val store = context.getStore(namespace) + val state = ApolloTestState(stubHandlers, stubWorld) + + store.put(ApolloTestState::class, state) + } + + override fun beforeEach(context: ExtensionContext) { + val testClassInstance = context.requiredTestInstance + val testClassProps = context.requiredTestClass.kotlin.declaredMemberProperties + + val store = context.getStore(namespace) + val state = store.get(ApolloTestState::class) as ApolloTestState + + val propertyStubSites = testClassProps.asSequence() + .mapNotNull { it as? KMutableProperty<*> } + .filter { supportedTestDoubleTypes.contains(it.returnType) } + + propertyStubSites.forEach { property -> + property.setter.call( + testClassInstance, + state.createStub(StubPrototype(property.returnType.jvmErasure, property.annotations)) + ) + } + } + + override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { + val param = parameterContext.parameter + val paramType = param.type.kotlin + + return supportedTestDoubleTypes.contains(paramType.createType()) + } + + override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any { + val param = parameterContext.parameter + val paramType = param.type.kotlin + val testStore = extensionContext.getStore(namespace) + val testState = testStore.get(ApolloTestState::class) as ApolloTestState + + return testState.createStub(StubPrototype(paramType, param.annotations.toList())) + } + + /** + * Mocks the definition class of type [D] for any function with the attached annotation [A]. + * + * @param testClassMethods All of the methods in the class being tested. + * @param idMapper The map function that returns an id given a definition [D]. + * @param lookup The lookup function that returns an instance of [D] given a definition id. + */ + private inline fun createTestDefinitions( + testClassMethods: Collection>, + companionObjectInstance: Any?, + crossinline idMapper: (D) -> Int, + crossinline lookup: (Int) -> D?, + crossinline getAll: () -> Array, + crossinline count: () -> Int + ) { + val testDefinitions = findTestDefinitions(testClassMethods, companionObjectInstance) + .associateBy(idMapper) + + if (testDefinitions.isNotEmpty()) { + val idSlot = slot() + staticMockk().mock() + + every { lookup(capture(idSlot)) } answers { testDefinitions[idSlot.captured] } + every { getAll() } answers { testDefinitions.values.sortedBy(idMapper).toTypedArray() } + every { count() } answers { _ -> testDefinitions.maxBy { (id, _) -> id }?.key?.let { it + 1 } ?: 0 } + } + } + + companion object { + internal val supportedTestDoubleTypes = setOf( + Player::class.createType(), + Npc::class.createType(), + GameObject::class.createType(), + World::class.createType(), + ActionCapture::class.createType() + ) + + inline fun findTestDefinitions( + callables: Collection>, + companionObjectInstance: Any? + ): List { + return callables + .filter { method -> method.annotations.any { it is A } } + .flatMap { method -> + @Suppress("UNCHECKED_CAST") + method as? KCallable> ?: throw RuntimeException("${method.name} is annotated with " + + "${A::class.simpleName} but does not return Collection<${D::class.simpleName}>." + ) + + method.isAccessible = true // lets us call methods in private companion objects + method.call(companionObjectInstance) + } + } + } +} \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/ActionCapture.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/ActionCapture.kt new file mode 100644 index 000000000..8ebf8fa35 --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/ActionCapture.kt @@ -0,0 +1,93 @@ +package org.apollo.game.plugin.testing.junit.api + +import kotlin.reflect.KClass +import kotlin.reflect.full.isSuperclassOf +import org.apollo.game.action.Action +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue + +class ActionCapture(val type: KClass>) { + private var action: Action<*>? = null + private val callbacks = mutableListOf() + private var lastTicks: Int = 0 + + fun capture(captured: Action<*>) { + assertTrue(type.isSuperclassOf(captured::class)) { + "${captured::class.simpleName} is not an instance of ${type.simpleName}" + } + + this.action = captured + } + + private fun callback(delay: ActionCaptureDelay): ActionCaptureCallbackRegistration { + val registration = ActionCaptureCallbackRegistration() + val callback = ActionCaptureCallback(delay, registration) + + callbacks.add(callback) + return registration + } + + fun runAction(timeout: Int = 50) { + action?.let { + var pulses = 0 + + do { + it.pulse() + pulses++ + + val tickCallbacks = callbacks.filter { it.delay == ActionCaptureDelay.Ticks(pulses) } + tickCallbacks.forEach { it.invoke() } + + callbacks.removeAll(tickCallbacks) + } while (it.isRunning && pulses < timeout) + + val completionCallbacks = callbacks.filter { it.delay == ActionCaptureDelay.Completed } + completionCallbacks.forEach { it.invoke() } + + callbacks.removeAll(completionCallbacks) + } + + assertEquals(0, callbacks.size, { + "untriggered callbacks:\n" + callbacks + .map { + val delayDescription = when (it.delay) { + is ActionCaptureDelay.Ticks -> "${it.delay.count} ticks" + is ActionCaptureDelay.Completed -> "action completion" + } + + "$delayDescription (${it.callbackRegistration.description ?: ""})" + } + .joinToString("\n") + .prependIndent(" ") + }) + } + + /** + * Create a callback registration that triggers after exactly [count] ticks. + */ + fun exactTicks(count: Int) = callback(ActionCaptureDelay.Ticks(count)) + + /** + * Create a callback registration that triggers after [count] ticks. This method is cumulative, + * and will take into account previous calls to [ticks] when creating new callbacks. + * + * To run a callback after an exact number of ticks use [exactTicks]. + */ + fun ticks(count: Int): ActionCaptureCallbackRegistration { + lastTicks += count + + return exactTicks(lastTicks) + } + + /** + * Create a callback registration that triggers when an [Action] completes. + */ + fun complete() = callback(ActionCaptureDelay.Completed) + + /** + * Check if this capture has a pending [Action] to run. + */ + fun isPending(): Boolean { + return action?.isRunning ?: false + } +} \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/ActionCaptureCallback.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/ActionCaptureCallback.kt new file mode 100644 index 000000000..7f9f9b78c --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/ActionCaptureCallback.kt @@ -0,0 +1,7 @@ +package org.apollo.game.plugin.testing.junit.api + +data class ActionCaptureCallback(val delay: ActionCaptureDelay, val callbackRegistration: ActionCaptureCallbackRegistration) { + fun invoke() { + callbackRegistration.function?.invoke() + } +} \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/ActionCaptureCallbackRegistration.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/ActionCaptureCallbackRegistration.kt new file mode 100644 index 000000000..15d940f5f --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/ActionCaptureCallbackRegistration.kt @@ -0,0 +1,5 @@ +package org.apollo.game.plugin.testing.junit.api + +typealias Function = () -> Unit + +class ActionCaptureCallbackRegistration(var function: Function? = null, var description: String? = null) \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/ActionCaptureDelay.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/ActionCaptureDelay.kt new file mode 100644 index 000000000..f24ff8b68 --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/ActionCaptureDelay.kt @@ -0,0 +1,6 @@ +package org.apollo.game.plugin.testing.junit.api + +sealed class ActionCaptureDelay { + data class Ticks(val count: Int) : ActionCaptureDelay() + object Completed : ActionCaptureDelay() +} diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/annotations/DefinitionAnnotations.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/annotations/DefinitionAnnotations.kt new file mode 100644 index 000000000..b3d6c8d64 --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/annotations/DefinitionAnnotations.kt @@ -0,0 +1,37 @@ +package org.apollo.game.plugin.testing.junit.api.annotations + +/** + * Specifies that the the ItemDefinitions returned by the annotated function should be inserted into the definition + * table. + * + * The annotated function **must**: + * - Be inside a **companion object** inside an apollo test class (a regular object will not work). + * - Return a `Collection`. + */ +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class ItemDefinitions + +/** + * Specifies that the the NpcDefinitions returned by the annotated function should be inserted into the definition + * table. + * + * The annotated function **must**: + * - Be inside a **companion object** inside an apollo test class (a regular object will not work). + * - Return a `Collection`. + */ +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class NpcDefinitions + +/** + * Specifies that the the ObjectDefinitions returned by the annotated function should be inserted into the definition + * table. + * + * The annotated function **must**: + * - Be inside a **companion object** inside an apollo test class (a regular object will not work). + * - Return a `Collection`. + */ +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class ObjectDefinitions \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/annotations/stubAnnotations.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/annotations/stubAnnotations.kt new file mode 100644 index 000000000..2acf1636b --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/annotations/stubAnnotations.kt @@ -0,0 +1,9 @@ +package org.apollo.game.plugin.testing.junit.api.annotations + +annotation class Id(val value: Int) +annotation class Pos(val x: Int, val y: Int, val height: Int = 0) +annotation class Name(val value: String) + +@Target(AnnotationTarget.FIELD) +@Retention(AnnotationRetention.RUNTIME) +annotation class TestMock \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/interactions/player.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/interactions/player.kt new file mode 100644 index 000000000..6fffee19a --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/interactions/player.kt @@ -0,0 +1,42 @@ +package org.apollo.game.plugin.testing.junit.api.interactions + +import org.apollo.game.message.impl.ItemOptionMessage +import org.apollo.game.message.impl.NpcActionMessage +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.game.message.impl.PlayerActionMessage +import org.apollo.game.model.Direction +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Entity +import org.apollo.game.model.entity.Npc +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.obj.GameObject + +/** + * Send an [ItemOptionMessage] for the given [id], [option], [slot], and [interfaceId], simulating a + * player interacting with an item. + */ +fun Player.interactWithItem(id: Int, option: Int, slot: Int? = null, interfaceId: Int? = null) { + send(ItemOptionMessage(option, interfaceId ?: -1, id, slot ?: inventory.slotOf(id))) +} + +/** + * Spawn a new object (defaulting to in-front of the player) and immediately interact with it. + */ +fun Player.interactWithObject(id: Int, option: Int, at: Position? = null) { + val obj = world.spawnObject(id, at ?: position.step(1, Direction.NORTH)) + interactWith(obj, option) +} + +/** + * Move the player within interaction distance to the given [Entity] and fake an action + * message. + */ +fun Player.interactWith(entity: Entity, option: Int = 1) { + position = entity.position.step(1, Direction.NORTH) + + when (entity) { + is GameObject -> send(ObjectActionMessage(option, entity.id, entity.position)) + is Npc -> send(NpcActionMessage(option, entity.index)) + is Player -> send(PlayerActionMessage(option, entity.index)) + } +} \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/interactions/world.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/interactions/world.kt new file mode 100644 index 000000000..8dfd2cbcd --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/api/interactions/world.kt @@ -0,0 +1,34 @@ +package org.apollo.game.plugin.testing.junit.api.interactions + +import org.apollo.cache.def.NpcDefinition +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.entity.Npc +import org.apollo.game.model.entity.obj.GameObject +import org.apollo.game.model.entity.obj.StaticGameObject + +/** + * Spawn a new static game object into the world with the given id and position. + */ +fun World.spawnObject(id: Int, position: Position): GameObject { + val obj = StaticGameObject(this, id, position, 0, 0) + + spawn(obj) + + return obj +} + +/** + * Spawns a new NPC with the minimum set of dependencies required to function correctly in the world. + */ +fun World.spawnNpc(id: Int, position: Position): Npc { + val definition = NpcDefinition(id) + val npc = Npc(this, position, definition, arrayOfNulls(4)) + val region = regionRepository.fromPosition(position) + val npcs = npcRepository + + npcs.add(npc) + region.addEntity(npc) + + return npc +} \ No newline at end of file diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/mocking/StubPrototype.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/mocking/StubPrototype.kt new file mode 100644 index 000000000..924b2d5b5 --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/mocking/StubPrototype.kt @@ -0,0 +1,5 @@ +package org.apollo.game.plugin.testing.junit.mocking + +import kotlin.reflect.KClass + +data class StubPrototype(val type: KClass, val annotations: Collection) diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/params/DefinitionProviders.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/params/DefinitionProviders.kt new file mode 100644 index 000000000..1daced5d4 --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/params/DefinitionProviders.kt @@ -0,0 +1,97 @@ +package org.apollo.game.plugin.testing.junit.params + +import java.util.stream.Stream +import kotlin.reflect.KCallable +import kotlin.reflect.full.companionObject +import kotlin.reflect.full.declaredMemberFunctions +import kotlin.reflect.full.declaredMemberProperties +import org.apollo.cache.def.ItemDefinition +import org.apollo.cache.def.NpcDefinition +import org.apollo.cache.def.ObjectDefinition +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension.Companion.findTestDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.NpcDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.ObjectDefinitions +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import org.junit.jupiter.params.support.AnnotationConsumer + +/** + * An [ArgumentsProvider] for a definition of type `D`. + */ +abstract class DefinitionsProvider( + private val definitionProvider: (methods: Collection>, companionObjectInstance: Any) -> List +) : ArgumentsProvider { + + protected lateinit var sourceNames: Set + + override fun provideArguments(context: ExtensionContext): Stream { + val companion = context.requiredTestClass.kotlin.companionObject + ?: throw RuntimeException("${context.requiredTestMethod.name} is annotated with a DefinitionsProvider," + + " but does not contain a companion object to search for Definitions in." + ) + + val companionInstance = companion.objectInstance!! // safe + val callables: List> = companion.declaredMemberFunctions + companion.declaredMemberProperties + + val filtered = if (sourceNames.isEmpty()) { + callables + } else { + callables.filter { it.name in sourceNames } + } + + return definitionProvider(filtered, companionInstance).map { Arguments.of(it) }.stream() + } +} + +// These providers are separate because of a JUnit bug in its use of ArgumentsSource and AnnotationConsumer - +// the reflection code that invokes the AnnotationConsumer searches for an accept() method that takes an +// Annotation parameter, prohibiting usage of the actual `Annotation` type as the parameter - meaning +// DefinitionsProvider cannot abstract over different annotation implementations (i.e. over ItemDefinitionSource, +// NpcDefinitionSource, and ObjectDefinitionSource). + +/** + * An [ArgumentsProvider] for [ItemDefinition]s. + * + * Test authors should not need to utilise this class, and should instead annotate their function with + * [@ItemDefinitionSource][ItemDefinitionSource]. + */ +object ItemDefinitionsProvider : DefinitionsProvider( + { methods, companion -> findTestDefinitions(methods, companion) } +), AnnotationConsumer { + + override fun accept(source: ItemDefinitionSource) { + sourceNames = source.sourceNames.toHashSet() + } +} + +/** + * An [ArgumentsProvider] for [NpcDefinition]s. + * + * Test authors should not need to utilise this class, and should instead annotate their function with + * [@NpcDefinitionSource][NpcDefinitionSource]. + */ +object NpcDefinitionsProvider : DefinitionsProvider( + { methods, companion -> findTestDefinitions(methods, companion) } +), AnnotationConsumer { + + override fun accept(source: NpcDefinitionSource) { + sourceNames = source.sourceNames.toHashSet() + } +} + +/** + * An [ArgumentsProvider] for [ObjectDefinition]s. + * + * Test authors should not need to utilise this class, and should instead annotate their function with + * [@ObjectDefinitionSource][ObjectDefinitionSource]. + */ +object ObjectDefinitionsProvider : DefinitionsProvider( + { methods, companion -> findTestDefinitions(methods, companion) } +), AnnotationConsumer { + + override fun accept(source: ObjectDefinitionSource) { + sourceNames = source.sourceNames.toHashSet() + } +} diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/params/DefinitionSource.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/params/DefinitionSource.kt new file mode 100644 index 000000000..c97fc9ca1 --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/params/DefinitionSource.kt @@ -0,0 +1,54 @@ +package org.apollo.game.plugin.testing.junit.params + +import org.apollo.cache.def.ItemDefinition +import org.apollo.cache.def.NpcDefinition +import org.apollo.cache.def.ObjectDefinition +import org.junit.jupiter.params.provider.ArgumentsSource + +/** + * `@ItemDefinitionSource` is an [ArgumentsSource] for [ItemDefinition]s. + * + * @param sourceNames The names of the properties or functions annotated with `@ItemDefinitions` to use as sources of + * [ItemDefinition]s for the test with this annotation. Every property/function must return + * `Collection`. If no [sourceNames] are provided, every property and function annotated with + * `@ItemDefinitions` (in this class's companion object) will be used. + * + * @see org.junit.jupiter.params.provider.ArgumentsSource + * @see org.junit.jupiter.params.ParameterizedTest + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +@ArgumentsSource(ItemDefinitionsProvider::class) +annotation class ItemDefinitionSource(vararg val sourceNames: String) + +/** + * `@NpcDefinitionSource` is an [ArgumentsSource] for [NpcDefinition]s. + * + * @param sourceNames The names of the properties or functions annotated with `@NpcDefinitions` to use as sources of + * [NpcDefinition]s for the test with this annotation. Every property/function must return + * `Collection`. If no [sourceNames] are provided, every property and function annotated with + * `@NpcDefinitions` (in this class's companion object) will be used. + * + * @see org.junit.jupiter.params.provider.ArgumentsSource + * @see org.junit.jupiter.params.ParameterizedTest + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +@ArgumentsSource(NpcDefinitionsProvider::class) +annotation class NpcDefinitionSource(vararg val sourceNames: String) + +/** + * `@ObjectDefinitionSource` is an [ArgumentsSource] for [ObjectDefinition]s. + * + * @param sourceNames The names of the properties or functions annotated with `@ObjectDefinitions` to use as sources of + * [ObjectDefinition]s for the test with this annotation. Every property/function must return + * `Collection`. If no [sourceNames] are provided, every property and function annotated with + * `@ObjectDefinitions` (in this class's companion object) will be used. + * + * @see org.junit.jupiter.params.provider.ArgumentsSource + * @see org.junit.jupiter.params.ParameterizedTest + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +@ArgumentsSource(ObjectDefinitionsProvider::class) +annotation class ObjectDefinitionSource(vararg val sourceNames: String) diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/stubs/GameObjectStubInfo.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/stubs/GameObjectStubInfo.kt new file mode 100644 index 000000000..48c842f89 --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/stubs/GameObjectStubInfo.kt @@ -0,0 +1 @@ +package org.apollo.game.plugin.testing.junit.stubs diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/stubs/NpcStubInfo.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/stubs/NpcStubInfo.kt new file mode 100644 index 000000000..48c842f89 --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/stubs/NpcStubInfo.kt @@ -0,0 +1 @@ +package org.apollo.game.plugin.testing.junit.stubs diff --git a/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/stubs/PlayerStubInfo.kt b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/stubs/PlayerStubInfo.kt new file mode 100644 index 000000000..cd192c068 --- /dev/null +++ b/game/plugin-testing/src/main/kotlin/org/apollo/game/plugin/testing/junit/stubs/PlayerStubInfo.kt @@ -0,0 +1,25 @@ +package org.apollo.game.plugin.testing.junit.stubs + +import org.apollo.game.model.Position +import org.apollo.game.plugin.testing.junit.api.annotations.Name +import org.apollo.game.plugin.testing.junit.api.annotations.Pos + +class PlayerStubInfo { + companion object { + fun create(annotations: Collection): PlayerStubInfo { + val info = PlayerStubInfo() + + annotations.forEach { + when (it) { + is Name -> info.name = it.value + is Pos -> info.position = Position(it.x, it.y, it.height) + } + } + + return info + } + } + + var position = Position(3222, 3222) + var name = "test" +} diff --git a/game/plugin/api/build.gradle b/game/plugin/api/build.gradle new file mode 100644 index 000000000..c1c8d477f --- /dev/null +++ b/game/plugin/api/build.gradle @@ -0,0 +1,16 @@ +apply plugin: 'org.jetbrains.kotlin.jvm' +apply plugin: 'java' +description = 'Helpers and API for common plugin usecases' + +test { + useJUnitPlatform() +} + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/api/src/org/apollo/game/plugin/api/Definitions.kt b/game/plugin/api/src/org/apollo/game/plugin/api/Definitions.kt new file mode 100644 index 000000000..f0873b998 --- /dev/null +++ b/game/plugin/api/src/org/apollo/game/plugin/api/Definitions.kt @@ -0,0 +1,104 @@ +package org.apollo.game.plugin.api + +import java.lang.IllegalArgumentException +import org.apollo.cache.def.ItemDefinition +import org.apollo.cache.def.NpcDefinition +import org.apollo.cache.def.ObjectDefinition + +/** + * Provides plugins with access to item, npc, and object definitions + */ +object Definitions { + + /** + * Returns the [ItemDefinition] with the specified [id]. Callers of this function must perform bounds checking on + * the [id] prior to invoking this method (i.e. verify that `id >= 0 && id < ItemDefinition.count()`). + * + * @throws IndexOutOfBoundsException If the id is out of bounds. + */ + fun item(id: Int): ItemDefinition { + return ItemDefinition.lookup(id) + } + + /** + * Returns the [ItemDefinition] with the specified name, performing case-insensitive matching. If multiple items + * share the same name, the item with the lowest id is returned. + * + * The name may be suffixed with an explicit item id (as a way to disambiguate in the above case), by ending the + * name with `_id`, e.g. `monks_robe_42`. If an explicit id is attached, it must be bounds checked (in the same + * manner as [item(id: Int)][item]). + */ + fun item(name: String): ItemDefinition? { + return findEntity(ItemDefinition::getDefinitions, ItemDefinition::getName, name) + } + + /** + * Returns the [ObjectDefinition] with the specified [id]. Callers of this function must perform bounds checking on + * the [id] prior to invoking this method (i.e. verify that `id >= 0 && id < ObjectDefinition.count()`). + * + * @throws IndexOutOfBoundsException If the id is out of bounds. + */ + fun obj(id: Int): ObjectDefinition { + return ObjectDefinition.lookup(id) + } + + /** + * Returns the [ObjectDefinition] with the specified name, performing case-insensitive matching. If multiple objects + * share the same name, the object with the lowest id is returned. + * + * The name may be suffixed with an explicit object id (as a way to disambiguate in the above case), by ending the + * name with `_id`, e.g. `man_2`. If an explicit id is attached, it must be bounds checked (in the same + * manner as [object(id: Int)][object]). + */ + fun obj(name: String): ObjectDefinition? { + return findEntity(ObjectDefinition::getDefinitions, ObjectDefinition::getName, name) + } + + /** + * Returns the [NpcDefinition] with the specified [id]. Callers of this function must perform bounds checking on + * the [id] prior to invoking this method (i.e. verify that `id >= 0 && id < NpcDefinition.count()`). + * + * @throws IndexOutOfBoundsException If the id is out of bounds. + */ + fun npc(id: Int): NpcDefinition { + return NpcDefinition.lookup(id) + } + + /** + * Returns the [NpcDefinition] with the specified name, performing case-insensitive matching. If multiple npcs + * share the same name, the npc with the lowest id is returned. + * + * The name may be suffixed with an explicit npc id (as a way to disambiguate in the above case), by ending the + * name with `_id`, e.g. `man_2`. If an explicit id is attached, it must be bounds checked (in the same + * manner as [npc(id: Int)][npc]). + */ + fun npc(name: String): NpcDefinition? { + return findEntity(NpcDefinition::getDefinitions, NpcDefinition::getName, name) + } + + /** + * The [Regex] used to match 'names' that have an id attached to the end. + */ + private val ID_REGEX = Regex(".+_[0-9]+$") + + private fun findEntity( + definitionsProvider: () -> Array, + nameSupplier: T.() -> String, + name: String + ): T? { + val definitions = definitionsProvider() + + if (ID_REGEX matches name) { + val id = name.substring(name.lastIndexOf('_') + 1, name.length).toIntOrNull() + + if (id == null || id >= definitions.size) { + throw IllegalArgumentException("Error while searching for definition: invalid id suffix in $name.") + } + + return definitions[id] + } + + val normalizedName = name.replace('_', ' ') + return definitions.firstOrNull { it.nameSupplier().equals(normalizedName, ignoreCase = true) } + } +} \ No newline at end of file diff --git a/game/plugin/api/src/org/apollo/game/plugin/api/Player.kt b/game/plugin/api/src/org/apollo/game/plugin/api/Player.kt new file mode 100644 index 000000000..a98a54481 --- /dev/null +++ b/game/plugin/api/src/org/apollo/game/plugin/api/Player.kt @@ -0,0 +1,89 @@ +package org.apollo.game.plugin.api + +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.model.entity.SkillSet + +val Player.attack: SkillProxy get() = SkillProxy(skillSet, Skill.ATTACK) +val Player.defence: SkillProxy get() = SkillProxy(skillSet, Skill.DEFENCE) +val Player.strength: SkillProxy get() = SkillProxy(skillSet, Skill.STRENGTH) +val Player.hitpoints: SkillProxy get() = SkillProxy(skillSet, Skill.HITPOINTS) +val Player.ranged: SkillProxy get() = SkillProxy(skillSet, Skill.RANGED) +val Player.prayer: SkillProxy get() = SkillProxy(skillSet, Skill.PRAYER) +val Player.magic: SkillProxy get() = SkillProxy(skillSet, Skill.MAGIC) +val Player.cooking: SkillProxy get() = SkillProxy(skillSet, Skill.COOKING) +val Player.woodcutting: SkillProxy get() = SkillProxy(skillSet, Skill.WOODCUTTING) +val Player.fletching: SkillProxy get() = SkillProxy(skillSet, Skill.FLETCHING) +val Player.fishing: SkillProxy get() = SkillProxy(skillSet, Skill.FISHING) +val Player.firemaking: SkillProxy get() = SkillProxy(skillSet, Skill.FIREMAKING) +val Player.crafting: SkillProxy get() = SkillProxy(skillSet, Skill.CRAFTING) +val Player.smithing: SkillProxy get() = SkillProxy(skillSet, Skill.SMITHING) +val Player.mining: SkillProxy get() = SkillProxy(skillSet, Skill.MINING) +val Player.herblore: SkillProxy get() = SkillProxy(skillSet, Skill.HERBLORE) +val Player.agility: SkillProxy get() = SkillProxy(skillSet, Skill.AGILITY) +val Player.thieving: SkillProxy get() = SkillProxy(skillSet, Skill.THIEVING) +val Player.slayer: SkillProxy get() = SkillProxy(skillSet, Skill.SLAYER) +val Player.farming: SkillProxy get() = SkillProxy(skillSet, Skill.FARMING) +val Player.runecraft: SkillProxy get() = SkillProxy(skillSet, Skill.RUNECRAFT) + +/** + * A proxy class to allow + */ +class SkillProxy(private val skills: SkillSet, private val skill: Int) { + + /** + * The maximum level of this skill. + */ + val maximum: Int + get() = skills.getMaximumLevel(skill) + + /** + * The current level of this skill. + */ + val current: Int + get() = skills.getCurrentLevel(skill) + + /** + * The amount of experience in this skill a player has. + */ + var experience: Double + get() = skills.getExperience(skill) + set(value) { + skills.setExperience(skill, value) + } + + /** + * Boosts the current level of this skill by [amount], if possible. + */ + fun boost(amount: Int) { + require(amount >= 1) { "Can only boost skills by positive values." } + + val new = if (current - maximum > amount) { + current + } else { + Math.min(current + amount, maximum + amount) + } + + skills.setCurrentLevel(skill, new) + } + + /** + * Drains the current level of this skill by [amount], if possible. + */ + fun drain(amount: Int) { + require(amount >= 1) { "Can only drain skills by positive values." } + + val new = Math.max(current - amount, 0) + skills.setCurrentLevel(skill, new) + } + + /** + * Restores the current level of this skill by [amount], if possible. + */ + fun restore(amount: Int) { + require(amount >= 1) { "Can only restore skills by positive values." } + + val new = Math.min(current + amount, maximum) + skills.setCurrentLevel(skill, new) + } +} \ No newline at end of file diff --git a/game/plugin/api/src/org/apollo/game/plugin/api/Position.kt b/game/plugin/api/src/org/apollo/game/plugin/api/Position.kt new file mode 100644 index 000000000..35f901427 --- /dev/null +++ b/game/plugin/api/src/org/apollo/game/plugin/api/Position.kt @@ -0,0 +1,12 @@ +package org.apollo.game.plugin.api + +import org.apollo.game.model.Position + +/** + * Support destructuring a Position into its components. + */ +object Position { + operator fun Position.component1(): Int = x + operator fun Position.component2(): Int = y + operator fun Position.component3(): Int = height +} \ No newline at end of file diff --git a/game/plugin/api/src/org/apollo/game/plugin/api/Random.kt b/game/plugin/api/src/org/apollo/game/plugin/api/Random.kt new file mode 100644 index 000000000..a8d538264 --- /dev/null +++ b/game/plugin/api/src/org/apollo/game/plugin/api/Random.kt @@ -0,0 +1,9 @@ +package org.apollo.game.plugin.api + +import java.util.* + +val RAND = Random() + +fun rand(bounds: Int): Int { + return RAND.nextInt(bounds) +} \ No newline at end of file diff --git a/game/plugin/api/src/org/apollo/game/plugin/api/World.kt b/game/plugin/api/src/org/apollo/game/plugin/api/World.kt new file mode 100644 index 000000000..6f981e487 --- /dev/null +++ b/game/plugin/api/src/org/apollo/game/plugin/api/World.kt @@ -0,0 +1,103 @@ +package org.apollo.game.plugin.api + +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.area.Region +import org.apollo.game.model.entity.Entity +import org.apollo.game.model.entity.EntityType +import org.apollo.game.model.entity.EntityType.DYNAMIC_OBJECT +import org.apollo.game.model.entity.EntityType.STATIC_OBJECT +import org.apollo.game.model.entity.obj.DynamicGameObject +import org.apollo.game.model.entity.obj.GameObject +import org.apollo.game.scheduling.ScheduledTask + +/** + * Finds all of the [Entities][Entity] with the specified [EntityTypes][EntityType] at the specified [position], that + * match the provided [predicate]. + * + * ``` + * const val GOLD_COINS = 995 + * ... + * + * val allCoins: Sequence = region.find(position, EntityType.GROUND_ITEM) { item -> item.id == GOLD_COINS } + * ``` + */ +fun Region.find(position: Position, vararg types: EntityType, predicate: (T) -> Boolean): Sequence { + return getEntities(position, *types).asSequence().filter(predicate) +} + +/** + * Finds the first [GameObject]s with the specified [id] at the specified [position]. + * + * Note that the iteration order of entities in a [Region] is not defined - this function should not be used if there + * may be more than [GameObject] with the specified [id] (see [Region.findObjects]). + */ +fun Region.findObject(position: Position, id: Int): GameObject? { + return find(position, DYNAMIC_OBJECT, STATIC_OBJECT) { it.id == id } + .firstOrNull() +} + +/** + * Finds **all** [GameObject]s with the specified [id] at the specified [position]. + */ +fun Region.findObjects(position: Position, id: Int): Sequence { + return find(position, DYNAMIC_OBJECT, STATIC_OBJECT) { it.id == id } +} + +/** + * Finds the first [GameObject]s with the specified [id] at the specified [position]. + * + * Note that the iteration order of entities in a [Region] is not defined - this function should not be used if there + * may be more than [GameObject] with the specified [id] (see [World.findObjects]). + */ +fun World.findObject(position: Position, id: Int): GameObject? { + return regionRepository.fromPosition(position).findObject(position, id) +} + +/** + * Finds **all** [GameObject]s with the specified [id] at the specified [position]. + */ +fun World.findObjects(position: Position, id: Int): Sequence { + return regionRepository.fromPosition(position).findObjects(position, id) +} + +/** + * Removes the specified [GameObject] from the world, replacing it with [replacement] object for [delay] **pulses**. + */ +fun World.replaceObject(obj: GameObject, replacement: Int, delay: Int) { + val replacementObj = DynamicGameObject.createPublic(this, replacement, obj.position, obj.type, obj.orientation) + + schedule(ExpireObjectTask(this, obj, replacementObj, delay)) +} + +/** + * A [ScheduledTask] that temporarily replaces the [existing] [GameObject] with the [replacement] [GameObject] for the + * specified [duration]. + * + * @param existing The [GameObject] that already exists and should be replaced. + * @param replacement The [GameObject] to replace the [existing] object with. + * @param duration The time, in **pulses**, for the [replacement] object to exist in the game world. + */ +private class ExpireObjectTask( + private val world: World, + private val existing: GameObject, + private val replacement: GameObject, + private val duration: Int +) : ScheduledTask(0, true) { + + private var respawning: Boolean = false + + override fun execute() { + val region = world.regionRepository.fromPosition(existing.position) + + if (!respawning) { + world.spawn(replacement) + respawning = true + setDelay(duration) + } else { + region.removeEntity(replacement) + world.spawn(existing) + stop() + } + } +} \ No newline at end of file diff --git a/game/plugin/api/test/org/apollo/game/plugin/api/DefinitionsTests.kt b/game/plugin/api/test/org/apollo/game/plugin/api/DefinitionsTests.kt new file mode 100644 index 000000000..89c662aee --- /dev/null +++ b/game/plugin/api/test/org/apollo/game/plugin/api/DefinitionsTests.kt @@ -0,0 +1,92 @@ +package org.apollo.game.plugin.api + +import org.apollo.cache.def.ItemDefinition +import org.apollo.cache.def.NpcDefinition +import org.apollo.cache.def.ObjectDefinition +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.NpcDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.ObjectDefinitions +import org.apollo.game.plugin.testing.junit.params.ItemDefinitionSource +import org.apollo.game.plugin.testing.junit.params.NpcDefinitionSource +import org.apollo.game.plugin.testing.junit.params.ObjectDefinitionSource +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest + +@ExtendWith(ApolloTestingExtension::class) +class DefinitionsTests { + + @Test + fun `can find an ItemDefinition directly using its id`() { + val searched = Definitions.item(0) + assertEquals(items.first().id, searched.id) + } + + @Test + fun `can find an ItemDefinition using its name`() { + val searched = Definitions.item("item_two") + assertEquals(items[2].id, searched?.id) + } + + @ParameterizedTest + @ItemDefinitionSource + fun `can find ItemDefinitions directly using id suffixing`(item: ItemDefinition) { + val searched = Definitions.item("${item.name}_${item.id}") + assertEquals(item.id, searched?.id) + } + + @Test + fun `can find an NpcDefinition directly using its id`() { + val searched = Definitions.npc(0) + assertEquals(npcs.first().id, searched.id) + } + + @Test + fun `can find an NpcDefinition using its name`() { + val searched = Definitions.npc("npc_two") + assertEquals(items[2].id, searched?.id) + } + + @ParameterizedTest + @NpcDefinitionSource + fun `can find NpcDefinitions directly using id suffixing`(npc: NpcDefinition) { + val searched = Definitions.npc("${npc.name}_${npc.id}") + assertEquals(npc.id, searched?.id) + } + + @Test + fun `can find an ObjectDefinition directly using its id`() { + val searched = Definitions.obj(0) + assertEquals(objs.first().id, searched.id) + } + + @Test + fun `can find an ObjectDefinition using its name`() { + val searched = Definitions.obj("obj_two") + assertEquals(items[2].id, searched?.id) + } + + @ParameterizedTest + @ObjectDefinitionSource + fun `can find ObjectDefinitions directly using id suffixing`(obj: ObjectDefinition) { + val searched = Definitions.obj("${obj.name}_${obj.id}") + assertEquals(obj.id, searched?.id) + } + + private companion object { + + @ItemDefinitions + val items = listOf("item zero", "item one", "item two", "item duplicate name", "item duplicate name") + .mapIndexed { id, name -> ItemDefinition(id).also { it.name = name } } + + @NpcDefinitions + val npcs = listOf("npc zero", "npc one", "npc two", "npc duplicate name", "npc duplicate name") + .mapIndexed { id, name -> NpcDefinition(id).also { it.name = name } } + + @ObjectDefinitions + val objs = listOf("obj zero", "obj one", "obj two", "obj duplicate name", "obj duplicate name") + .mapIndexed { id, name -> ObjectDefinition(id).also { it.name = name } } + } +} \ No newline at end of file diff --git a/game/plugin/api/test/org/apollo/game/plugin/api/PlayerTests.kt b/game/plugin/api/test/org/apollo/game/plugin/api/PlayerTests.kt new file mode 100644 index 000000000..753f16639 --- /dev/null +++ b/game/plugin/api/test/org/apollo/game/plugin/api/PlayerTests.kt @@ -0,0 +1,122 @@ +package org.apollo.game.plugin.api + +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class PlayerTests { + + @TestMock + lateinit var player: Player + + @BeforeEach + fun setHitpointsLevel() { + player.skillSet.setSkill(Skill.HITPOINTS, Skill(1_154.0, 10, 10)) + } + + @Test + fun `can boost skill above maximum level`() { + player.apply { + hitpoints.boost(5) + + assertEquals(15, hitpoints.current) + assertEquals(10, hitpoints.maximum) + } + } + + @Test + fun `boosts to the same skill do not accumulate`() { + player.apply { + hitpoints.boost(5) + hitpoints.boost(4) + + assertEquals(15, hitpoints.current) + assertEquals(10, hitpoints.maximum) + } + } + + @Test + fun `greater boosts can override earlier boosts`() { + player.apply { + hitpoints.boost(5) + hitpoints.boost(7) + + assertEquals(17, hitpoints.current) + assertEquals(10, hitpoints.maximum) + } + } + + @Test + fun `can drain skills`() { + player.apply { + hitpoints.drain(5) + + assertEquals(5, hitpoints.current) + assertEquals(10, hitpoints.maximum) + } + } + + @Test + fun `repeated drains on the same skill accumulate`() { + player.apply { + hitpoints.drain(4) + hitpoints.drain(5) + + assertEquals(1, hitpoints.current) + assertEquals(10, hitpoints.maximum) + } + } + + @Test + fun `cannot drain skills below zero`() { + player.apply { + hitpoints.drain(99) + + assertEquals(0, hitpoints.current) + assertEquals(10, hitpoints.maximum) + } + } + + @Test + fun `can restore previously-drained skills`() { + player.skillSet.setCurrentLevel(Skill.HITPOINTS, 1) + + player.apply { + hitpoints.restore(5) + + assertEquals(6, hitpoints.current) + assertEquals(10, hitpoints.maximum) + } + } + + @Test + fun `repeated restores on the same skill accumulate`() { + player.skillSet.setCurrentLevel(Skill.HITPOINTS, 1) + + player.apply { + hitpoints.restore(3) + hitpoints.restore(4) + + assertEquals(8, hitpoints.current) + assertEquals(10, hitpoints.maximum) + } + } + + @Test + fun `cannot restore skills above their maximum level`() { + player.skillSet.setCurrentLevel(Skill.HITPOINTS, 1) + + player.apply { + hitpoints.restore(99) + + assertEquals(10, hitpoints.current) + assertEquals(10, hitpoints.maximum) + } + } +} \ No newline at end of file diff --git a/game/plugin/api/test/org/apollo/game/plugin/api/PositionTests.kt b/game/plugin/api/test/org/apollo/game/plugin/api/PositionTests.kt new file mode 100644 index 000000000..7d9644bf3 --- /dev/null +++ b/game/plugin/api/test/org/apollo/game/plugin/api/PositionTests.kt @@ -0,0 +1,25 @@ +package org.apollo.game.plugin.api + +import org.apollo.game.model.Position +import org.apollo.game.plugin.api.Position.component1 +import org.apollo.game.plugin.api.Position.component2 +import org.apollo.game.plugin.api.Position.component3 +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class PositionTests { + + @Test + fun `Positions are destructured in the correct order`() { + val x = 10 + val y = 20 + val z = 1 + + val position = Position(x, y, z) + val (x2, y2, z2) = position + + assertEquals(x, x2) { "x coordinate mismatch in Position destructuring." } + assertEquals(y, y2) { "y coordinate mismatch in Position destructuring." } + assertEquals(z, z2) { "z coordinate mismatch in Position destructuring." } + } +} \ No newline at end of file diff --git a/game/plugin/areas/build.gradle b/game/plugin/areas/build.gradle new file mode 100644 index 000000000..22344beaa --- /dev/null +++ b/game/plugin/areas/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'kotlin' + +description = 'Enables plugins to listen on mobs entering, moving inside of, or leaving a rectangular area.' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:api') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/areas/src/Area.kt b/game/plugin/areas/src/Area.kt new file mode 100644 index 000000000..e594a170d --- /dev/null +++ b/game/plugin/areas/src/Area.kt @@ -0,0 +1,25 @@ +package org.apollo.game.plugins.area + +import org.apollo.game.model.Position +import org.apollo.game.plugin.api.Position.component1 +import org.apollo.game.plugin.api.Position.component2 +import org.apollo.game.plugin.api.Position.component3 + +/** + * An area in the game world. + */ +interface Area { + + /** + * Returns whether or not the specified [Position] is inside this [Area]. + */ + operator fun contains(position: Position): Boolean +} + +internal class RectangularArea(private val x: IntRange, private val y: IntRange, private val height: Int) : Area { + + override operator fun contains(position: Position): Boolean { + val (x, y, z) = position + return x in this.x && y in this.y && z == height + } +} diff --git a/game/plugin/areas/src/AreaAction.kt b/game/plugin/areas/src/AreaAction.kt new file mode 100644 index 000000000..85d8141a5 --- /dev/null +++ b/game/plugin/areas/src/AreaAction.kt @@ -0,0 +1,51 @@ +package org.apollo.game.plugins.area + +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player + +/** + * A set of actions to execute when a player enters, moves inside, or exits a specific area of the world. + */ +internal class AreaAction(val entrance: AreaListener, val inside: AreaListener, val exit: AreaListener) + +/** + * A function that is invoked when a player enters, moves inside of, or exits an [Area]. + */ +typealias AreaListener = Player.(Position) -> Unit + +/** + * Registers an [AreaAction] for the specified [Area] using the builder. + */ +fun action(name: String, area: Area, builder: AreaActionBuilder.() -> Unit) { + actions += AreaActionBuilder(name, area).apply(builder).build() +} + +/** + * Registers an [AreaAction] for the specified [Area] using the builder. + * + * @param predicate The predicate that determines whether or not the given [Position] is inside the [Area]. + */ +fun action(name: String, predicate: (Position) -> Boolean, builder: AreaActionBuilder.() -> Unit) { + val area = object : Area { + override fun contains(position: Position): Boolean = predicate(position) + } + + action(name, area, builder) +} + +/** + * Registers an [AreaAction] for the specified [Area] using the builder. + * + * @param x The `x` coordinate range, both ends inclusive. + * @param y The `y` coordinate range, both ends inclusive. + */ +fun action(name: String, x: IntRange, y: IntRange, height: Int = 0, builder: AreaActionBuilder.() -> Unit) { + val area = RectangularArea(x, y, height) + + action(name, area, builder) +} + +/** + * The [Set] of ([Area], [AreaAction]) [Pair]s. + */ +internal val actions = mutableSetOf>() diff --git a/game/plugin/areas/src/AreaActionBuilder.kt b/game/plugin/areas/src/AreaActionBuilder.kt new file mode 100644 index 000000000..7da8aeada --- /dev/null +++ b/game/plugin/areas/src/AreaActionBuilder.kt @@ -0,0 +1,41 @@ +package org.apollo.game.plugins.area + +/** + * A builder for ([Area], [AreaAction]) [Pair]s. + */ +class AreaActionBuilder internal constructor(val name: String, val area: Area) { + + private var entrance: AreaListener = { } + + private var inside: AreaListener = { } + + private var exit: AreaListener = { } + + /** + * Places the contents of this builder into an ([Area], [AreaAction]) [Pair]. + */ + internal fun build(): Pair { + return Pair(area, AreaAction(entrance, inside, exit)) + } + + /** + * The [listener] to execute when a player enters the associated [Area]. + */ + fun entrance(listener: AreaListener) { + this.entrance = listener + } + + /** + * The [listener] to execute when a player moves around inside the associated [Area]. + */ + fun inside(listener: AreaListener) { + this.inside = listener + } + + /** + * The [listener] to execute when a player moves exits the associated [Area]. + */ + fun exit(listener: AreaListener) { + this.exit = listener + } +} \ No newline at end of file diff --git a/game/plugin/areas/src/Areas.plugin.kts b/game/plugin/areas/src/Areas.plugin.kts new file mode 100644 index 000000000..e029b1999 --- /dev/null +++ b/game/plugin/areas/src/Areas.plugin.kts @@ -0,0 +1,23 @@ + +import org.apollo.game.model.entity.Player +import org.apollo.game.model.event.impl.MobPositionUpdateEvent +import org.apollo.game.plugins.area.actions + +/** + * Intercepts the [MobPositionUpdateEvent] and invokes area actions if necessary. + */ +on_event { MobPositionUpdateEvent::class } + .where { mob is Player } + .then { + for ((area, action) in actions) { + if (mob.position in area) { + if (next in area) { + action.inside(mob as Player, next) + } else { + action.exit(mob as Player, next) + } + } else if (next in area) { + action.entrance(mob as Player, next) + } + } + } diff --git a/game/plugin/areas/test/AreaActionTests.kt b/game/plugin/areas/test/AreaActionTests.kt new file mode 100644 index 000000000..7c577efd9 --- /dev/null +++ b/game/plugin/areas/test/AreaActionTests.kt @@ -0,0 +1,58 @@ +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.apollo.game.plugins.area.action +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class AreaActionTests { + + @TestMock + lateinit var player: Player + + @Test + fun `entrance action is triggered when a player enters the area`() { + var triggered = false + val position = Position(3222, 3222) + + action("entrance_test_action", predicate = { it == player.position }) { + triggered = true + } + + player.position = position + + assertTrue(triggered) { "entrance_test_action was not triggered." } + } + + @Test + fun `inside action is triggered when a player moves inside an area`() { + player.position = Position(3222, 3222) + var triggered = false + + action("inside_test_action", x = 3220..3224, y = 3220..3224) { + triggered = true + } + + player.position = Position(3223, 3222) + + assertTrue(triggered) { "inside_test_action was not triggered." } + } + + @Test + fun `exit action is triggered when a player exits the area`() { + player.position = Position(3222, 3222) + + var triggered = false + + action("exit_test_action", predicate = { it == player.position }) { + triggered = true + } + + player.position = Position(3221, 3221) + + assertTrue(triggered) { "exit_test_action was not triggered." } + } +} \ No newline at end of file diff --git a/game/plugin/bank/build.gradle b/game/plugin/bank/build.gradle new file mode 100644 index 000000000..3a9351392 --- /dev/null +++ b/game/plugin/bank/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/bank/src/bank.plugin.kts b/game/plugin/bank/src/bank.plugin.kts new file mode 100644 index 000000000..0aef5ac18 --- /dev/null +++ b/game/plugin/bank/src/bank.plugin.kts @@ -0,0 +1,73 @@ +import org.apollo.game.action.DistancedAction +import org.apollo.game.message.impl.NpcActionMessage +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Npc +import org.apollo.game.model.entity.Player +import org.apollo.game.model.inter.bank.BankUtils +import org.apollo.net.message.Message + +val BANK_BOOTH_ID = 2213 + +/** + * Hook into the [ObjectActionMessage] and listen for when a bank booth's second action ("Open Bank") is selected. + */ +on { ObjectActionMessage::class } + .where { option == 2 && id == BANK_BOOTH_ID } + .then { BankAction.start(this, it, position) } + +/** + * Hook into the [NpcActionMessage] and listen for when a banker's second action ("Open Bank") is selected. + */ +on { NpcActionMessage::class } + .where { option == 2 } + .then { + val npc = it.world.npcRepository[index] + + if (npc.id in BANKER_NPCS) { + BankAction.start(this, it, npc.position) + } + } + +/** + * The ids of all banker [Npcs][Npc]. + */ +val BANKER_NPCS = setOf(166, 494, 495, 496, 497, 498, 499, 1036, 1360, 1702, 2163, 2164, 2354, 2355, 2568, 2569, 2570) + +/** + * A [DistancedAction] that opens a [Player]'s bank when they get close enough to a booth or banker. + * + * @property position The [Position] of the booth/[Npc]. + */ +class BankAction(player: Player, position: Position) : DistancedAction(0, true, player, position, DISTANCE) { + + companion object { + + /** + * The distance threshold that must be reached before the bank interface is opened. + */ + const val DISTANCE = 1 + + /** + * Starts a [BankAction] for the specified [Player], terminating the [Message] that triggered. + */ + fun start(message: Message, player: Player, position: Position) { + player.startAction(BankAction(player, position)) + message.terminate() + } + } + + override fun executeAction() { + mob.turnTo(position) + BankUtils.openBank(mob) + stop() + } + + override fun equals(other: Any?): Boolean { + return other is BankAction && position == other.position + } + + override fun hashCode(): Int { + return position.hashCode() + } +} diff --git a/game/plugin/bank/test/OpenBankTest.kt b/game/plugin/bank/test/OpenBankTest.kt new file mode 100644 index 000000000..578325e08 --- /dev/null +++ b/game/plugin/bank/test/OpenBankTest.kt @@ -0,0 +1,52 @@ + +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.testing.assertions.verifyAfter +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.apollo.game.plugin.testing.junit.api.interactions.interactWith +import org.apollo.game.plugin.testing.junit.api.interactions.spawnNpc +import org.apollo.game.plugin.testing.junit.api.interactions.spawnObject +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class OpenBankTest { + + companion object { + const val BANK_BOOTH_ID = 2213 + const val BANK_TELLER_ID = 166 + + val BANK_POSITION = Position(3200, 3200, 0) + } + + @TestMock + lateinit var action: ActionCapture + + @TestMock + lateinit var player: Player + + @TestMock + lateinit var world: World + + @Test + fun `Interacting with a bank teller should open the players bank`() { + val bankTeller = world.spawnNpc(BANK_TELLER_ID, BANK_POSITION) + + // @todo - these option numbers only match by coincidence, we should be looking up the correct ones + player.interactWith(bankTeller, option = 2) + + verifyAfter(action.complete()) { player.openBank() } + } + + @Test + fun `Interacting with a bank booth object should open the players bank`() { + val bankBooth = world.spawnObject(BANK_BOOTH_ID, BANK_POSITION) + + player.interactWith(bankBooth, option = 2) + + verifyAfter(action.complete()) { player.openBank() } + } +} \ No newline at end of file diff --git a/game/plugin/build.gradle b/game/plugin/build.gradle new file mode 100644 index 000000000..24ba2cc84 --- /dev/null +++ b/game/plugin/build.gradle @@ -0,0 +1,33 @@ +gradle.projectsEvaluated { + configure(subprojects.findAll { it.buildFile.exists() }) { subproj -> + apply from: "$rootDir/gradle/kotlin.gradle" + + sourceSets { + main { + kotlin { + srcDirs += "src" + } + } + + test { + kotlin { + srcDirs += "test" + } + } + } + + test { + useJUnitPlatform() + } + + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions { + jvmTarget = "1.8" + } + } + + dependencies { + implementation group: 'com.google.guava', name: 'guava', version: guavaVersion + } + } +} \ No newline at end of file diff --git a/game/plugin/chat/private-messaging/build.gradle b/game/plugin/chat/private-messaging/build.gradle new file mode 100644 index 000000000..3a9351392 --- /dev/null +++ b/game/plugin/chat/private-messaging/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/chat/private-messaging/src/friends.plugin.kts b/game/plugin/chat/private-messaging/src/friends.plugin.kts new file mode 100644 index 000000000..6ac357d72 --- /dev/null +++ b/game/plugin/chat/private-messaging/src/friends.plugin.kts @@ -0,0 +1,21 @@ +import org.apollo.game.message.impl.AddFriendMessage +import org.apollo.game.message.impl.SendFriendMessage +import org.apollo.game.model.entity.setting.PrivacyState + +on { AddFriendMessage::class } + .then { + it.addFriend(username) + + val friend = it.world.getPlayer(username) + + if (friend == null || friend.friendPrivacy == PrivacyState.OFF) { + it.send(SendFriendMessage(username, 0)) + return@then + } else { + it.send(SendFriendMessage(username, friend.worldId)) + } + + if (friend.friendsWith(it.username) && it.friendPrivacy != PrivacyState.OFF) { + friend.send(SendFriendMessage(it.username, it.worldId)) + } + } diff --git a/game/plugin/chat/private-messaging/src/ignores.plugin.kts b/game/plugin/chat/private-messaging/src/ignores.plugin.kts new file mode 100644 index 000000000..47b0f29fe --- /dev/null +++ b/game/plugin/chat/private-messaging/src/ignores.plugin.kts @@ -0,0 +1,8 @@ +import org.apollo.game.message.impl.AddIgnoreMessage +import org.apollo.game.message.impl.RemoveIgnoreMessage + +on { AddIgnoreMessage::class } + .then { it.addIgnore(username) } + +on { RemoveIgnoreMessage::class } + .then { it.removeIgnore(username) } \ No newline at end of file diff --git a/game/plugin/chat/private-messaging/src/messaging.plugin.kts b/game/plugin/chat/private-messaging/src/messaging.plugin.kts new file mode 100644 index 000000000..d9fd21e92 --- /dev/null +++ b/game/plugin/chat/private-messaging/src/messaging.plugin.kts @@ -0,0 +1,25 @@ +import org.apollo.game.message.impl.ForwardPrivateChatMessage +import org.apollo.game.message.impl.PrivateChatMessage +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivacyState.OFF +import org.apollo.game.model.entity.setting.PrivacyState.ON + +on { PrivateChatMessage::class } + .then { + val friend = it.world.getPlayer(username) + + if (interactionPermitted(it, friend)) { + friend.send(ForwardPrivateChatMessage(it.username, it.privilegeLevel, compressedMessage)) + } + } + +fun interactionPermitted(player: Player, friend: Player?): Boolean { + val username = player.username + val privacy = friend?.friendPrivacy + + if (friend == null || friend.hasIgnored(username)) { + return false + } else { + return if (friend.friendsWith(username)) privacy != OFF else privacy == ON + } +} diff --git a/game/plugin/cmd/build.gradle b/game/plugin/cmd/build.gradle new file mode 100644 index 000000000..4d96c9f57 --- /dev/null +++ b/game/plugin/cmd/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:api') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/cmd/src/animate-cmd.plugin.kts b/game/plugin/cmd/src/animate-cmd.plugin.kts new file mode 100644 index 000000000..20993a93c --- /dev/null +++ b/game/plugin/cmd/src/animate-cmd.plugin.kts @@ -0,0 +1,15 @@ +import org.apollo.game.model.Animation +import org.apollo.game.model.entity.setting.PrivilegeLevel + +on_command("animate", PrivilegeLevel.MODERATOR) + .then { player -> + arguments.firstOrNull() + ?.let(String::toIntOrNull) + ?.let(::Animation) + ?.let { + player.playAnimation(it) + return@then + } + + player.sendMessage("Invalid syntax - ::animate [animation-id]") + } \ No newline at end of file diff --git a/game/plugin/cmd/src/bank-cmd.plugin.kts b/game/plugin/cmd/src/bank-cmd.plugin.kts new file mode 100644 index 000000000..6ded33ebc --- /dev/null +++ b/game/plugin/cmd/src/bank-cmd.plugin.kts @@ -0,0 +1,5 @@ +import org.apollo.game.model.entity.setting.PrivilegeLevel + +// Opens the player's bank if they are an administrator. +on_command("bank", PrivilegeLevel.ADMINISTRATOR) + .then { player -> player.openBank() } \ No newline at end of file diff --git a/game/plugin/cmd/src/item-cmd.plugin.kts b/game/plugin/cmd/src/item-cmd.plugin.kts new file mode 100644 index 000000000..f6c27ab85 --- /dev/null +++ b/game/plugin/cmd/src/item-cmd.plugin.kts @@ -0,0 +1,42 @@ +import com.google.common.primitives.Ints +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.entity.setting.PrivilegeLevel + +on_command("item", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + if (arguments.size !in 1..2) { + player.sendMessage("Invalid syntax - ::item [id] [amount]") + return@then + } + + val id = Ints.tryParse(arguments[0]) + if (id == null) { + player.sendMessage("Invalid syntax - ::item [id] [amount]") + return@then + } + + val amount = if (arguments.size == 2) { + val amt = Ints.tryParse(arguments[1]) + + if (amt == null) { + player.sendMessage("Invalid syntax - ::item [id] [amount]") + return@then + } + + amt + } else { + 1 + } + + if (id < 0 || id >= ItemDefinition.count()) { + player.sendMessage("The item id you specified is out of bounds!") + return@then + } + + if (amount < 0) { + player.sendMessage("The amount you specified is out of bounds!") + return@then + } + + player.inventory.add(id, amount) + } \ No newline at end of file diff --git a/game/plugin/cmd/src/lookup.plugin.kts b/game/plugin/cmd/src/lookup.plugin.kts new file mode 100644 index 000000000..80d5178c0 --- /dev/null +++ b/game/plugin/cmd/src/lookup.plugin.kts @@ -0,0 +1,52 @@ +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.plugin.api.Definitions + +on_command("iteminfo", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + arguments.firstOrNull() + ?.let(String::toIntOrNull) + ?.let(Definitions::item) + ?.apply { + val members = if (isMembersOnly) "members" else "not members" + + player.sendMessage("Item $id is called $name and is $members only.") + player.sendMessage("Its description is `$description`.") + + return@then + } + + player.sendMessage("Invalid syntax - ::iteminfo [item id]") + } + +on_command("npcinfo", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + arguments.firstOrNull() + ?.let(String::toIntOrNull) + ?.let(Definitions::npc) + ?.apply { + val combat = if (hasCombatLevel()) "has a combat level of $combatLevel" else + "does not have a combat level" + + player.sendMessage("Npc $id is called $name and $combat.") + player.sendMessage("Its description is `$description`.") + + return@then + } + + player.sendMessage("Invalid syntax - ::npcinfo [npc id]") + } + +on_command("objectinfo", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + arguments.firstOrNull() + ?.let(String::toIntOrNull) + ?.let(Definitions::obj) + ?.apply { + player.sendMessage("Object ${arguments[0]} is called $name (width=$width, length=$length).") + player.sendMessage("Its description is `$description`.") + + return@then + } + + player.sendMessage("Invalid syntax - ::objectinfo [object id]") + } \ No newline at end of file diff --git a/game/plugin/cmd/src/messaging-cmd.plugin.kts b/game/plugin/cmd/src/messaging-cmd.plugin.kts new file mode 100644 index 000000000..304c14685 --- /dev/null +++ b/game/plugin/cmd/src/messaging-cmd.plugin.kts @@ -0,0 +1,11 @@ +import org.apollo.game.model.entity.setting.PrivilegeLevel + +on_command("broadcast", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val message = arguments.joinToString(" ") + val broadcast = "[Broadcast] ${player.username.capitalize()}: $message" + + player.world.playerRepository.forEach { other -> + other.sendMessage(broadcast) + } + } diff --git a/game/plugin/cmd/src/punish-cmd.plugin.kts b/game/plugin/cmd/src/punish-cmd.plugin.kts new file mode 100644 index 000000000..3df799c47 --- /dev/null +++ b/game/plugin/cmd/src/punish-cmd.plugin.kts @@ -0,0 +1,59 @@ +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel + +/** + * Adds a command to mute a player. Admins cannot be muted. + */ +on_command("mute", PrivilegeLevel.MODERATOR) + .then { player -> + val name = arguments.joinToString(" ") + val targetPlayer = player.world.getPlayer(name) + + if (validate(player, targetPlayer)) { + targetPlayer.isMuted = true + player.sendMessage("You have just unmuted ${targetPlayer.username}.") + } + } + +/** + * Adds a command to unmute a player. + */ +on_command("unmute", PrivilegeLevel.MODERATOR) + .then { player -> + val name = arguments.joinToString(" ") + val targetPlayer = player.world.getPlayer(name) + + if (validate(player, targetPlayer)) { + targetPlayer.isMuted = false + player.sendMessage("You have just unmuted ${targetPlayer.username}.") + } + } + +/** + * Adds a command to ban a player. Admins cannot be banned. + */ +on_command("ban", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val name = arguments.joinToString(" ") + val targetPlayer = player.world.getPlayer(name) + + if (validate(player, targetPlayer)) { + targetPlayer.ban() + targetPlayer.logout() // TODO force logout + player.sendMessage("You have just banned ${targetPlayer.username}.") + } + } + +/** + * Ensures the player isn't null, and that they aren't an Administrator. + */ +fun validate(player: Player, targetPlayer: Player?): Boolean { + if (targetPlayer == null) { + player.sendMessage("That player does not exist.") + return false + } else if (targetPlayer.privilegeLevel == PrivilegeLevel.ADMINISTRATOR) { + player.sendMessage("You cannot perform this action on Administrators.") + return false + } + return true +} \ No newline at end of file diff --git a/game/plugin/cmd/src/skill-cmd.plugin.kts b/game/plugin/cmd/src/skill-cmd.plugin.kts new file mode 100644 index 000000000..b95ed0181 --- /dev/null +++ b/game/plugin/cmd/src/skill-cmd.plugin.kts @@ -0,0 +1,85 @@ +import com.google.common.primitives.Doubles +import com.google.common.primitives.Ints +import org.apollo.game.model.entity.Skill +import org.apollo.game.model.entity.SkillSet +import org.apollo.game.model.entity.setting.PrivilegeLevel + +/** + * Maximises the player's skill set. + */ +on_command("max", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val skills = player.skillSet + + for (skill in 0 until skills.size()) { + skills.addExperience(skill, SkillSet.MAXIMUM_EXP) + } + } + +/** + * Levels the specified skill to the specified level, optionally updating the current level as well. + */ +on_command("level", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::level [skill-id] [level] " + if (arguments.size !in 2..3) { + player.sendMessage(invalidSyntax) + return@then + } + + val skillId = Ints.tryParse(arguments[0]) + if (skillId == null) { + player.sendMessage(invalidSyntax) + return@then + } + val level = Ints.tryParse(arguments[1]) + if (level == null) { + player.sendMessage(invalidSyntax) + return@then + } + + if (skillId !in 0..20 || level !in 1..99) { + player.sendMessage(invalidSyntax) + return@then + } + + val experience = SkillSet.getExperienceForLevel(level).toDouble() + var current = level + + if (arguments.size == 3 && arguments[2] == "old") { + val skill = player.skillSet.getSkill(skillId) + current = skill.currentLevel + } + + player.skillSet.setSkill(skillId, Skill(experience, current, level)) + } + +/** + * Adds the specified amount of experience to the specified skill. + */ +on_command("xp", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::xp [skill-id] [experience]" + if (arguments.size != 2) { + player.sendMessage(invalidSyntax) + return@then + } + + val skillId = Ints.tryParse(arguments[0]) + if (skillId == null) { + player.sendMessage(invalidSyntax) + return@then + } + val experience = Doubles.tryParse(arguments[1]) + if (experience == null) { + player.sendMessage(invalidSyntax) + return@then + } + + if (skillId !in 0..20 || experience <= 0) { + player.sendMessage("Invalid syntax - ::xp [skill-id] [experience]") + return@then + } + + player.skillSet.addExperience(skillId, experience) + } \ No newline at end of file diff --git a/game/plugin/cmd/src/spawn-cmd.plugin.kts b/game/plugin/cmd/src/spawn-cmd.plugin.kts new file mode 100644 index 000000000..4626503d2 --- /dev/null +++ b/game/plugin/cmd/src/spawn-cmd.plugin.kts @@ -0,0 +1,121 @@ +import com.google.common.primitives.Ints +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Npc +import org.apollo.game.model.entity.setting.PrivilegeLevel + +/** + * An array of npcs that cannot be spawned. + */ +val blacklist: IntArray = intArrayOf() + +/** + * Spawns a non-blacklisted npc in the specified position, or the player's position if both 'x' and + * 'y' are not supplied. + */ +on_command("spawn", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::spawn [npc id] [optional-x] [optional-y] [optional-z]" + if (arguments.size !in intArrayOf(1, 3, 4)) { + player.sendMessage(invalidSyntax) + return@then + } + + val id = Ints.tryParse(arguments[0]) + if (id == null) { + player.sendMessage(invalidSyntax) + return@then + } + + if (id in blacklist) { + player.sendMessage("Sorry, npc $id is blacklisted!") + return@then + } + + val position: Position? + if (arguments.size == 1) { + position = player.position + } else { + val x = Ints.tryParse(arguments[1]) + val y = Ints.tryParse(arguments[2]) + + if (x == null || y == null) { + player.sendMessage(invalidSyntax) + return@then + } + + val height = if (arguments.size == 4) { + val h = Ints.tryParse(arguments[3]) + if (h == null) { + player.sendMessage(invalidSyntax) + return@then + } + + h + } else { + player.position.height + } + + position = Position(x, y, height) + } + + player.world.register(Npc(player.world, id, position)) + } + +/** + * Mass spawns npcs around the player. + */ +on_command("mass", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::mass [npc id] [range (1-5)]" + if (arguments.size != 2) { + player.sendMessage(invalidSyntax) + return@then + } + + val id = Ints.tryParse(arguments[0]) + if (id == null) { + player.sendMessage(invalidSyntax) + return@then + } + + val range = Ints.tryParse(arguments[1]) + if (range == null) { + player.sendMessage(invalidSyntax) + return@then + } + + if (id < 0 || range !in 1..5) { + player.sendMessage(invalidSyntax) + return@then + } + + if (id in blacklist) { + player.sendMessage("Sorry, npc $id is blacklisted!") + return@then + } + + val centerPosition = player.position + + val minX = centerPosition.x - range + val minY = centerPosition.y - range + val maxX = centerPosition.x + range + val maxY = centerPosition.y + range + val z = centerPosition.height + + for (x in minX..maxX) { + for (y in minY..maxY) { + player.world.register(Npc(player.world, id, Position(x, y, z))) + } + } + + player.sendMessage("Mass spawning npcs with id $id.") + } + +/** + * Unregisters all npcs from the world npc repository. + */ +on_command("clearnpcs", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + player.world.npcRepository.forEach { npc -> player.world.unregister(npc) } + player.sendMessage("Unregistered all npcs from the world.") + } \ No newline at end of file diff --git a/game/plugin/cmd/src/teleport-cmd.plugin.kts b/game/plugin/cmd/src/teleport-cmd.plugin.kts new file mode 100644 index 000000000..2ff9ad5db --- /dev/null +++ b/game/plugin/cmd/src/teleport-cmd.plugin.kts @@ -0,0 +1,154 @@ + +import com.google.common.primitives.Ints +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.plugin.api.Position.component1 +import org.apollo.game.plugin.api.Position.component2 +import org.apollo.game.plugin.api.Position.component3 + +/** + * Sends a player's position. + */ +on_command("pos", PrivilegeLevel.MODERATOR) + .then { player -> + val target: Player + val name: String + + if (arguments.size >= 1) { + name = arguments.joinToString(" ") + if (player.world.isPlayerOnline(name)) { + target = player.world.getPlayer(name) + } else { + player.sendMessage("$name is offline.") + return@then + } + } else { + target = player + } + + val (x, y, z) = target.position + val region = target.position.regionCoordinates + + player.sendMessage("${target.username} is located at ($x, $y, $z) in region (${region.x}, ${region.y}).") + } + +/** + * Teleports the player to the specified position. + */ +on_command("tele", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::tele [x] [y] [optional-z] or ::tele [place name]" + + if (arguments.size == 1) { + val query = arguments[0] + val results = TELEPORT_DESTINATIONS.filter { (name) -> name.startsWith(query) } + + if (results.isEmpty()) { + player.sendMessage("No destinations matching '$query'.") + player.sendMessage(invalidSyntax) + return@then + } else if (results.size > 1) { + player.sendMessage("Ambiguous query '$query' (could be $results). Please disambiguate.") + return@then + } + + val (name, dest) = results[0] + player.sendMessage("Teleporting to $name.") + player.teleport(dest) + + return@then + } + + if (arguments.size !in 2..3) { + player.sendMessage(invalidSyntax) + return@then + } + + val x = Ints.tryParse(arguments[0]) + if (x == null) { + player.sendMessage(invalidSyntax) + return@then + } + + val y = Ints.tryParse(arguments[1]) + if (y == null) { + player.sendMessage(invalidSyntax) + return@then + } + + var z = player.position.height + if (arguments.size == 3) { + val plane = Ints.tryParse(arguments[2]) + if (plane == null) { + player.sendMessage(invalidSyntax) + return@then + } + z = plane + } + + if (z in 0..4) { + player.teleport(Position(x, y, z)) + } + } + +/** + * Teleports the player to another player. + */ +on_command("teleto", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::teleto [player name]" + + if (arguments.size == 1) { + val playerName = arguments[0] + try { + val foundPlayer = player.world.getPlayer(playerName) + + if (foundPlayer == null) { + player.sendMessage("Player $playerName is currently offline or does not exist.") + return@then + } + + player.teleport(foundPlayer.position) + player.sendMessage("You have teleported to player $playerName.") + } catch (_: Exception) { + // Invalid player name syntax + player.sendMessage(invalidSyntax) + } + } else { + player.sendMessage(invalidSyntax) + } + } + +internal val TELEPORT_DESTINATIONS = listOf( + "alkharid" to Position(3292, 3171), + "ardougne" to Position(2662, 3304), + "barrows" to Position(3565, 3314), + "brimhaven" to Position(2802, 3179), + "burthorpe" to Position(2898, 3545), + "camelot" to Position(2757, 3478), + "canifis" to Position(3493, 3489), + "cw" to Position(2442, 3090), + "draynor" to Position(3082, 3249), + "duelarena" to Position(3370, 3267), + "edgeville" to Position(3087, 3504), + "entrana" to Position(2827, 3343), + "falador" to Position(2965, 3379), + "ge" to Position(3164, 3476), + "kbd" to Position(2273, 4680), + "keldagrim" to Position(2845, 10210), + "kq" to Position(3507, 9494), + "lumbridge" to Position(3222, 3219), + "lunar" to Position(2113, 3915), + "misc" to Position(2515, 3866), + "neit" to Position(2332, 3804), + "pc" to Position(2658, 2660), + "rellekka" to Position(2660, 3657), + "shilo" to Position(2852, 2955), + "taverley" to Position(2895, 3443), + "tutorial" to Position(3094, 3107), + "tzhaar" to Position(2480, 5175), + "varrock" to Position(3212, 3423), + "yanille" to Position(2605, 3096), + "zanaris" to Position(2452, 4473) +) \ No newline at end of file diff --git a/game/plugin/cmd/test/AnimateCommandTests.kt b/game/plugin/cmd/test/AnimateCommandTests.kt new file mode 100644 index 000000000..5a49dd6e1 --- /dev/null +++ b/game/plugin/cmd/test/AnimateCommandTests.kt @@ -0,0 +1,39 @@ +import io.mockk.verify +import org.apollo.game.command.Command +import org.apollo.game.model.Animation +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class AnimateCommandTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var player: Player + + @Test + fun `Plays the animation provided as input`() { + player.privilegeLevel = PrivilegeLevel.MODERATOR + world.commandDispatcher.dispatch(player, Command("animate", arrayOf("1"))) + + verify { player.playAnimation(Animation(1)) } + } + + @Test + fun `Help message sent on invalid syntax`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("animate", arrayOf(""))) + + verify { + player.sendMessage(contains("Invalid syntax")) + } + } +} \ No newline at end of file diff --git a/game/plugin/cmd/test/BankCommandTests.kt b/game/plugin/cmd/test/BankCommandTests.kt new file mode 100644 index 000000000..4ef96051c --- /dev/null +++ b/game/plugin/cmd/test/BankCommandTests.kt @@ -0,0 +1,27 @@ +import io.mockk.verify +import org.apollo.game.command.Command +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class BankCommandTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var player: Player + + @Test + fun `Opens bank when used`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("bank", emptyArray())) + + verify { player.openBank() } + } +} \ No newline at end of file diff --git a/game/plugin/cmd/test/BroadcastCommandTests.kt b/game/plugin/cmd/test/BroadcastCommandTests.kt new file mode 100644 index 000000000..d3ba8b16f --- /dev/null +++ b/game/plugin/cmd/test/BroadcastCommandTests.kt @@ -0,0 +1,29 @@ +import io.mockk.verify +import org.apollo.game.command.Command +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class BroadcastCommandTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var player: Player + + @Test + fun `Shows basic information on an item`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("broadcast", arrayOf("msg"))) + + verify { + player.sendMessage("[Broadcast] Test: msg") + } + } +} \ No newline at end of file diff --git a/game/plugin/cmd/test/ItemCommandTests.kt b/game/plugin/cmd/test/ItemCommandTests.kt new file mode 100644 index 000000000..c9884105a --- /dev/null +++ b/game/plugin/cmd/test/ItemCommandTests.kt @@ -0,0 +1,57 @@ +import io.mockk.verify +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.command.Command +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +@ExtendWith(ApolloTestingExtension::class) +class ItemCommandTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var player: Player + + @Test + fun `Defaults to an amount of 1`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("item", arrayOf("1"))) + + assertEquals(1, player.inventory.getAmount(1)) + } + + @Test + fun `Adds item of specified amount to inventory`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("item", arrayOf("1", "10"))) + + assertEquals(10, player.inventory.getAmount(1)) + } + + @ParameterizedTest(name = "::item {0}") + @ValueSource(strings = ["", "1 ", " 1"]) + fun `Help message sent on invalid syntax`(args: String) { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("item", args.split(" ").toTypedArray())) + + verify { + player.sendMessage(contains("Invalid syntax")) + } + } + + companion object { + @ItemDefinitions + val items = listOf(ItemDefinition(1)) + } +} \ No newline at end of file diff --git a/game/plugin/cmd/test/LookupCommandTests.kt b/game/plugin/cmd/test/LookupCommandTests.kt new file mode 100644 index 000000000..ada7ebeeb --- /dev/null +++ b/game/plugin/cmd/test/LookupCommandTests.kt @@ -0,0 +1,96 @@ +import io.mockk.verify +import org.apollo.cache.def.ItemDefinition +import org.apollo.cache.def.NpcDefinition +import org.apollo.cache.def.ObjectDefinition +import org.apollo.game.command.Command +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.NpcDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.ObjectDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +@ExtendWith(ApolloTestingExtension::class) +class LookupCommandTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var player: Player + + @Test + fun `Shows basic information on an item`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("iteminfo", arrayOf("1"))) + + verify { + player.sendMessage("Item 1 is called and is not members only.") + player.sendMessage("Its description is ``.") + } + } + + @Test + fun `Shows basic information on an npc`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("npcinfo", arrayOf("1"))) + + verify { + player.sendMessage("Npc 1 is called and has a combat level of 126.") + player.sendMessage("Its description is ``.") + } + } + + @Test + fun `Shows basic information on an object`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("objectinfo", arrayOf("1"))) + + verify { + player.sendMessage("Object 1 is called (width=1, length=1).") + player.sendMessage("Its description is ``.") + } + } + + @ParameterizedTest(name = "::{0} ") + @ValueSource(strings = ["npcinfo", "iteminfo", "objectinfo"]) + fun `Help message sent on invalid syntax`(command: String) { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command(command, arrayOf(""))) + + verify { + player.sendMessage(contains("Invalid syntax")) + } + } + + companion object { + @ItemDefinitions + val items = listOf(ItemDefinition(1).apply { + name = "" + description = "" + isMembersOnly = false + }) + + @NpcDefinitions + val npcs = listOf(NpcDefinition(1).apply { + name = "" + combatLevel = 126 + description = "" + }) + + @ObjectDefinitions + val objects = listOf(ObjectDefinition(1).apply { + name = "" + description = "" + width = 1 + length = 1 + }) + } +} \ No newline at end of file diff --git a/game/plugin/cmd/test/PunishCommandTests.kt b/game/plugin/cmd/test/PunishCommandTests.kt new file mode 100644 index 000000000..6f3c48801 --- /dev/null +++ b/game/plugin/cmd/test/PunishCommandTests.kt @@ -0,0 +1,97 @@ +import io.mockk.verify +import org.apollo.game.command.Command +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.Name +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class PunishCommandTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var admin: Player + + @TestMock + @Name("player_two") + lateinit var player: Player + + @BeforeEach + fun setup() { + admin.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + } + + @Test + fun `Staff can mute players`() { + world.commandDispatcher.dispatch(admin, Command("mute", arrayOf("player_two"))) + + verify { + player.setMuted(true) + } + } + + @Test + fun `Staff can unmute players`() { + player.isMuted = true + world.commandDispatcher.dispatch(admin, Command("unmute", arrayOf("player_two"))) + + verify { + player.setMuted(false) + } + } + + @Test + fun `Staff cant mute admins`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(admin, Command("unmute", arrayOf("player_two"))) + + verify { + admin.sendMessage("You cannot perform this action on Administrators.") + } + } + + @Test + fun `Cant mute players that arent online`() { + world.commandDispatcher.dispatch(admin, Command("mute", arrayOf("player555"))) + + verify { + admin.sendMessage("That player does not exist.") + } + } + + @Test + fun `Staff can ban players`() { + world.commandDispatcher.dispatch(admin, Command("ban", arrayOf("player_two"))) + + verify { + player.ban() + player.logout() + } + } + + @Test + fun `Staff cant ban admins`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(admin, Command("ban", arrayOf("player_two"))) + + verify { + admin.sendMessage("You cannot perform this action on Administrators.") + } + } + + @Test + fun `Cant ban players that arent online`() { + world.commandDispatcher.dispatch(admin, Command("ban", arrayOf("player555"))) + + verify { + admin.sendMessage("That player does not exist.") + } + } +} \ No newline at end of file diff --git a/game/plugin/cmd/test/SkillCommandTests.kt b/game/plugin/cmd/test/SkillCommandTests.kt new file mode 100644 index 000000000..4a243cb73 --- /dev/null +++ b/game/plugin/cmd/test/SkillCommandTests.kt @@ -0,0 +1,75 @@ +import io.mockk.verify +import org.apollo.game.command.Command +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +@ExtendWith(ApolloTestingExtension::class) +class SkillCommandTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var player: Player + + @BeforeEach + fun setup() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + } + + @Test + fun `Max stats to 99`() { + world.commandDispatcher.dispatch(player, Command("max", emptyArray())) + + for (stat in 0 until Skill.RUNECRAFT) { + assertEquals(99, player.skillSet.getCurrentLevel(stat)) + } + } + + @Test + fun `Set skill to given level`() { + world.commandDispatcher.dispatch(player, Command("level", arrayOf("1", "99"))) + + assertEquals(99, player.skillSet.getCurrentLevel(1)) + } + + @Test + fun `Set skill to given experience`() { + world.commandDispatcher.dispatch(player, Command("xp", arrayOf("1", "50"))) + + assertEquals(50.0, player.skillSet.getExperience(1)) + } + + @ParameterizedTest(name = "::{0}") + @ValueSource(strings = [ + "level 50 100", + "level 15 100", + "level 100", + "level 15 ", + "level", + "xp", + "xp 50 ", + "xp 50" + ]) + fun `Help message shown on invalid syntax`(command: String) { + val args = command.split(" ").toMutableList() + val cmd = args.removeAt(0) + + world.commandDispatcher.dispatch(player, Command(cmd, args.toTypedArray())) + + verify { + player.sendMessage(contains("Invalid syntax")) + } + } +} \ No newline at end of file diff --git a/game/plugin/cmd/test/SpawnCommandTests.kt b/game/plugin/cmd/test/SpawnCommandTests.kt new file mode 100644 index 000000000..8dfde978e --- /dev/null +++ b/game/plugin/cmd/test/SpawnCommandTests.kt @@ -0,0 +1,76 @@ +import io.mockk.verify +import org.apollo.cache.def.NpcDefinition +import org.apollo.game.command.Command +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.entity.Npc +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.NpcDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +@ExtendWith(ApolloTestingExtension::class) +class SpawnCommandTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var player: Player + + @BeforeEach + fun setup() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + } + + @Test + fun `Spawns NPC at players position by default`() { + player.position = Position(3, 3, 0) + world.commandDispatcher.dispatch(player, Command("spawn", arrayOf("1"))) + + verify { + world.register(match { + it.id == 1 && it.position == Position(3, 3, 0) + }) + } + } + + @Test + fun `Spawn NPC at given position`() { + world.commandDispatcher.dispatch(player, Command("spawn", arrayOf("1", "5", "5"))) + + verify { + world.register(match { + it.id == 1 && it.position == Position(5, 5) + }) + } + } + + @ParameterizedTest(name = "::spawn {0}") + @ValueSource(strings = [ + "", + "1 2", + "1 2 ", + "1 2 3 " + ]) + fun `Help message on invalid syntax`(args: String) { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("spawn", args.split(" ").toTypedArray())) + + verify { + player.sendMessage(contains("Invalid syntax")) + } + } + + companion object { + @NpcDefinitions + val npcs = listOf(NpcDefinition(1)) + } +} \ No newline at end of file diff --git a/game/plugin/cmd/test/TeleportCommandTests.kt b/game/plugin/cmd/test/TeleportCommandTests.kt new file mode 100644 index 000000000..2fffdf342 --- /dev/null +++ b/game/plugin/cmd/test/TeleportCommandTests.kt @@ -0,0 +1,94 @@ +import io.mockk.verify +import org.apollo.game.command.Command +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.Name +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +@ExtendWith(ApolloTestingExtension::class) +class TeleportCommandTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var player: Player + + @TestMock + @Name("player_two") + lateinit var player_two: Player + + @Test + fun `Teleport to given coordinates`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("tele", arrayOf("1", "2", "0"))) + + assertEquals(Position(1, 2, 0), player.position) + } + + @Test + fun `Teleport to given coordinates on players plane when no plane given`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + player.position = Position(1, 1, 1) + world.commandDispatcher.dispatch(player, Command("tele", arrayOf("1", "2"))) + + assertEquals(Position(1, 2, 1), player.position) + } + + @Test + fun `Shows current position information`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + player.position = Position(1, 2, 3) + world.commandDispatcher.dispatch(player, Command("pos", emptyArray())) + + verify { + player.sendMessage(contains("1, 2, 3")) + } + } + + @Test + fun `Shows another players current position information`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + player_two.position = Position(1, 2, 3) + world.commandDispatcher.dispatch(player, Command("pos", arrayOf("player_two"))) + + verify { + player.sendMessage(contains("1, 2, 3")) + } + } + + @Test + fun `Shows no position information for a nonexistent player`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("pos", arrayOf("player999"))) + + verify { + player.sendMessage(contains("offline")) + } + } + + @ParameterizedTest(name = "::tele {0}") + @ValueSource(strings = [ + "1 2 ", + "1 2", + "1", + "1 2 3 4" + ]) + fun `Help message sent on invalid syntax`(args: String) { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("tele", args.split(" ").toTypedArray())) + + verify { + player.sendMessage(contains("Invalid syntax")) + } + } +} \ No newline at end of file diff --git a/game/plugin/cmd/test/TeleportToPlayerCommandTests.kt b/game/plugin/cmd/test/TeleportToPlayerCommandTests.kt new file mode 100644 index 000000000..d70c8e4d3 --- /dev/null +++ b/game/plugin/cmd/test/TeleportToPlayerCommandTests.kt @@ -0,0 +1,52 @@ +import io.mockk.verify +import org.apollo.game.command.Command +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.Name +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +@ExtendWith(ApolloTestingExtension::class) +class TeleportToPlayerCommandTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var player: Player + + @TestMock + @Name("player_two") + lateinit var secondPlayer: Player + + @Test + fun `Teleport to given player`() { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + player.position = Position(300, 300) + secondPlayer.position = Position(1, 2) + world.commandDispatcher.dispatch(player, Command("teleto", arrayOf("player_two"))) + + assertEquals(secondPlayer.position, player.position) + } + + @ParameterizedTest(name = "::teleto {0}") + @ValueSource(strings = [ + "" + ]) + fun `Help message sent on invalid syntax`(args: String) { + player.privilegeLevel = PrivilegeLevel.ADMINISTRATOR + world.commandDispatcher.dispatch(player, Command("teleto", args.split(" ").toTypedArray())) + + verify { + player.sendMessage(contains("Invalid syntax")) + } + } +} \ No newline at end of file diff --git a/game/plugin/consumables/build.gradle b/game/plugin/consumables/build.gradle new file mode 100644 index 000000000..3a9351392 --- /dev/null +++ b/game/plugin/consumables/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/consumables/src/consumables.kt b/game/plugin/consumables/src/consumables.kt new file mode 100644 index 000000000..f8bc5ef0e --- /dev/null +++ b/game/plugin/consumables/src/consumables.kt @@ -0,0 +1,80 @@ +package org.apollo.plugin.consumables + +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill + +/** + * An item that can be consumed to restore or buff stats. + */ +abstract class Consumable(val name: String, val id: Int, val sound: Int, val delay: Int, val replacement: Int?) { + + abstract fun addEffect(player: Player) + + fun consume(player: Player, slot: Int) { + addEffect(player) + player.inventory.reset(slot) + + if (replacement != null) { + player.inventory.add(replacement) + } + } +} + +private val consumables = mutableMapOf() + +fun isConsumable(itemId: Int) = consumables.containsKey(itemId) +fun lookupConsumable(itemId: Int): Consumable = consumables.get(itemId)!! +fun consumable(consumable: Consumable) = consumables.put(consumable.id, consumable) + +enum class FoodOrDrinkType(val action: String) { + FOOD("eat"), DRINK("drink") +} + +class FoodOrDrink : Consumable { + + companion object { + const val EAT_FOOD_SOUND = 317 + } + + val restoration: Int + val type: FoodOrDrinkType + + constructor( + name: String, + id: Int, + delay: Int, + type: FoodOrDrinkType, + restoration: Int, + replacement: Int? = null + ) : super(name, id, EAT_FOOD_SOUND, delay, replacement) { + this.type = type + this.restoration = restoration + } + + override fun addEffect(player: Player) { + val hitpoints = player.skillSet.getSkill(Skill.HITPOINTS) + val hitpointsLevel = hitpoints.currentLevel + val newHitpointsLevel = Math.min(hitpointsLevel + restoration, hitpoints.maximumLevel) + + player.sendMessage("You ${type.action} the $name.") + if (newHitpointsLevel > hitpointsLevel) { + player.sendMessage("It heals some health.") + } + + player.skillSet.setCurrentLevel(Skill.HITPOINTS, newHitpointsLevel) + } +} + +/** + * Define a new type of [Consumable] food. + */ +fun food(name: String, id: Int, restoration: Int, replacement: Int? = null, delay: Int = 3) { + consumable(FoodOrDrink(name, id, delay, FoodOrDrinkType.FOOD, restoration, replacement)) +} + +/** + * Define a new type of [Consumable] drink. + */ +fun drink(name: String, id: Int, restoration: Int, replacement: Int? = null, delay: Int = 3) { + consumable(FoodOrDrink(name, id, delay, FoodOrDrinkType.DRINK, restoration, replacement)) +} \ No newline at end of file diff --git a/game/plugin/consumables/src/consumables.plugin.kts b/game/plugin/consumables/src/consumables.plugin.kts new file mode 100644 index 000000000..1711d43eb --- /dev/null +++ b/game/plugin/consumables/src/consumables.plugin.kts @@ -0,0 +1,37 @@ +import org.apollo.game.action.ActionBlock +import org.apollo.game.action.AsyncAction +import org.apollo.game.message.impl.ItemOptionMessage +import org.apollo.game.model.Animation +import org.apollo.game.model.entity.Player +import org.apollo.net.message.Message +import org.apollo.plugin.consumables.* + +on { ItemOptionMessage::class } + .where { option == 1 && isConsumable(id) } + .then { + ConsumeAction.start(this, it, lookupConsumable(id), slot) + } + +class ConsumeAction(val consumable: Consumable, player: Player, val slot: Int) : + AsyncAction(CONSUME_STARTUP_DELAY, true, player) { + + companion object { + const val CONSUME_ANIMATION_ID = 829 + const val CONSUME_STARTUP_DELAY = 2 + + /** + * Starts a [ConsumeAction] for the specified [Player], terminating the [Message] that triggered it. + */ + fun start(message: Message, player: Player, consumable: Consumable, slot: Int) { + player.startAction(ConsumeAction(consumable, player, slot)) + message.terminate() + } + } + + override fun action(): ActionBlock = { + consumable.consume(mob, slot) + mob.playAnimation(Animation(CONSUME_ANIMATION_ID)) + wait(consumable.delay) + stop() + } +} diff --git a/game/plugin/consumables/src/drinks.plugin.kts b/game/plugin/consumables/src/drinks.plugin.kts new file mode 100644 index 000000000..a96a8a3f8 --- /dev/null +++ b/game/plugin/consumables/src/drinks.plugin.kts @@ -0,0 +1,24 @@ +import org.apollo.plugin.consumables.drink + +// # Wine +drink(name = "jug_of_wine", id = 1993, restoration = 11) + +// # Hot Drinks +drink(name = "nettle_tea", id = 4239, restoration = 3) +drink(name = "nettle_tea", id = 4240, restoration = 3) + +// # Gnome Cocktails +drink(name = "fruit_blast", id = 2034, restoration = 9) +drink(name = "fruit_blast", id = 2084, restoration = 9) +drink(name = "pineapple_punch", id = 2036, restoration = 9) +drink(name = "pineapple_punch", id = 2048, restoration = 9) +drink(name = "wizard_blizzard", id = 2040, restoration = 5) // -4 attack, +5 strength also +drink(name = "wizard_blizzard", id = 2054, restoration = 5) // -4 attack, +5 strength also +drink(name = "short_green_guy", id = 2038, restoration = 5) // -4 attack, +5 strength also +drink(name = "short_green_guy", id = 2080, restoration = 5) // -4 attack, +5 strength also +drink(name = "drunk_dragon", id = 2032, restoration = 5) // -4 attack, +6 strength also +drink(name = "drunk_dragon", id = 2092, restoration = 5) // -4 attack, +6 strength also +drink(name = "chocolate_saturday", id = 2030, restoration = 7) // -4 attack, +6 strength also +drink(name = "chocolate_saturday", id = 2074, restoration = 7) // -4 attack, +6 strength also +drink(name = "blurberry_special", id = 2028, restoration = 7) // -4 attack, +6 strength also +drink(name = "blurberry_special", id = 2064, restoration = 7) // -4 attack, +6 strength also diff --git a/game/plugin/consumables/src/foods.plugin.kts b/game/plugin/consumables/src/foods.plugin.kts new file mode 100644 index 000000000..029818900 --- /dev/null +++ b/game/plugin/consumables/src/foods.plugin.kts @@ -0,0 +1,159 @@ +import org.apollo.plugin.consumables.food + +food(name = "anchovies", id = 319, restoration = 1) +food(name = "crab_meat", id = 7521, restoration = 2, replacement = 7523) +food(name = "crab_meat", id = 7523, restoration = 2, replacement = 7524) +food(name = "crab_meat", id = 7524, restoration = 2, replacement = 7525) +food(name = "crab_meat", id = 7525, restoration = 2, replacement = 7526) +food(name = "crab_meat", id = 7526, restoration = 2) +food(name = "shrimp", id = 315, restoration = 3) +food(name = "sardine", id = 325, restoration = 3) +food(name = "cooked_meat", id = 2142, restoration = 3) +food(name = "cooked_chicken", id = 2140, restoration = 3) +food(name = "ugthanki_meat", id = 1861, restoration = 3) +food(name = "karambwanji", id = 3151, restoration = 3) +food(name = "cooked_rabbit", id = 3228, restoration = 5) +food(name = "herring", id = 347, restoration = 6) +food(name = "trout", id = 333, restoration = 7) +food(name = "cod", id = 339, restoration = 7) +food(name = "mackeral", id = 355, restoration = 7) +food(name = "roast_rabbit", id = 7223, restoration = 7) +food(name = "pike", id = 351, restoration = 8) +food(name = "lean_snail_meat", id = 3371, restoration = 8) +food(name = "salmon", id = 329, restoration = 9) +food(name = "tuna", id = 361, restoration = 10) +food(name = "lobster", id = 379, restoration = 12) +food(name = "bass", id = 365, restoration = 13) +food(name = "swordfish", id = 373, restoration = 14) +food(name = "cooked_jubbly", id = 7568, restoration = 15) +food(name = "monkfish", id = 7946, restoration = 16) +food(name = "cooked_karambwan", id = 3144, restoration = 18, delay = 0) +food(name = "shark", id = 385, restoration = 20) +food(name = "sea_turtle", id = 397, restoration = 21) +food(name = "manta_ray", id = 391, restoration = 22) + +// # Breads/Wraps +food(name = "bread", id = 2309, restoration = 5) +food(name = "oomlie_wrap", id = 2343, restoration = 14) +food(name = "ugthanki_kebab", id = 1883, restoration = 19) + +// # Fruits +food(name = "banana", id = 1963, restoration = 2) +food(name = "sliced_banana", id = 3162, restoration = 2) +food(name = "lemon", id = 2102, restoration = 2) +food(name = "lemon_chunks", id = 2104, restoration = 2) +food(name = "lemon_slices", id = 2106, restoration = 2) +food(name = "lime", id = 2120, restoration = 2) +food(name = "lime_chunks", id = 2122, restoration = 2) +food(name = "lime_slices", id = 2124, restoration = 2) +food(name = "strawberry", id = 5504, restoration = 5) +food(name = "papaya_fruit", id = 5972, restoration = 8) +food(name = "pineapple_chunks", id = 2116, restoration = 2) +food(name = "pineapple_ring", id = 2118, restoration = 2) +food(name = "orange", id = 2108, restoration = 2) +food(name = "orange_rings", id = 2110, restoration = 2) +food(name = "orange_slices", id = 2112, restoration = 2) + +// # Pies +// # TODO: pie special effects (e.g. fish pie raises fishing level) +food(name = "redberry_pie", id = 2325, restoration = 5, replacement = 2333, delay = 1) +food(name = "redberry_pie", id = 2333, restoration = 5, delay = 1) + +food(name = "meat_pie", id = 2327, restoration = 6, replacement = 2331, delay = 1) +food(name = "meat_pie", id = 2331, restoration = 6, delay = 1) + +food(name = "apple_pie", id = 2323, restoration = 7, replacement = 2335, delay = 1) +food(name = "apple_pie", id = 2335, restoration = 7, delay = 1) + +food(name = "fish_pie", id = 7188, restoration = 6, replacement = 7190, delay = 1) +food(name = "fish_pie", id = 7190, restoration = 6, delay = 1) + +food(name = "admiral_pie", id = 7198, restoration = 8, replacement = 7200, delay = 1) +food(name = "admiral_pie", id = 7200, restoration = 8, delay = 1) + +food(name = "wild_pie", id = 7208, restoration = 11, replacement = 7210, delay = 1) +food(name = "wild_pie", id = 7210, restoration = 11, delay = 1) + +food(name = "summer_pie", id = 7218, restoration = 11, replacement = 7220, delay = 1) +food(name = "summer_pie", id = 7220, restoration = 11, delay = 1) + +// # Stews +food(name = "stew", id = 2003, restoration = 11) +food(name = "banana_stew", id = 4016, restoration = 11) +food(name = "curry", id = 2011, restoration = 19) + +// # Pizzas +food(name = "plain_pizza", id = 2289, restoration = 7, replacement = 2291) +food(name = "plain_pizza", id = 2291, restoration = 7) + +food(name = "meat_pizza", id = 2293, restoration = 8, replacement = 2295) +food(name = "meat_pizza", id = 2295, restoration = 8) + +food(name = "anchovy_pizza", id = 2297, restoration = 9, replacement = 2299) +food(name = "anchovy_pizza", id = 2299, restoration = 9) + +food(name = "pineapple_pizza", id = 2301, restoration = 11, replacement = 2303) +food(name = "pineapple_pizza", id = 2303, restoration = 11) + +// # Cakes +food(name = "fishcake", id = 7530, restoration = 11) + +food(name = "cake", id = 1891, restoration = 4, replacement = 1893) +food(name = "cake", id = 1893, restoration = 4, replacement = 1895) +food(name = "cake", id = 1895, restoration = 4) + +food(name = "chocolate_cake", id = 1897, restoration = 5, replacement = 1899) +food(name = "chocolate_cake", id = 1899, restoration = 5, replacement = 1901) +food(name = "chocolate_cake", id = 1901, restoration = 5) + +// # Vegetables +food(name = "potato", id = 1942, restoration = 1) +food(name = "spinach_roll", id = 1969, restoration = 2) +food(name = "baked_potato", id = 6701, restoration = 4) +food(name = "sweetcorn", id = 5988, restoration = 10) +food(name = "sweetcorn_bowl", id = 7088, restoration = 13) +food(name = "potato_with_butter", id = 6703, restoration = 14) +food(name = "chili_potato", id = 7054, restoration = 14) +food(name = "potato_with_cheese", id = 6705, restoration = 16) +food(name = "egg_potato", id = 7056, restoration = 16) +food(name = "mushroom_potato", id = 7058, restoration = 20) +food(name = "tuna_potato", id = 7060, restoration = 22) + +// # Dairy +food(name = "cheese", id = 1985, restoration = 2) +food(name = "pot_of_cream", id = 2130, restoration = 1) + +// # Gnome Food +food(name = "toads_legs", id = 2152, restoration = 3) + +// # Gnome Bowls +food(name = "worm_hole", id = 2191, restoration = 12) +food(name = "worm_hole", id = 2233, restoration = 12) +food(name = "vegetable_ball", id = 2195, restoration = 12) +food(name = "vegetable_ball", id = 2235, restoration = 12) +food(name = "tangled_toads_legs", id = 2187, restoration = 15) +food(name = "tangled_toads_legs", id = 2231, restoration = 15) +food(name = "chocolate_bomb", id = 2185, restoration = 15) +food(name = "chocolate_bomb", id = 2229, restoration = 15) + +// # Gnome Crunchies +food(name = "toad_crunchies", id = 2217, restoration = 7) +food(name = "toad_crunchies", id = 2243, restoration = 7) +food(name = "spicy_crunchies", id = 2213, restoration = 7) +food(name = "spicy_crunchies", id = 2241, restoration = 7) +food(name = "worm_crunchies", id = 2205, restoration = 8) +food(name = "worm_crunchies", id = 2237, restoration = 8) +food(name = "chocchip_crunchies", id = 2209, restoration = 7) +food(name = "chocchip_crunchies", id = 2239, restoration = 7) + +// # Gnome Battas +food(name = "fruit_batta", id = 2225, restoration = 11) +food(name = "fruit_batta", id = 2277, restoration = 11) +food(name = "toad_batta", id = 2221, restoration = 11) +food(name = "toad_batta", id = 2255, restoration = 11) +food(name = "worm_batta", id = 2219, restoration = 11) +food(name = "worm_batta", id = 2253, restoration = 11) +food(name = "vegetable_batta", id = 2227, restoration = 11) +food(name = "vegetable_batta", id = 2281, restoration = 11) +food(name = "cheese_tom_batta", id = 2223, restoration = 11) +food(name = "cheese_tom_batta", id = 2259, restoration = 11) diff --git a/game/plugin/consumables/test/FoodOrDrinkTests.kt b/game/plugin/consumables/test/FoodOrDrinkTests.kt new file mode 100644 index 000000000..59aad8da8 --- /dev/null +++ b/game/plugin/consumables/test/FoodOrDrinkTests.kt @@ -0,0 +1,93 @@ +package org.apollo.plugin.consumables + +import io.mockk.verify +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.plugin.testing.assertions.after +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.assertions.verifyAfter +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.apollo.game.plugin.testing.junit.api.interactions.interactWithItem +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class FoodOrDrinkTests { + + companion object { + const val TEST_FOOD_NAME = "test_food" + const val TEST_FOOD_ID = 2000 + const val TEST_FOOD_RESTORATION = 5 + + const val TEST_DRINK_NAME = "test_drink" + const val TEST_DRINK_ID = 2001 + const val TEST_DRINK_RESTORATION = 5 + + const val HP_LEVEL = 5 + const val MAX_HP_LEVEL = 10 + } + + @TestMock + lateinit var player: Player + + @TestMock + lateinit var action: ActionCapture + + @BeforeEach + fun setup() { + val skills = player.skillSet + skills.setCurrentLevel(Skill.HITPOINTS, HP_LEVEL) + skills.setMaximumLevel(Skill.HITPOINTS, MAX_HP_LEVEL) + + food("test_food", TEST_FOOD_ID, TEST_FOOD_RESTORATION) + drink("test_drink", TEST_DRINK_ID, TEST_DRINK_RESTORATION) + } + + @Test + fun `Consuming food or drink should restore the players hitpoints`() { + val expectedHpLevel = TEST_FOOD_RESTORATION + HP_LEVEL + + player.interactWithItem(TEST_FOOD_ID, option = 1, slot = 1) + + after(action.complete()) { + assertEquals(expectedHpLevel, player.skillSet.getCurrentLevel(Skill.HITPOINTS)) + } + } + + @Test + fun `A message should be sent notifying the player if the item restored hitpoints`() { + player.interactWithItem(TEST_FOOD_ID, option = 1, slot = 1) + + verifyAfter(action.complete()) { player.sendMessage("It heals some health.") } + } + + @Test + fun `A message should not be sent to the player if the item did not restore hitpoints`() { + player.skillSet.setCurrentLevel(Skill.HITPOINTS, MAX_HP_LEVEL) + player.interactWithItem(TEST_FOOD_ID, option = 1, slot = 1) + + after(action.complete()) { + verify(exactly = 0) { player.sendMessage(contains("it heals some")) } + } + } + + @Test + fun `A message should be sent saying the player has drank an item when consuming a drink`() { + player.interactWithItem(TEST_DRINK_ID, option = 1, slot = 1) + + verifyAfter(action.complete()) { player.sendMessage("You drink the $TEST_DRINK_NAME.") } + } + + @Test + fun `A message should be sent saying the player has eaten an item when consuming food`() { + player.interactWithItem(TEST_FOOD_ID, option = 1, slot = 1) + + verifyAfter(action.complete()) { player.sendMessage("You eat the $TEST_FOOD_NAME.") } + } +} \ No newline at end of file diff --git a/game/plugin/dummy/build.gradle b/game/plugin/dummy/build.gradle new file mode 100644 index 000000000..3a9351392 --- /dev/null +++ b/game/plugin/dummy/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/dummy/src/dummy.plugin.kts b/game/plugin/dummy/src/dummy.plugin.kts new file mode 100644 index 000000000..75dee6f19 --- /dev/null +++ b/game/plugin/dummy/src/dummy.plugin.kts @@ -0,0 +1,65 @@ +import org.apollo.game.action.ActionBlock +import org.apollo.game.action.AsyncDistancedAction +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.game.model.Animation +import org.apollo.game.model.Position +import org.apollo.game.model.entity.* +import org.apollo.net.message.Message + +/** + * A list of [ObjectDefinition] identifiers which are training dummies. + */ +val DUMMY_IDS = setOf(823) + +on { ObjectActionMessage::class } + .where { option == 2 && id in DUMMY_IDS } + .then { DummyAction.start(this, it, position) } + +class DummyAction(val player: Player, position: Position) : AsyncDistancedAction(0, true, player, position, DISTANCE) { + + companion object { + + /** + * The maximum level a player can be before the dummy stops giving XP. + */ + const val LEVEL_THRESHOLD = 8 + + /** + * The number of experience points per hit. + */ + const val EXP_PER_HIT = 5.0 + + /** + * The minimum distance a player can be from the dummy. + */ + const val DISTANCE = 1 + + /** + * The [Animation] played when a player hits a dummy. + */ + val PUNCH_ANIMATION = Animation(422) + + /** + * Starts a [DummyAction] for the specified [Player], terminating the [Message] that triggered it. + */ + fun start(message: Message, player: Player, position: Position) { + player.startAction(DummyAction(player, position)) + message.terminate() + } + } + + override fun action(): ActionBlock = { + mob.sendMessage("You hit the dummy.") + mob.turnTo(position) + mob.playAnimation(PUNCH_ANIMATION) + wait() + + val skills = player.skillSet + + if (skills.getSkill(Skill.ATTACK).maximumLevel >= LEVEL_THRESHOLD) { + player.sendMessage("There is nothing more you can learn from hitting a dummy.") + } else { + skills.addExperience(Skill.ATTACK, EXP_PER_HIT) + } + } +} diff --git a/game/plugin/dummy/test/TrainingDummyTest.kt b/game/plugin/dummy/test/TrainingDummyTest.kt new file mode 100644 index 000000000..ccc22be2b --- /dev/null +++ b/game/plugin/dummy/test/TrainingDummyTest.kt @@ -0,0 +1,63 @@ + +import io.mockk.verify +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.plugin.testing.assertions.after +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.apollo.game.plugin.testing.junit.api.interactions.interactWith +import org.apollo.game.plugin.testing.junit.api.interactions.spawnObject +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class TrainingDummyTest { + + companion object { + const val DUMMY_ID = 823 + val DUMMY_POSITION = Position(3200, 3230, 0) + } + + @TestMock + lateinit var action: ActionCapture + + @TestMock + lateinit var player: Player + + @TestMock + lateinit var world: World + + @Test + fun `Hitting the training dummy should give the player attack experience`() { + val dummy = world.spawnObject(DUMMY_ID, DUMMY_POSITION) + val skills = player.skillSet + val beforeExp = skills.getExperience(Skill.ATTACK) + + player.interactWith(dummy, option = 2) + + after(action.complete()) { + assertTrue(skills.getExperience(Skill.ATTACK) > beforeExp) + } + } + + @Test + fun `The player should stop getting attack experience from the training dummy at level 8`() { + val dummy = world.spawnObject(DUMMY_ID, DUMMY_POSITION) + val skills = player.skillSet + skills.setMaximumLevel(Skill.ATTACK, 8) + val beforeExp = skills.getExperience(Skill.ATTACK) + + player.interactWith(dummy, option = 2) + + after(action.complete()) { + verify { player.sendMessage(contains("nothing more you can learn")) } + assertEquals(beforeExp, skills.getExperience(Skill.ATTACK)) + } + } +} diff --git a/game/plugin/emote-tab/build.gradle b/game/plugin/emote-tab/build.gradle new file mode 100644 index 000000000..3a9351392 --- /dev/null +++ b/game/plugin/emote-tab/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/emote-tab/src/Emote.kt b/game/plugin/emote-tab/src/Emote.kt new file mode 100644 index 000000000..2b61f88ba --- /dev/null +++ b/game/plugin/emote-tab/src/Emote.kt @@ -0,0 +1,40 @@ +import org.apollo.game.model.Animation + +enum class Emote(val button: Int, animation: Int) { + ANGRY_EMOTE(button = 165, animation = 859), + BECKON_EMOTE(button = 167, animation = 864), + BLOW_KISS_EMOTE(button = 11_100, animation = 1368), + BOW_EMOTE(button = 164, animation = 858), + CHEER_EMOTE(button = 171, animation = 862), + CLAP_EMOTE(button = 172, animation = 865), + CLIMB_ROPE_EMOTE(button = 6503, animation = 1130), + CRY_EMOTE(button = 161, animation = 860), + DANCE_EMOTE(button = 166, animation = 866), + GLASS_BOX_EMOTE(button = 667, animation = 1131), + GLASS_WALL_EMOTE(button = 666, animation = 1128), + GOBLIN_BOW_EMOTE(button = 13_383, animation = 2127), + GOBLIN_DANCE_EMOTE(button = 13_384, animation = 2128), + HEAD_BANG_EMOTE(button = 13_365, animation = 2108), + JIG_EMOTE(button = 13_363, animation = 2106), + JOY_JUMP_EMOTE(button = 13_366, animation = 2109), + LAUGH_EMOTE(button = 170, animation = 861), + LEAN_EMOTE(button = 6_506, animation = 1129), + NO_EMOTE(button = 169, animation = 856), + PANIC_EMOTE(button = 3_362, animation = 2105), + RASPBERRY_EMOTE(button = 13_367, animation = 2110), + SALUTE_EMOTE(button = 13_369, animation = 2112), + SHRUG_EMOTE(button = 13_370, animation = 2113), + SPIN_EMOTE(button = 13_364, animation = 2107), + THINKING_EMOTE(button = 162, animation = 857), + WAVE_EMOTE(button = 163, animation = 863), + YAWN_EMOTE(button = 13_368, animation = 2111), + YES_EMOTE(button = 168, animation = 855); + + val animation = Animation(animation) + + companion object { + internal val MAP = Emote.values().associateBy { it.button } + + fun fromButton(button: Int): Emote? = MAP[button] + } +} \ No newline at end of file diff --git a/game/plugin/emote-tab/src/EmoteTab.plugin.kts b/game/plugin/emote-tab/src/EmoteTab.plugin.kts new file mode 100644 index 000000000..0f546e818 --- /dev/null +++ b/game/plugin/emote-tab/src/EmoteTab.plugin.kts @@ -0,0 +1,7 @@ +import org.apollo.game.message.impl.ButtonMessage + +on { ButtonMessage::class } + .where { widgetId in Emote.MAP } + .then { player -> + player.playAnimation(Emote.fromButton(widgetId)!!.animation) + } \ No newline at end of file diff --git a/game/plugin/entity/actions/build.gradle b/game/plugin/entity/actions/build.gradle new file mode 100644 index 000000000..3a9351392 --- /dev/null +++ b/game/plugin/entity/actions/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/entity/actions/src/PlayerActionEvent.kt b/game/plugin/entity/actions/src/PlayerActionEvent.kt new file mode 100644 index 000000000..3cca682a5 --- /dev/null +++ b/game/plugin/entity/actions/src/PlayerActionEvent.kt @@ -0,0 +1,6 @@ +package org.apollo.game.plugin.entity.actions + +import org.apollo.game.model.entity.Player +import org.apollo.game.model.event.PlayerEvent + +class PlayerActionEvent(player: Player, val target: Player, val action: PlayerActionType) : PlayerEvent(player) \ No newline at end of file diff --git a/game/plugin/entity/actions/src/PlayerActionType.kt b/game/plugin/entity/actions/src/PlayerActionType.kt new file mode 100644 index 000000000..049349157 --- /dev/null +++ b/game/plugin/entity/actions/src/PlayerActionType.kt @@ -0,0 +1,8 @@ +package org.apollo.game.plugin.entity.actions + +enum class PlayerActionType(val displayName: String, val slot: Int, val primary: Boolean = true) { + ATTACK("Attack", 2), + CHALLENGE("Challenge", 2), + FOLLOW("Follow", 4), + TRADE("Trade with", 5) +} \ No newline at end of file diff --git a/game/plugin/entity/actions/src/PlayerActions.plugin.kts b/game/plugin/entity/actions/src/PlayerActions.plugin.kts new file mode 100644 index 000000000..675a8a53e --- /dev/null +++ b/game/plugin/entity/actions/src/PlayerActions.plugin.kts @@ -0,0 +1,20 @@ +package org.apollo.game.plugin.entity.actions + +import org.apollo.game.message.impl.PlayerActionMessage +import org.apollo.game.model.event.impl.LoginEvent + +on { PlayerActionMessage::class } + .then { + val action = it.actionAt(option) + if (action != null) { + it.world.submit(PlayerActionEvent(it, it.world.playerRepository[index], action)) + } + + terminate() + } + +on_player_event { LoginEvent::class } + .then { + it.enableAction(PlayerActionType.FOLLOW) + it.enableAction(PlayerActionType.TRADE) + } \ No newline at end of file diff --git a/game/plugin/entity/actions/src/playerAction.kt b/game/plugin/entity/actions/src/playerAction.kt new file mode 100644 index 000000000..d08b77974 --- /dev/null +++ b/game/plugin/entity/actions/src/playerAction.kt @@ -0,0 +1,28 @@ +package org.apollo.game.plugin.entity.actions + +import java.util.* +import org.apollo.game.message.impl.SetPlayerActionMessage +import org.apollo.game.model.entity.Player + +fun Player.enableAction(action: PlayerActionType) { + send(SetPlayerActionMessage(action.displayName, action.slot, action.primary)) + actions += action +} + +fun Player.disableAction(action: PlayerActionType) { + send(SetPlayerActionMessage("null", action.slot, action.primary)) + actions -= action +} + +fun Player.actionEnabled(action: PlayerActionType): Boolean { + return action in actions +} + +fun Player.actionAt(slot: Int): PlayerActionType? { + return actions.find { it.slot == slot } +} + +private val playerActionsMap = mutableMapOf>() + +private val Player.actions: EnumSet + get() = playerActionsMap.computeIfAbsent(this) { EnumSet.noneOf(PlayerActionType::class.java) } diff --git a/game/plugin/entity/actions/test/PlayerActionTests.kt b/game/plugin/entity/actions/test/PlayerActionTests.kt new file mode 100644 index 000000000..153345d0b --- /dev/null +++ b/game/plugin/entity/actions/test/PlayerActionTests.kt @@ -0,0 +1,33 @@ +package org.apollo.game.plugin.entity.actions + +import io.mockk.verify +import org.apollo.game.message.impl.SetPlayerActionMessage +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +@ExtendWith(ApolloTestingExtension::class) +class PlayerActionTests { + + @TestMock + lateinit var player: Player + + @ParameterizedTest + @EnumSource(PlayerActionType::class) + fun `enabling and disabling PlayerActions sends SetPlayerActionMessages`(type: PlayerActionType) { + player.enableAction(type) + + verify { player.send(eq(SetPlayerActionMessage(type.displayName, type.slot, type.primary))) } + assertTrue(player.actionEnabled(type)) { "Action $type should have been enabled, but was not." } + + player.disableAction(type) + + verify { player.send(eq(SetPlayerActionMessage("null", type.slot, type.primary))) } + assertFalse(player.actionEnabled(type)) { "Action $type should not have been enabled, but was." } + } +} \ No newline at end of file diff --git a/game/plugin/entity/following/build.gradle b/game/plugin/entity/following/build.gradle new file mode 100644 index 000000000..4aca75d50 --- /dev/null +++ b/game/plugin/entity/following/build.gradle @@ -0,0 +1,11 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:entity:pathing') + implementation project(':game:plugin:entity:actions') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/entity/following/src/org/apollo/plugin/entity/following/FollowAction.kt b/game/plugin/entity/following/src/org/apollo/plugin/entity/following/FollowAction.kt new file mode 100644 index 000000000..517c0af96 --- /dev/null +++ b/game/plugin/entity/following/src/org/apollo/plugin/entity/following/FollowAction.kt @@ -0,0 +1,49 @@ +package org.apollo.plugin.entity.following + +import org.apollo.game.action.Action +import org.apollo.game.model.Direction +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player +import org.apollo.net.message.Message +import org.apollo.plugin.entity.pathing.walkBehind +import org.apollo.plugin.entity.pathing.walkTo + +class FollowAction(player: Player, private val target: Player) : Action(0, true, player) { + var lastPosition: Position? = null + + companion object { + fun start(player: Player, target: Player, message: Message? = null) { + player.startAction(FollowAction(player, target)) + message?.terminate() + } + } + + override fun execute() { + if (!target.isActive) { + stop() + return + } + + mob.interactingMob = target + + if (target.position == lastPosition) { + return + } + + val distance = mob.position.getDistance(target.position) + if (distance >= 15) { + stop() + return + } + + if (mob.position == target.position) { + val directions = Direction.NESW + val directionOffset = (Math.random() * directions.size).toInt() + + mob.walkTo(target.position.step(1, directions[directionOffset])) + } else { + mob.walkBehind(target) + lastPosition = target.position + } + } +} \ No newline at end of file diff --git a/game/plugin/entity/following/src/org/apollo/plugin/entity/following/Following.plugin.kts b/game/plugin/entity/following/src/org/apollo/plugin/entity/following/Following.plugin.kts new file mode 100644 index 000000000..98916343f --- /dev/null +++ b/game/plugin/entity/following/src/org/apollo/plugin/entity/following/Following.plugin.kts @@ -0,0 +1,11 @@ +package org.apollo.plugin.entity.following + +import org.apollo.game.plugin.entity.actions.PlayerActionEvent +import org.apollo.game.plugin.entity.actions.PlayerActionType + +on_player_event { PlayerActionEvent::class } + .where { action == PlayerActionType.FOLLOW } + .then { player -> + FollowAction.start(player, target) + terminate() + } \ No newline at end of file diff --git a/game/plugin/entity/pathing/build.gradle b/game/plugin/entity/pathing/build.gradle new file mode 100644 index 000000000..3a9351392 --- /dev/null +++ b/game/plugin/entity/pathing/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/entity/pathing/src/org/apollo/plugin/entity/pathing/Pathing.kt b/game/plugin/entity/pathing/src/org/apollo/plugin/entity/pathing/Pathing.kt new file mode 100644 index 000000000..963bb48ad --- /dev/null +++ b/game/plugin/entity/pathing/src/org/apollo/plugin/entity/pathing/Pathing.kt @@ -0,0 +1,66 @@ +package org.apollo.plugin.entity.pathing + +import org.apollo.game.model.Direction +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Entity +import org.apollo.game.model.entity.Mob +import org.apollo.game.model.entity.Npc +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.obj.GameObject +import org.apollo.game.model.entity.path.SimplePathfindingAlgorithm + +/** + * Adds a path from this [Mob] to the [target] [Entity], with the final position determined by the [facing] [Direction]. + */ +fun Mob.walkTo(target: Entity, facing: Direction? = null) { + val (width, length) = bounds(this) + val (targetWidth, targetLength) = bounds(target) + + val direction = facing ?: Direction.between(position, target.position) + val dx = direction.deltaX() + val dy = direction.deltaY() + + val targetX = if (dx <= 0) target.position.x else target.position.x + targetWidth - 1 + val targetY = if (dy <= 0) target.position.y else target.position.y + targetLength - 1 + val offsetX = if (dx < 0) -width else if (dx > 0) 1 else 0 + val offsetY = if (dy < 0) -length else if (dy > 0) 1 else 0 + + walkTo(Position(targetX + offsetX, targetY + offsetY, position.height)) +} + +/** + * Adds a path for this [Mob] to the target [Mob]s last position. + */ +fun Mob.walkBehind(target: Mob) { + walkTo(target, target.lastDirection.opposite()) +} + +/** + * Adds a path from this [Mob] to the [target] [Position], ending the path as soon as [positionPredicate] returns + * `false` (if provided). + */ +fun Mob.walkTo(target: Position, positionPredicate: ((Position) -> Boolean)? = null) { + if (position == target) { + return + } + + val pathfinder = SimplePathfindingAlgorithm(world.collisionManager) + val path = pathfinder.find(position, target) + + if (positionPredicate == null) { + path.forEach(walkingQueue::addStep) + } else { + for (step in path) { + if (!positionPredicate(step)) { + return + } + + walkingQueue.addStep(step) + } + } +} + +/** + * Returns the bounding size of the specified [Entity], in [x-size, y-size] format. + */ +private fun bounds(target: Entity): Pair = Pair(target.width, target.length) \ No newline at end of file diff --git a/game/plugin/entity/spawn/build.gradle b/game/plugin/entity/spawn/build.gradle new file mode 100644 index 000000000..4d96c9f57 --- /dev/null +++ b/game/plugin/entity/spawn/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:api') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/entity/spawn/src/org/apollo/game/plugin/entity/spawn/Spawn.kt b/game/plugin/entity/spawn/src/org/apollo/game/plugin/entity/spawn/Spawn.kt new file mode 100644 index 000000000..3e7732691 --- /dev/null +++ b/game/plugin/entity/spawn/src/org/apollo/game/plugin/entity/spawn/Spawn.kt @@ -0,0 +1,27 @@ +package org.apollo.game.plugin.entity.spawn + +import org.apollo.game.model.Animation +import org.apollo.game.model.Direction +import org.apollo.game.model.Graphic +import org.apollo.game.model.Position + +fun spawnNpc(name: String, x: Int, y: Int, z: Int = 0, id: Int? = null, facing: Direction = Direction.NORTH) { + Spawns.list += Spawn(id, name, Position(x, y, z), facing) +} + +fun spawnNpc(name: String, position: Position, id: Int? = null, facing: Direction = Direction.NORTH) { + Spawns.list += Spawn(id, name, position, facing) +} + +internal data class Spawn( + val id: Int?, + val name: String, + val position: Position, + val facing: Direction, + val spawnAnimation: Animation? = null, + val spawnGraphic: Graphic? = null +) + +internal object Spawns { + val list = mutableListOf() +} diff --git a/game/plugin/entity/spawn/src/org/apollo/game/plugin/entity/spawn/Spawn.plugin.kts b/game/plugin/entity/spawn/src/org/apollo/game/plugin/entity/spawn/Spawn.plugin.kts new file mode 100644 index 000000000..7d9fb4ce8 --- /dev/null +++ b/game/plugin/entity/spawn/src/org/apollo/game/plugin/entity/spawn/Spawn.plugin.kts @@ -0,0 +1,20 @@ +package org.apollo.game.plugin.entity.spawn + +import org.apollo.game.model.entity.Npc +import org.apollo.game.plugin.api.Definitions + +start { world -> + for ((id, name, position, facing, animation, graphic) in Spawns.list) { + val definition = requireNotNull(id?.let(Definitions::npc) ?: Definitions.npc(name)) { + "Could not find an Npc named $name to spawn." + } + + val npc = Npc(world, definition.id, position).apply { + turnTo(position.step(1, facing)) + animation?.let(::playAnimation) + graphic?.let(::playGraphic) + } + + world.register(npc) + } +} diff --git a/game/plugin/entity/spawn/test/org/apollo/game/plugin/entity/spawn/SpawnTests.kt b/game/plugin/entity/spawn/test/org/apollo/game/plugin/entity/spawn/SpawnTests.kt new file mode 100644 index 000000000..7290a7892 --- /dev/null +++ b/game/plugin/entity/spawn/test/org/apollo/game/plugin/entity/spawn/SpawnTests.kt @@ -0,0 +1,142 @@ +package org.apollo.game.plugin.entity.spawn + +import org.apollo.cache.def.NpcDefinition +import org.apollo.game.model.* +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.NpcDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource + +@ExtendWith(ApolloTestingExtension::class) +class SpawnTests { + + @TestMock + lateinit var world: World + + @BeforeEach + fun addNewNpcs() { + var previousSize: Int + + do { + previousSize = world.npcRepository.size() + world.pulse() + } while (previousSize != world.npcRepository.size()) + } + + @MethodSource("npcs spawned by id") + @ParameterizedTest(name = "spawning {1} at {2} (id = {0})") + fun `spawned npcs are in the correct position`(id: Int, name: String, spawn: Position) { + val npc = world.npcRepository.find { it.id == id } + + assertEquals(spawn, npc?.position) { "Failed to find npc $name with id $id." } + } + + @MethodSource("npcs spawned by id with directions") + @ParameterizedTest(name = "spawning {1} with direction {3} (id = {0})") + fun `spawned npcs are facing the correct direction`(id: Int, name: String, spawn: Position, direction: Direction) { + val npc = requireNotNull(world.npcRepository.find { it.id == id }) { "Failed to find npc $name with id $id." } + val facing = spawn.step(1, direction) + + assertEquals(facing, npc.facingPosition) + } + + @Disabled("Currently no way to test if the animation was played") + @MethodSource("npcs spawned by id with animations") + @ParameterizedTest(name = "spawning {1} with animation {3} (id = {0})") + fun `spawned npcs are playing the correct animation`(id: Int, name: String, spawn: Position, animation: Animation) { + val npc = requireNotNull(world.npcRepository.find { it.id == id }) { "Failed to find npc $name with id $id." } + + TODO("How to verify that npc.playAnimation was called with $animation.") + } + + @Disabled("Currently no way to test if the graphic was played") + @MethodSource("npcs spawned by id with graphics") + @ParameterizedTest(name = "spawning {1} with graphic {3} (id = {0})") + fun `spawned npcs are playing the correct graphic`(id: Int, name: String, spawn: Position, graphic: Graphic) { + val npc = requireNotNull(world.npcRepository.find { it.id == id }) { "Failed to find npc $name with id $id." } + + TODO("How to verify that npc.playGraphic was called with $graphic.") + } + + @MethodSource("npcs spawned by name") + @ParameterizedTest(name = "spawning {0}") + fun `spawns are looked up by name if the id is unspecified`(name: String) { + val npc = world.npcRepository.find { it.definition.name === name }!! + val expectedId = name.substringAfterLast("_").toInt() + + assertEquals(expectedId, npc.id) + } + + companion object { + + // This test class has multiple (hidden) order dependencies because of the nature of the spawn + // plugin, where npcs are inserted into the world immediately after world initialisation. + // + // All npcs that should be spawned by the test must be passed to `spawnNpc` _before_ the test world + // is created by the ApolloTestingExtension - which means they must be done inside the initialisation + // block of this companion object. + // + // When npcs are created, however, they look up their NpcDefinition - so all of the definitions must + // be created (via `@NpcDefinitions`) before the initialisation block is executed. + // + // The world must also be pulsed after the spawn plugin executes, so that npcs are registered (i.e. moved + // out of the queue). + + @JvmStatic + fun `npcs spawned by id`(): List { + return npcs.filterNot { it.id == null } + .map { (id, name, position) -> Arguments.of(id, name, position) } + } + + @JvmStatic + fun `npcs spawned by id with directions`(): List { + return npcs.filterNot { it.id == null } + .map { (id, name, position, direction) -> Arguments.of(id, name, position, direction) } + } + + @JvmStatic + fun `npcs spawned by id with animations`(): List { + return npcs.filterNot { it.id == null } + .map { (id, name, position, _, animation) -> Arguments.of(id, name, position, animation) } + } + + @JvmStatic + fun `npcs spawned by id with graphics`(): List { + return npcs.filterNot { it.id == null } + .map { (id, name, position, _, _, graphic) -> Arguments.of(id, name, position, graphic) } + } + + @JvmStatic + fun `npcs spawned by name`(): List { + return npcs.filter { it.id == null } + .map { (_, name) -> Arguments.of(name) } + } + + private val npcs = listOf( + Spawn(0, "hans", Position(1000, 1000, 0), facing = Direction.NORTH, spawnAnimation = Animation(10, 100)), + Spawn(1, "man", Position(1000, 1000, 0), facing = Direction.NORTH, spawnGraphic = Graphic(154, 0, 100)), + Spawn(null, "man_2", Position(1000, 1000, 2), facing = Direction.NORTH), + Spawn(null, "man_3", Position(1000, 1000, 3), facing = Direction.SOUTH), + Spawn(6, "fakename123", Position(1500, 1500, 3), facing = Direction.EAST, spawnAnimation = Animation(112)), + Spawn(12, "fakename123", Position(1500, 1500, 3), facing = Direction.WEST, spawnGraphic = Graphic(964)) + ) + + @NpcDefinitions + val definitions = npcs.map { (id, name) -> + val definitionId = id ?: name.substringAfterLast("_").toInt() + NpcDefinition(definitionId).also { it.name = name } + } + + init { // Must come after NpcDef initialisation, before mocked World initialisation + for ((id, name, position, direction) in npcs) { + spawnNpc(name, position.x, position.y, position.height, id, direction) + } + } + } +} diff --git a/game/plugin/locations/al-kharid/build.gradle b/game/plugin/locations/al-kharid/build.gradle new file mode 100644 index 000000000..7eed7d878 --- /dev/null +++ b/game/plugin/locations/al-kharid/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'kotlin' + + + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:entity:spawn') + implementation project(':game:plugin:shops') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/locations/al-kharid/src/npcs.plugin.kts b/game/plugin/locations/al-kharid/src/npcs.plugin.kts new file mode 100644 index 000000000..79b74471c --- /dev/null +++ b/game/plugin/locations/al-kharid/src/npcs.plugin.kts @@ -0,0 +1,114 @@ +package org.apollo.plugin.locations.alKharid + +import org.apollo.game.model.Direction +import org.apollo.game.plugin.entity.spawn.spawnNpc + +// Generic npcs + +spawnNpc("man", x = 3276, y = 3186) +spawnNpc("man", x = 3282, y = 3197) + +spawnNpc("man", id = 3, x = 3301, y = 3200) +spawnNpc("man", id = 3, x = 3300, y = 3208) + +spawnNpc("man", id = 2, x = 3297, y = 3196) + +spawnNpc("man", id = 16, x = 3294, y = 3204) + +spawnNpc("spider", x = 3319, y = 3145) +spawnNpc("spider", x = 3319, y = 3140) +spawnNpc("spider", x = 3323, y = 3138) + +spawnNpc("scorpion", x = 3282, y = 3149) + +// Camels + +spawnNpc("cam_the_camel", x = 3295, y = 3232) +spawnNpc("elly_the_camel", x = 3312, y = 3210) +spawnNpc("camel", x = 3285, y = 3198) +spawnNpc("ollie_the_camel", x = 3291, y = 3209) +spawnNpc("al_the_camel", x = 3275, y = 3162) + +// Quest npc + +spawnNpc("osman", x = 3286, y = 3180, facing = Direction.EAST) + +spawnNpc("hassan", x = 3302, y = 3163) + +spawnNpc("father_reen", x = 3272, y = 3158) + +spawnNpc("man", id = 663, x = 3297, y = 3287) + +// Boarder guards + +spawnNpc("border_guard", x = 3268, y = 3226) +spawnNpc("border_guard", x = 3267, y = 3226) +spawnNpc("border_guard", x = 3268, y = 3229, facing = Direction.SOUTH) +spawnNpc("border_guard", x = 3267, y = 3229, facing = Direction.SOUTH) + +// Palace guards + +spawnNpc("Al-Kharid warrior", x = 3285, y = 3174) +spawnNpc("Al-Kharid warrior", x = 3283, y = 3168) +spawnNpc("Al-Kharid warrior", x = 3285, y = 3169) +spawnNpc("Al-Kharid warrior", x = 3290, y = 3162) +spawnNpc("Al-Kharid warrior", x = 3295, y = 3170) +spawnNpc("Al-Kharid warrior", x = 3300, y = 3175) +spawnNpc("Al-Kharid warrior", x = 3300, y = 3171) +spawnNpc("Al-Kharid warrior", x = 3301, y = 3168) + +// Shanty pass + +spawnNpc("shantay_guard", x = 3301, y = 3120) +spawnNpc("shantay_guard", x = 3302, y = 3126) +spawnNpc("shantay_guard", x = 3306, y = 3126) +spawnNpc("shantay_guard", id = 838, x = 3303, y = 3118) + +// Mine + +spawnNpc("scorpion", x = 3296, y = 3294) +spawnNpc("scorpion", x = 3298, y = 3280) +spawnNpc("scorpion", x = 3299, y = 3299) +spawnNpc("scorpion", x = 3299, y = 3309) +spawnNpc("scorpion", x = 3300, y = 3287) +spawnNpc("scorpion", x = 3300, y = 3315) +spawnNpc("scorpion", x = 3301, y = 3305) +spawnNpc("scorpion", x = 3301, y = 3312) + +// Functional npcs + +spawnNpc("gnome_pilot", x = 3279, y = 3213) + +spawnNpc("banker", id = 496, x = 3267, y = 3164, facing = Direction.EAST) +spawnNpc("banker", id = 496, x = 3267, y = 3167, facing = Direction.EAST) +spawnNpc("banker", id = 496, x = 3267, y = 3169, facing = Direction.EAST) + +spawnNpc("banker", id = 497, x = 3267, y = 3166, facing = Direction.EAST) +spawnNpc("banker", id = 497, x = 3267, y = 3168, facing = Direction.EAST) + +spawnNpc("gem_trader", x = 3287, y = 3210) + +spawnNpc("zeke", x = 3289, y = 3189) + +spawnNpc("shantay", x = 3304, y = 3124) + +spawnNpc("rug_merchant", id = 2296, x = 3311, y = 3109, facing = Direction.WEST) + +spawnNpc("ranael", x = 3315, y = 3163) + +spawnNpc("shop_keeper", id = 524, x = 3315, y = 3178) +spawnNpc("shop_assistant", id = 525, x = 3315, y = 3180, facing = Direction.WEST) + +spawnNpc("louie_legs", x = 3316, y = 3175, facing = Direction.WEST) + +spawnNpc("ellis", x = 3274, y = 3192) + +spawnNpc("dommik", x = 3321, y = 3193) + +spawnNpc("tool_leprechaun", x = 3319, y = 3204) + +spawnNpc("ali_morrisane", x = 3304, y = 3211, facing = Direction.EAST) + +spawnNpc("silk_trader", x = 3300, y = 3203) + +spawnNpc("karim", x = 3273, y = 3180) \ No newline at end of file diff --git a/game/plugin/locations/al-kharid/src/shops.plugin.kts b/game/plugin/locations/al-kharid/src/shops.plugin.kts new file mode 100644 index 000000000..2d51292bc --- /dev/null +++ b/game/plugin/locations/al-kharid/src/shops.plugin.kts @@ -0,0 +1,146 @@ +package org.apollo.plugin.locations.alKharid + +import org.apollo.game.plugin.shops.builder.shop + +shop("Al-Kharid General Store") { + operated by "Shop keeper"(524) and "Shop assistant"(525) + buys any items + + sell(5) of "Pot" + sell(2) of "Jug" + sell(2) of "Shears" + sell(3) of "Bucket" + sell(2) of "Bowl" + sell(2) of "Cake tin" + sell(2) of "Tinderbox" + sell(2) of "Chisel" + sell(5) of "Hammer" + sell(5) of "Newcomer map" +} + +/** + * TODO add a way to "unlock" items, as more of Ali Morrisane's items are unlocked by completing certain parts of + * the Rogue Trader minigame progressively. + * + * TODO this shop can be accessed only through dialogue, so support for that should be added. + */ +/*shop("Ali's Discount Wares") { + operated by "Ali Morrisane" + + sell(3) of "Pot" + sell(2) of "Jug" + sell(10) of { "Waterskin"(1825) } + sell(3) of "Desert shirt" + sell(2) of "Desert boots" + sell(19) of "Bucket" + sell(11) of "Fake beard" + sell(12) of "Karidian headpiece" + sell(50) of "Papyrus" + sell(5) of "Knife" + sell(11) of "Tinderbox" + sell(23) of "Bronze pickaxe" + sell(15) of "Raw chicken" +}*/ + +shop("Dommik's Crafting Store") { + operated by "Dommik" + + sell(2) of "Chisel" + category("mould") { + sell(10) of "Ring" + sell(2) of "Necklace" + sell(10) of "Amulet" + } + sell(3) of "Needle" + sell(100) of "Thread" + category("mould") { + sell(3) of "Holy" + sell(10) of "Sickle" + sell(10) of "Tiara" + } +} + +shop("Gem Trader") { + operated by "Gem trader" + + category("uncut", affix = prefix) { + sell(1) of { + -"Sapphire" + -"Emerald" + } + sell(0) of { + -"Ruby" + -"Diamond" + } + } + + sell(1) of { + -"Sapphire" + -"Emerald" + } + sell(0) of { + -"Ruby" + -"Diamond" + } +} + +shop("Louie's Armoured Legs Bazaar") { + operated by "Louie Legs" + + category("platelegs", depluralise = false) { + sell(5) of "Bronze" + sell(3) of "Iron" + sell(2) of "Steel" + sell(1) of "Black" + sell(1) of "Mithril" + sell(1) of "Adamant" + } +} + +shop("Ranael's Super Skirt Store") { + operated by "Ranael" + + category("plateskirt") { + sell(5) of "Bronze" + sell(3) of "Iron" + sell(2) of "Steel" + sell(1) of "Black" + sell(1) of "Mithril" + sell(1) of "Adamant" + } +} + +shop("Shantay Pass Shop") { + operated by "Shantay" + + sell(100) of { "Waterskin"(1823) } + sell(100) of { "Waterskin"(1831) } + sell(10) of "Jug of water" + sell(10) of "Bowl of water" + sell(10) of "Bucket of water" + sell(10) of "Knife" + category("desert", affix = prefix) { + sell(10) of "shirt" + sell(10) of "robe" + sell(10) of "boots" + } + sell(10) of "Bronze bar" + sell(500) of "Feather" + sell(10) of "Hammer" + sell(0) of "Bucket" + sell(0) of "Bowl" + sell(0) of "Jug" + sell(500) of "Shantay pass" + sell(20) of "Rope" +} + +shop("Zeke's Superior Scimitars") { + operated by "Zeke" + + category("scimitar") { + sell(5) of "Bronze" + sell(3) of "Iron" + sell(2) of "Steel" + sell(1) of "Mithril" + } +} \ No newline at end of file diff --git a/game/plugin/locations/edgeville/build.gradle b/game/plugin/locations/edgeville/build.gradle new file mode 100644 index 000000000..7eed7d878 --- /dev/null +++ b/game/plugin/locations/edgeville/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'kotlin' + + + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:entity:spawn') + implementation project(':game:plugin:shops') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/locations/edgeville/src/npcs.plugin.kts b/game/plugin/locations/edgeville/src/npcs.plugin.kts new file mode 100644 index 000000000..d89e84926 --- /dev/null +++ b/game/plugin/locations/edgeville/src/npcs.plugin.kts @@ -0,0 +1,53 @@ +package org.apollo.plugin.locations.edgeville + +import org.apollo.game.model.Direction +import org.apollo.game.plugin.entity.spawn.spawnNpc + +// Generic npcs + +spawnNpc("man", x = 3095, y = 3508) +spawnNpc("man", x = 3095, y = 3511) +spawnNpc("man", x = 3098, y = 3509) +spawnNpc("man", id = 2, x = 3093, y = 3511) +spawnNpc("man", id = 3, x = 3097, y = 3508) +spawnNpc("man", id = 3, x = 3092, y = 3508) +spawnNpc("man", id = 3, x = 3097, y = 3512) + +spawnNpc("guard", x = 3086, y = 3516) +spawnNpc("guard", x = 3094, y = 3518) +spawnNpc("guard", x = 3108, y = 3514) +spawnNpc("guard", x = 3110, y = 3514) +spawnNpc("guard", x = 3113, y = 3514) +spawnNpc("guard", x = 3113, y = 3516) + +spawnNpc("sheep", id = 43, x = 3050, y = 3516) +spawnNpc("sheep", id = 43, x = 3051, y = 3514) +spawnNpc("sheep", id = 43, x = 3056, y = 3517) +spawnNpc("ram", id = 3673, x = 3048, y = 3515) + +spawnNpc("monk", x = 3044, y = 3491) +spawnNpc("monk", x = 3045, y = 3483) +spawnNpc("monk", x = 3045, y = 3497) +spawnNpc("monk", x = 3050, y = 3490) +spawnNpc("monk", x = 3054, y = 3490) +spawnNpc("monk", x = 3058, y = 3497) + +// Functional npcs + +spawnNpc("richard", x = 3098, y = 3516) +spawnNpc("doris", x = 3079, y = 3491) +spawnNpc("brother_jered", x = 3045, y = 3488) +spawnNpc("brother_althric", x = 3054, y = 3504) + +spawnNpc("abbot_langley", x = 3059, y = 3484) +spawnNpc("oziach", x = 3067, y = 3518, facing = Direction.EAST) + +spawnNpc("shop_keeper", id = 528, x = 3079, y = 3509) +spawnNpc("shop_assistant", id = 529, x = 3082, y = 3513) + +spawnNpc("banker", x = 3096, y = 3489, facing = Direction.WEST) +spawnNpc("banker", x = 3096, y = 3491, facing = Direction.WEST) +spawnNpc("banker", x = 3096, y = 3492) +spawnNpc("banker", x = 3098, y = 3492) + +spawnNpc("mage_of_zamorak", x = 3106, y = 3560) \ No newline at end of file diff --git a/game/plugin/locations/edgeville/src/shops.plugin.kts b/game/plugin/locations/edgeville/src/shops.plugin.kts new file mode 100644 index 000000000..0a5facec5 --- /dev/null +++ b/game/plugin/locations/edgeville/src/shops.plugin.kts @@ -0,0 +1,31 @@ +package org.apollo.plugin.locations.edgeville + +import org.apollo.game.plugin.shops.builder.shop + +shop("Edgeville General Store") { + operated by "Shop keeper"(528) and "Shop assistant"(529) + buys any items + + sell(5) of "Pot" + sell(2) of "Jug" + sell(2) of "Shears" + sell(3) of "Bucket" + sell(2) of "Bowl" + sell(2) of "Cake tin" + sell(2) of "Tinderbox" + sell(2) of "Chisel" + sell(5) of "Hammer" + sell(5) of "Newcomer map" +} + +/** + * TODO make a way to have requirements to open shops. Players have to have finished Dragon Slayer to access + * "Oziach's Armour" + */ +shop("Oziach's Armour") { + operated by "Oziach" + + sell(2) of "Rune platebody" + sell(2) of "Green d'hide body" + sell(35) of "Anti-dragon shield" +} \ No newline at end of file diff --git a/game/plugin/locations/falador/build.gradle b/game/plugin/locations/falador/build.gradle new file mode 100644 index 000000000..7eed7d878 --- /dev/null +++ b/game/plugin/locations/falador/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'kotlin' + + + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:entity:spawn') + implementation project(':game:plugin:shops') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/locations/falador/src/npcs.plugin.kts b/game/plugin/locations/falador/src/npcs.plugin.kts new file mode 100644 index 000000000..9136f9c09 --- /dev/null +++ b/game/plugin/locations/falador/src/npcs.plugin.kts @@ -0,0 +1,171 @@ +package org.apollo.plugin.locations.falador + +import org.apollo.game.plugin.entity.spawn.spawnNpc + +// Generic npcs + +spawnNpc("chicken", x = 2965, y = 3345) + +spawnNpc("duck", x = 2988, y = 3383) +spawnNpc("duck", x = 2992, y = 3383) +spawnNpc("duck", x = 2993, y = 3385) + +spawnNpc("drunken_man", x = 2957, y = 3368, z = 1) + +spawnNpc("dwarf", id = 118, x = 3023, y = 3334) +spawnNpc("dwarf", id = 118, x = 3027, y = 3341) +spawnNpc("dwarf", id = 118, x = 3012, y = 3341) +spawnNpc("dwarf", id = 118, x = 3017, y = 3346, z = 1) +spawnNpc("dwarf", id = 118, x = 3011, y = 3341, z = 1) + +spawnNpc("dwarf", id = 121, x = 3027, y = 3341) + +spawnNpc("dwarf", id = 382, x = 3017, y = 3340) + +spawnNpc("dwarf", id = 3294, x = 3022, y = 3338) +spawnNpc("dwarf", id = 3295, x = 3021, y = 3341) + +spawnNpc("gardener", x = 2998, y = 3385) +spawnNpc("gardener", x = 3019, y = 3369) +spawnNpc("gardener", id = 3234, x = 3016, y = 3386) + +spawnNpc("guard", x = 2965, y = 3394) +spawnNpc("guard", x = 2964, y = 3396) +spawnNpc("guard", x = 2966, y = 3397) +spawnNpc("guard", x = 2964, y = 3384) +spawnNpc("guard", x = 2963, y = 3380) +spawnNpc("guard", x = 3006, y = 3325) +spawnNpc("guard", x = 3008, y = 3320) +spawnNpc("guard", x = 3006, y = 3322) +spawnNpc("guard", x = 3038, y = 3356) + +spawnNpc("guard", id = 10, x = 2942, y = 3375) +spawnNpc("guard", id = 10, x = 3040, y = 3352) + +spawnNpc("guard", id = 3230, x = 2967, y = 3395) +spawnNpc("guard", id = 3230, x = 2966, y = 3392) +spawnNpc("guard", id = 3230, x = 2963, y = 3376) +spawnNpc("guard", id = 3230, x = 2954, y = 3382) +spawnNpc("guard", id = 3230, x = 2950, y = 3377) +spawnNpc("guard", id = 3230, x = 2968, y = 3381) + +spawnNpc("guard", id = 3231, x = 3033, y = 3389, z = 1) +spawnNpc("guard", id = 3231, x = 3041, y = 3388, z = 1) +spawnNpc("guard", id = 3231, x = 3048, y = 3389, z = 1) +spawnNpc("guard", id = 3231, x = 3056, y = 3389, z = 1) +spawnNpc("guard", id = 3231, x = 3062, y = 3386, z = 1) +spawnNpc("guard", id = 3231, x = 3058, y = 3329, z = 1) +spawnNpc("guard", id = 3231, x = 3050, y = 3329, z = 1) +spawnNpc("guard", id = 3231, x = 3038, y = 3329, z = 1) +spawnNpc("guard", id = 3231, x = 3029, y = 3329, z = 1) + +spawnNpc("swan", x = 2960, y = 3359) +spawnNpc("swan", x = 2963, y = 3360) +spawnNpc("swan", x = 2968, y = 3359) +spawnNpc("swan", x = 2971, y = 3360) +spawnNpc("swan", x = 2976, y = 3358) +spawnNpc("swan", x = 2989, y = 3384) + +spawnNpc("man", id = 3223, x = 2991, y = 3365) +spawnNpc("man", id = 3225, x = 3037, y = 3345, z = 1) + +spawnNpc("white_knight", x = 2983, y = 3343) +spawnNpc("white_knight", x = 2981, y = 3334) +spawnNpc("white_knight", x = 2988, y = 3335) +spawnNpc("white_knight", x = 2996, y = 3342) +spawnNpc("white_knight", x = 2960, y = 3340) +spawnNpc("white_knight", x = 2962, y = 3336) +spawnNpc("white_knight", x = 2974, y = 3342) +spawnNpc("white_knight", x = 2972, y = 3345) +spawnNpc("white_knight", x = 2977, y = 3348) + +spawnNpc("white_knight", id = 3348, x = 2971, y = 3340) +spawnNpc("white_knight", id = 3348, x = 2978, y = 3350) + +spawnNpc("white_knight", x = 2964, y = 3330, z = 1) +spawnNpc("white_knight", x = 2968, y = 3334, z = 1) +spawnNpc("white_knight", x = 2969, y = 3339, z = 1) +spawnNpc("white_knight", x = 2978, y = 3332, z = 1) +spawnNpc("white_knight", x = 2958, y = 3340, z = 1) +spawnNpc("white_knight", x = 2960, y = 3343, z = 1) + +spawnNpc("white_knight", id = 3348, x = 2987, y = 3334, z = 1) +spawnNpc("white_knight", id = 3348, x = 2983, y = 3336, z = 1) +spawnNpc("white_knight", id = 3348, x = 2987, y = 3334, z = 1) +spawnNpc("white_knight", id = 3348, x = 2979, y = 3348, z = 1) +spawnNpc("white_knight", id = 3348, x = 2964, y = 3337, z = 1) + +spawnNpc("white_knight", id = 3349, x = 2989, y = 3344, z = 1) + +spawnNpc("white_knight", x = 2985, y = 3342, z = 2) + +spawnNpc("white_knight", id = 3348, x = 2979, y = 3348, z = 2) +spawnNpc("white_knight", id = 3348, x = 2974, y = 3329, z = 2) +spawnNpc("white_knight", id = 3348, x = 2982, y = 3341, z = 2) + +spawnNpc("white_knight", id = 3349, x = 2990, y = 3341, z = 2) +spawnNpc("white_knight", id = 3349, x = 2971, y = 3330, z = 2) +spawnNpc("white_knight", id = 3349, x = 2965, y = 3350, z = 2) +spawnNpc("white_knight", id = 3349, x = 2965, y = 3329, z = 2) + +spawnNpc("white_knight", id = 3350, x = 2961, y = 3347, z = 2) + +spawnNpc("white_knight", id = 3349, x = 2962, y = 3339, z = 3) + +spawnNpc("white_knight", id = 3350, x = 2960, y = 3336, z = 3) +spawnNpc("white_knight", id = 3350, x = 2984, y = 3349, z = 3) + +spawnNpc("woman", id = 3226, x = 2991, y = 3384) + +// Functional npcs + +spawnNpc("apprentice_workman", id = 3235, x = 2971, y = 3369, z = 1) + +spawnNpc("banker", id = 495, x = 2945, y = 3366) +spawnNpc("banker", id = 495, x = 2946, y = 3366) +spawnNpc("banker", id = 495, x = 2947, y = 3366) +spawnNpc("banker", id = 495, x = 2948, y = 3366) + +spawnNpc("banker", x = 2949, y = 3366) + +spawnNpc("banker", x = 3015, y = 3353) +spawnNpc("banker", x = 3014, y = 3353) +spawnNpc("banker", x = 3013, y = 3353) +spawnNpc("banker", x = 3012, y = 3353) +spawnNpc("banker", x = 3011, y = 3353) +spawnNpc("banker", x = 3010, y = 3353) + +spawnNpc("cassie", x = 2976, y = 3383) + +spawnNpc("emily", x = 2954, y = 3372) + +spawnNpc("flynn", x = 2950, y = 3387) + +spawnNpc("hairdresser", x = 2944, y = 3380) + +spawnNpc("herquin", x = 2945, y = 3335) + +spawnNpc("heskel", x = 3007, y = 3374) + +spawnNpc("kaylee", x = 2957, y = 3372) + +spawnNpc("tina", x = 2955, y = 3371, z = 1) + +spawnNpc("tool_leprechaun", x = 3005, y = 3370) + +spawnNpc("squire", x = 2977, y = 3343) + +spawnNpc("sir_tiffy_cashien", x = 2997, y = 3373) + +spawnNpc("sir_amik_varze", x = 2960, y = 3336, z = 2) + +spawnNpc("sir_vyvin", x = 2983, y = 3335, z = 2) + +spawnNpc("shop_keeper", id = 524, x = 2955, y = 3389) +spawnNpc("shop_assistant", id = 525, x = 2957, y = 3387) + +spawnNpc("wayne", x = 2972, y = 3312) + +spawnNpc("workman", id = 3236, x = 2975, y = 3369, z = 1) + +spawnNpc("wyson_the_gardener", x = 3028, y = 3381) \ No newline at end of file diff --git a/game/plugin/locations/falador/src/shops.plugin.kts b/game/plugin/locations/falador/src/shops.plugin.kts new file mode 100644 index 000000000..5b7446fa9 --- /dev/null +++ b/game/plugin/locations/falador/src/shops.plugin.kts @@ -0,0 +1,76 @@ +package org.apollo.plugin.locations.falador + +import org.apollo.game.plugin.shops.builder.shop + +shop("Falador General Store") { + operated by "Shop keeper"(524) and "Shop assistant"( 525) + buys any items + + sell(5) of "Pot" + sell(2) of "Jug" + sell(2) of "Shears" + sell(3) of "Bucket" + sell(2) of "Bowl" + sell(2) of "Cake tin" + sell(2) of "Tinderbox" + sell(2) of "Chisel" + sell(5) of "Hammer" + sell(5) of "Newcomer map" +} + +shop("Cassie's Shield Shop") { + operated by "Cassie" + + sell(5) of "Wooden shield" + sell(3) of "Bronze sq shield" + sell(3) of "Bronze kiteshield" + sell(2) of "Iron sq shield" + sell(0) of "Iron kiteshield" + sell(0) of "Steel sq shield" + sell(0) of "Steel kiteshield" + sell(0) of "Mithril sq shield" +} + +shop("Flynn's Mace Market") { + operated by "Flynn" + + category("mace") { + sell(5) of "Bronze" + sell(4) of "Iron" + sell(3) of "Mithril" + sell(2) of "Adamant" + } +} + +shop("Herquin's Gems") { + operated by "Herquin" + + category("uncut", affix = prefix) { + sell(1) of "Sapphire" + sell(0) of { + -"Emerald" + -"Ruby" + -"Diamond" + } + } + + sell(1) of "Sapphire" + sell(0) of { + -"Emerald" + -"Ruby" + -"Diamond" + } +} + +shop("Wayne's Chains - Chainmail Specialist") { + operated by "Wayne" + + category("chainbody") { + sell(3) of "Bronze" + sell(2) of "Iron" + sell(1) of "Steel" + sell(1) of "Black" + sell(1) of "Mithril" + sell(1) of "Adamant" + } +} \ No newline at end of file diff --git a/game/plugin/locations/lumbridge/build.gradle b/game/plugin/locations/lumbridge/build.gradle new file mode 100644 index 000000000..7eed7d878 --- /dev/null +++ b/game/plugin/locations/lumbridge/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'kotlin' + + + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:entity:spawn') + implementation project(':game:plugin:shops') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/locations/lumbridge/src/npcs.plugin.kts b/game/plugin/locations/lumbridge/src/npcs.plugin.kts new file mode 100644 index 000000000..03094618d --- /dev/null +++ b/game/plugin/locations/lumbridge/src/npcs.plugin.kts @@ -0,0 +1,15 @@ +package org.apollo.plugin.locations.lumbridge + +import org.apollo.game.plugin.entity.spawn.spawnNpc + +spawnNpc("woman", id = 4, x = 3232, y = 3207) +spawnNpc("man", id = 1, x = 3231, y = 3237) +spawnNpc("man", id = 2, x = 3224, y = 3240) +spawnNpc("woman", id = 5, x = 3229, y = 2329) + +spawnNpc("hans", x = 3221, y = 3221) +spawnNpc("father aereck", x = 3243, y = 3210) +spawnNpc("bob", x = 3231, y = 3203) +spawnNpc("shop keeper", x = 3212, y = 3247) +spawnNpc("shop assistant", x = 3211, y = 3245) +spawnNpc("lumbridge guide", x = 323, y = 3229) diff --git a/game/plugin/locations/lumbridge/src/shops.plugin.kts b/game/plugin/locations/lumbridge/src/shops.plugin.kts new file mode 100644 index 000000000..942bb62d3 --- /dev/null +++ b/game/plugin/locations/lumbridge/src/shops.plugin.kts @@ -0,0 +1,42 @@ +package org.apollo.plugin.locations.lumbridge + +import org.apollo.game.plugin.shops.builder.shop + +shop("Lumbridge General Store") { + operated by "Shop keeper" and "Shop assistant" + buys any items + + sell(5) of "Pot" + sell(2) of "Jug" + sell(2) of "Shears" + sell(3) of "Bucket" + sell(2) of "Bowl" + sell(2) of "Cake tin" + sell(2) of "Tinderbox" + sell(2) of "Chisel" + sell(5) of "Hammer" + sell(5) of "Newcomer map" +} + +shop("Bob's Brilliant Axes") { + operated by "Bob" + + category("pickaxe") { + sell(5) of "Bronze" + } + + category("axe") { + sell(10) of "Bronze" + sell(5) of "Iron" + sell(3) of "Steel" + } + + category("battleaxe") { + sell(5) of "Iron" + sell(2) of "Steel" + sell(1) of "Mithril" + } +} + +// TODO find out how to make objects be able to open stores for the Culinaromancer's Chest. Also links to TODO in +// Al-Kharid's shops plugin for "unlockable" items. \ No newline at end of file diff --git a/game/plugin/locations/tutorial-island/build.gradle b/game/plugin/locations/tutorial-island/build.gradle new file mode 100644 index 000000000..24e7bbaed --- /dev/null +++ b/game/plugin/locations/tutorial-island/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:entity:spawn') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/locations/tutorial-island/src/npcs.plugin.kts b/game/plugin/locations/tutorial-island/src/npcs.plugin.kts new file mode 100644 index 000000000..5ec51c09b --- /dev/null +++ b/game/plugin/locations/tutorial-island/src/npcs.plugin.kts @@ -0,0 +1,44 @@ +package org.apollo.plugin.locations.tutorialIsland + +import org.apollo.game.model.Direction +import org.apollo.game.plugin.entity.spawn.spawnNpc + +// Functional npcs + +// 'Above-ground' npcs + +spawnNpc("master_chef", x = 3076, y = 3085) +spawnNpc("quest_guide", x = 3086, y = 3122) +spawnNpc("financial_advisor", x = 3127, y = 3124, facing = Direction.WEST) +spawnNpc("brother_brace", x = 3124, y = 3107, facing = Direction.EAST) +spawnNpc("magic_instructor", x = 3140, y = 3085) + +// 'Below-ground' npcs +// Note: They aren't actually on a different plane, they're just in a different location that +// pretends to be underground. + +spawnNpc("mining_instructor", x = 3081, y = 9504) +spawnNpc("combat_instructor", x = 3104, y = 9506) + +// Non-humanoid npcs + +spawnNpc("fishing_spot", id = 316, x = 3102, y = 3093) + +spawnNpc("chicken", x = 3140, y = 3095) +spawnNpc("chicken", x = 3140, y = 3093) +spawnNpc("chicken", x = 3138, y = 3092) +spawnNpc("chicken", x = 3137, y = 3094) +spawnNpc("chicken", x = 3138, y = 3095) + +// 'Below-ground' npcs +// Note: They aren't actually on a different plane, they're just in a different location that +// pretends to be underground. + +spawnNpc("giant_rat", id = 87, x = 3105, y = 9514) +spawnNpc("giant_rat", id = 87, x = 3105, y = 9517) +spawnNpc("giant_rat", id = 87, x = 3106, y = 9514) +spawnNpc("giant_rat", id = 87, x = 3104, y = 9514) +spawnNpc("giant_rat", id = 87, x = 3105, y = 9519) +spawnNpc("giant_rat", id = 87, x = 3109, y = 9516) +spawnNpc("giant_rat", id = 87, x = 3108, y = 9520) +spawnNpc("giant_rat", id = 87, x = 3102, y = 9517) \ No newline at end of file diff --git a/game/plugin/locations/varrock/build.gradle b/game/plugin/locations/varrock/build.gradle new file mode 100644 index 000000000..6a06d7334 --- /dev/null +++ b/game/plugin/locations/varrock/build.gradle @@ -0,0 +1,11 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:entity:spawn') + implementation project(':game:plugin:shops') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/locations/varrock/src/npcs.plugin.kts b/game/plugin/locations/varrock/src/npcs.plugin.kts new file mode 100644 index 000000000..83d5fb9ec --- /dev/null +++ b/game/plugin/locations/varrock/src/npcs.plugin.kts @@ -0,0 +1,268 @@ +package org.apollo.plugin.locations.varrock + +import org.apollo.game.model.Direction +import org.apollo.game.plugin.entity.spawn.spawnNpc + +spawnNpc("barbarian_woman", x = 3222, y = 3399) + +spawnNpc("bear", id = 106, x = 3289, y = 3351) + +spawnNpc("black_knight", x = 3238, y = 3514) +spawnNpc("black_knight", x = 3227, y = 3518) +spawnNpc("black_knight", x = 3279, y = 3502) + +spawnNpc("dark_wizard", id = 174, x = 3230, y = 3366) + +spawnNpc("dark_wizard", id = 174, x = 3228, y = 3368) +spawnNpc("dark_wizard", id = 174, x = 3225, y = 3367) +spawnNpc("dark_wizard", id = 174, x = 3226, y = 3365) +spawnNpc("dark_wizard", id = 174, x = 3226, y = 3372) +spawnNpc("dark_wizard", id = 174, x = 3231, y = 3371) + +spawnNpc("dark_wizard", id = 172, x = 3229, y = 3372) +spawnNpc("dark_wizard", id = 172, x = 3224, y = 3370) +spawnNpc("dark_wizard", id = 172, x = 3228, y = 3366) +spawnNpc("dark_wizard", id = 172, x = 3232, y = 3368) +spawnNpc("dark_wizard", id = 172, x = 3226, y = 3369) + +spawnNpc("giant_rat", id = 87, x = 3292, y = 3375) +spawnNpc("giant_rat", id = 87, x = 3265, y = 3384) +spawnNpc("giant_rat", id = 87, x = 3267, y = 3381) + +spawnNpc("guard", id = 368, x = 3263, y = 3407, facing = Direction.SOUTH) + +spawnNpc("jeremy_clerksin", x = 3253, y = 3477) +spawnNpc("martina_scorsby", x = 3256, y = 3481) + +spawnNpc("man", x = 3281, y = 3500) +spawnNpc("man", x = 3193, y = 3394) +spawnNpc("man", x = 3159, y = 3429) +spawnNpc("man", x = 3245, y = 3394) +spawnNpc("man", x = 3283, y = 3492, z = 1) + +spawnNpc("man", id = 2, x = 3283, y = 3492, z = 1) +spawnNpc("man", id = 2, x = 3263, y = 3400) + +spawnNpc("man", id = 3, x = 3227, y = 3395, z = 1) +spawnNpc("man", id = 3, x = 3231, y = 3399, z = 1) + +spawnNpc("mugger", x = 3251, y = 3390) +spawnNpc("mugger", x = 3177, y = 3363) + +spawnNpc("tramp", id = 2792, x = 3177, y = 3363) + +spawnNpc("woman", x = 3221, y = 3396) + +spawnNpc("woman", id = 5, x = 3279, y = 3497) +spawnNpc("woman", id = 25, x = 3278, y = 3492) + +spawnNpc("thief", x = 3285, y = 3500) +spawnNpc("thief", x = 3234, y = 3389) +spawnNpc("thief", x = 3188, y = 3383) +spawnNpc("thief", x = 3184, y = 3390) +spawnNpc("thief", x = 3188, y = 3394) + +spawnNpc("unicorn", x = 3286, y = 3342) +spawnNpc("unicorn", x = 3279, y = 3345) + +// North Guards + +spawnNpc("guard", x = 3244, y = 3500) +spawnNpc("guard", x = 3247, y = 3503) + +// East Guards + +spawnNpc("guard", x = 3271, y = 3431) +spawnNpc("guard", x = 3270, y = 3425) +spawnNpc("guard", x = 3274, y = 3421) +spawnNpc("guard", x = 3274, y = 3427) + +// South Guards + +spawnNpc("guard", x = 3210, y = 3382) +spawnNpc("guard", x = 3212, y = 3380) +spawnNpc("guard", x = 3207, y = 3376) + +// West Guards + +spawnNpc("guard", x = 3174, y = 3427) +spawnNpc("guard", x = 3176, y = 3430) +spawnNpc("guard", x = 3176, y = 3427) +spawnNpc("guard", x = 3180, y = 3399) +spawnNpc("guard", x = 3175, y = 3415, z = 1) +spawnNpc("guard", x = 3174, y = 3403, z = 1) + +// Varrock Palace + +spawnNpc("guard", x = 3210, y = 3461) +spawnNpc("guard", x = 3211, y = 3465) +spawnNpc("guard", x = 3214, y = 3462) +spawnNpc("guard", x = 3216, y = 3464) +spawnNpc("guard", x = 3220, y = 3461) +spawnNpc("guard", x = 3206, y = 3461) +spawnNpc("guard", x = 3204, y = 3495) + +spawnNpc("guard", x = 3204, y = 3495, z = 1) +spawnNpc("guard", x = 3205, y = 3492, z = 1) +spawnNpc("guard", x = 3203, y = 3492, z = 1) +spawnNpc("guard", x = 3205, y = 3497, z = 1) + +spawnNpc("guard", x = 3221, y = 3471, z = 2) +spawnNpc("guard", x = 3214, y = 3474, z = 2) +spawnNpc("guard", x = 3215, y = 3471, z = 2) +spawnNpc("guard", x = 3211, y = 3471, z = 2) +spawnNpc("guard", x = 3209, y = 3473, z = 2) +spawnNpc("guard", x = 3212, y = 3475, z = 2) +spawnNpc("guard", x = 3207, y = 3477, z = 2) +spawnNpc("guard", x = 3203, y = 3476, z = 2) +spawnNpc("guard", x = 3205, y = 3479, z = 2) +spawnNpc("guard", x = 3203, y = 3483, z = 2) +spawnNpc("guard", x = 3221, y = 3485, z = 2) + +spawnNpc("monk_of_zamorak", id = 189, x = 3213, y = 3476) + +spawnNpc("warrior_woman", x = 3203, y = 3490) +spawnNpc("warrior_woman", x = 3205, y = 3493) + +// Varrock/Lumbridge Pen + +spawnNpc("swan", x = 3261, y = 3354) +spawnNpc("swan", x = 3260, y = 3356) + +spawnNpc("ram", id = 3673, x = 3238, y = 3346) +spawnNpc("ram", id = 3673, x = 3248, y = 3352) +spawnNpc("ram", id = 3673, x = 3260, y = 3348) + +spawnNpc("sheep", id = 42, x = 3263, y = 3347) +spawnNpc("sheep", id = 42, x = 3268, y = 3350) +spawnNpc("sheep", id = 42, x = 3252, y = 3352) +spawnNpc("sheep", id = 42, x = 3243, y = 3344) +spawnNpc("sheep", id = 42, x = 3235, y = 3347) + +spawnNpc("sheep", id = 3579, x = 3234, y = 3344) +spawnNpc("sheep", id = 3579, x = 3241, y = 3347) +spawnNpc("sheep", id = 3579, x = 3257, y = 3350) + +// Champions Guild + +spawnNpc("chicken", x = 3195, y = 3359) +spawnNpc("chicken", x = 3198, y = 3356) +spawnNpc("chicken", x = 3195, y = 3355) + +spawnNpc("chicken", id = 1017, x = 3196, y = 3353) +spawnNpc("chicken", id = 1017, x = 3197, y = 3356) + +spawnNpc("evil_chicken", x = 3198, y = 3359) + +// Function Npc + +spawnNpc("apothecary", x = 3196, y = 3403) + +spawnNpc("captain_rovin", x = 3204, y = 3496, z = 2) + +spawnNpc("curator", x = 3256, y = 3447) + +spawnNpc("dimintheis", x = 3280, y = 3403) + +spawnNpc("dr_harlow", x = 3224, y = 3398) + +spawnNpc("ellamaria", x = 3228, y = 3475) + +spawnNpc("father_lawrence", x = 3253, y = 3484) + +spawnNpc("guidors_wife", id = 342, x = 3280, y = 3382) + +spawnNpc("guidor", x = 3284, y = 3381, facing = Direction.SOUTH) + +spawnNpc("guild_master", x = 3189, y = 3360) + +spawnNpc("gypsy", x = 3203, y = 3423) + +spawnNpc("hooknosed_jack", x = 3268, y = 3400) + +spawnNpc("jonny_the_beard", x = 3223, y = 3395) + +spawnNpc("johnathon", x = 3278, y = 3503, z = 1) + +spawnNpc("katrine", x = 3185, y = 3386) + +spawnNpc("king_roald", x = 3223, y = 3473) + +spawnNpc("master_farmer", x = 3243, y = 3349) + +spawnNpc("pox", x = 3267, y = 3399) + +spawnNpc("reldo", x = 3210, y = 3492) + +spawnNpc("romeo", x = 3211, y = 3423) + +spawnNpc("shilop", x = 3211, y = 3435) + +spawnNpc("sir_prysin", x = 3204, y = 3472) + +spawnNpc("tarquin", x = 3203, y = 3344, facing = Direction.SOUTH) + +spawnNpc("tool_leprechaun", x = 3182, y = 3355) + +spawnNpc("tool_leprechaun", x = 3229, y = 3455) + +spawnNpc("tramp", id = 641, x = 3207, y = 3392) + +spawnNpc("wilough", x = 3222, y = 3437) + +// Shop Npc + +spawnNpc("aubury", x = 3253, y = 3401) + +spawnNpc("baraek", x = 3217, y = 3434) + +spawnNpc("bartender", x = 3226, y = 3400) + +spawnNpc("bartender", id = 1921, x = 3277, y = 3487) + +spawnNpc("fancy_dress_shop_owner", x = 3281, y = 3398) + +spawnNpc("horvik", x = 3229, y = 3438) + +spawnNpc("lowe", x = 3233, y = 3421) + +spawnNpc("scavvo", x = 3192, y = 3353, z = 1) + +spawnNpc("shop_keeper", id = 551, x = 3206, y = 3399) +spawnNpc("shop_assistant", id = 552, x = 3207, y = 3396) + +spawnNpc("shop_keeper", id = 522, x = 3216, y = 3414) +spawnNpc("shop_assistant", id = 523, x = 3216, y = 3417) + +spawnNpc("tea_seller", x = 3271, y = 3411) + +spawnNpc("thessalia", x = 3206, y = 3417) + +spawnNpc("zaff", x = 3203, y = 3434) + +// Juliet House + +spawnNpc("draul_leptoc", x = 3228, y = 3475) +spawnNpc("juliet", x = 3159, y = 3425, z = 1) +spawnNpc("phillipa", x = 3160, y = 3429, z = 1) + +// Gertrude House + +spawnNpc("gertrude", x = 3153, y = 3413) +spawnNpc("kanel", x = 3155, y = 3405, facing = Direction.EAST) +spawnNpc("philop", x = 3150, y = 3405, facing = Direction.SOUTH) + +// Small Bank + +spawnNpc("banker", id = 495, x = 3252, y = 3418) +spawnNpc("banker", id = 494, x = 3252, y = 3418) +spawnNpc("banker", id = 494, x = 3252, y = 3418) +spawnNpc("banker", id = 494, x = 3252, y = 3418) + +// Big Bank + +spawnNpc("banker", id = 494, x = 3187, y = 3436, facing = Direction.WEST) +spawnNpc("banker", id = 494, x = 3187, y = 3440, facing = Direction.WEST) +spawnNpc("banker", id = 494, x = 3187, y = 3444, facing = Direction.WEST) +spawnNpc("banker", id = 495, x = 3187, y = 3438, facing = Direction.WEST) +spawnNpc("banker", id = 495, x = 3187, y = 3442, facing = Direction.WEST) \ No newline at end of file diff --git a/game/plugin/locations/varrock/src/shops.plugin.kts b/game/plugin/locations/varrock/src/shops.plugin.kts new file mode 100644 index 000000000..cc3d14881 --- /dev/null +++ b/game/plugin/locations/varrock/src/shops.plugin.kts @@ -0,0 +1,176 @@ +package org.apollo.plugin.locations.varrock + +import org.apollo.game.plugin.shops.builder.shop + +shop("Aubury's Rune Shop.") { + operated by "Aubury" + + category("runes") { + sell(5000) of { + -"Earth" + -"Water" + -"Fire" + -"Air" + -"Mind" + -"Body" + } + + sell(250) of { + -"Chaos" + -"Death" + } + } +} + +shop("Lowe's Archery Emporium.") { + operated by "Lowe" + + category("arrows") { + sell(2000) of "Bronze" + sell(1500) of "Iron" + sell(1000) of "Steel" + sell(800) of "Mithril" + sell(600) of "Adamant" + } + + category("normal weapons", affix = nothing) { + sell(4) of "Shortbow" + sell(4) of "Longbow" + sell(2) of "Crossbow" + } + + category("shortbows") { + sell(3) of "Oak" + sell(2) of "Willow" + sell(1) of "Maple" + } + + category("longbows") { + sell(3) of "Oak" + sell(2) of "Willow" + sell(1) of "Maple" + } +} + +shop("Horvik's Armour Shop.") { + operated by "Horvik" + + category("chainbody") { + sell(5) of "Bronze" + sell(3) of "Iron" + sell(3) of "Steel" + sell(1) of "Mithril" + } + + category("platebody") { + sell(3) of "Bronze" + + sell(1) of { + -"Iron" + -"Steel" + -"Black" + -"Mithril" + } + } + + sell(1) of { + -"Iron platelegs" + -"Studded body" + -"Studded chaps" + } +} + +shop("Thessalia's Fine Clothes.") { + operated by "Thessalia" + + category("apron") { + sell(3) of "White" + sell(1) of "Brown" + } + + category("leather", affix = prefix) { + sell(12) of "Body" + sell(10) of "Gloves" + sell(10) of "Boots" + } + + category("skirt") { + sell(5) of "Pink" + sell(3) of "Black" + sell(2) of "Blue" + } + + sell(4) of "Cape" + sell(5) of "Silk" + + sell(3) of { + -"Priest gown"(426) + -"Priest gown"(428) + } +} + +shop("Varrock General Store.") { + operated by "Shopkeeper"(522) and "Shop assistant"(523) + buys any items + + sell(5) of "Pot" + sell(2) of "Jug" + sell(2) of "Shears" + sell(3) of "Bucket" + sell(2) of "Bowl" + sell(2) of "Cake tin" + sell(2) of "Tinderbox" + sell(2) of "Chisel" + sell(5) of "Hammer" + sell(5) of "Newcomer map" +} + +shop("Varrock Swordshop.") { + operated by "Shopkeeper"(551) and "Shop assistant"(552) + + category("swords") { + sell(5) of "Bronze" + sell(4) of "Iron" + sell(4) of "Steel" + sell(3) of "Black" + sell(3) of "Mithril" + sell(2) of "Adamant" + } + + category("longswords") { + sell(4) of "Bronze" + sell(3) of "Iron" + sell(3) of "Steel" + sell(2) of "Black" + sell(2) of "Mithril" + sell(1) of "Adamant" + } + + category("daggers") { + sell(10) of "Bronze" + sell(6) of "Iron" + sell(5) of "Steel" + sell(4) of "Black" + sell(3) of "Mithril" + sell(2) of "Adamant" + } +} + +shop("Zaff's Superior Staffs!") { + operated by "Zaff" + + category("staves", affix = nothing) { + sell(5) of { + -"Battlestaff" + -"Staff" + -"Magic staff" + } + + sell(2) of { + -"Staff of air" + -"Staff of water" + -"Staff of earth" + -"Staff of fire" + } + } +} diff --git a/game/plugin/logout/build.gradle b/game/plugin/logout/build.gradle new file mode 100644 index 000000000..3a9351392 --- /dev/null +++ b/game/plugin/logout/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/logout/src/logout.plugin.kts b/game/plugin/logout/src/logout.plugin.kts new file mode 100644 index 000000000..0243b091f --- /dev/null +++ b/game/plugin/logout/src/logout.plugin.kts @@ -0,0 +1,5 @@ +val LOGOUT_BUTTON_ID = 2458 + +on_button(LOGOUT_BUTTON_ID) + .where { widgetId == LOGOUT_BUTTON_ID } + .then { it.logout() } \ No newline at end of file diff --git a/game/plugin/logout/test/LogoutTests.kt b/game/plugin/logout/test/LogoutTests.kt new file mode 100644 index 000000000..9a8cb9c66 --- /dev/null +++ b/game/plugin/logout/test/LogoutTests.kt @@ -0,0 +1,25 @@ +import io.mockk.verify +import org.apollo.game.message.impl.ButtonMessage +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class LogoutTests { + + companion object { + const val LOGOUT_BUTTON_ID = 2458 + } + + @TestMock + lateinit var player: Player + + @Test + fun `The player should be logged out when they click the logout button`() { + player.send(ButtonMessage(LOGOUT_BUTTON_ID)) + + verify { player.logout() } + } +} \ No newline at end of file diff --git a/game/plugin/navigation/door/build.gradle b/game/plugin/navigation/door/build.gradle new file mode 100644 index 000000000..4d96c9f57 --- /dev/null +++ b/game/plugin/navigation/door/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:api') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/navigation/door/src/door.kt b/game/plugin/navigation/door/src/door.kt new file mode 100644 index 000000000..59ee13256 --- /dev/null +++ b/game/plugin/navigation/door/src/door.kt @@ -0,0 +1,157 @@ +package org.apollo.plugin.navigation.door + +import java.util.* +import org.apollo.game.action.DistancedAction +import org.apollo.game.model.Direction +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.obj.DynamicGameObject +import org.apollo.game.model.entity.obj.GameObject +import org.apollo.game.model.event.PlayerEvent +import org.apollo.game.plugin.api.findObject +import org.apollo.net.message.Message + +enum class DoorType { + LEFT, RIGHT, NOT_SUPPORTED +} + +class Door(private val gameObject: GameObject) { + + companion object { + + val LEFT_HINGE_ORIENTATION: HashMap = hashMapOf( + Direction.NORTH to Direction.WEST, + Direction.SOUTH to Direction.EAST, + Direction.WEST to Direction.SOUTH, + Direction.EAST to Direction.NORTH + ) + + val RIGHT_HINGE_ORIENTATION: HashMap = hashMapOf( + Direction.NORTH to Direction.EAST, + Direction.SOUTH to Direction.WEST, + Direction.WEST to Direction.NORTH, + Direction.EAST to Direction.SOUTH + ) + + val toggledDoors = hashMapOf() + + val LEFT_HINGED = setOf(1516, 1536, 1533) + + val RIGHT_HINGED = setOf(1519, 1530, 4465, 4467, 3014, 3017, 3018, 3019) + + /** + * Find a given door in the world + * @param world The [World] the door lives in + * @param position The [Position] of the door + * @param objectId The [GameObject] id of the door + */ + fun find(world: World, position: Position, objectId: Int): Door? { + return world.findObject(position, objectId)?.let(::Door) + } + } + + /** + * Returns the supported doors by the system + * See [DoorType] + */ + fun supported(): Boolean { + return type() !== DoorType.NOT_SUPPORTED + } + + /** + * Computes the given door type by which id exists in + * the supported left and right hinged doors + */ + fun type(): DoorType { + return when { + gameObject.id in LEFT_HINGED -> DoorType.LEFT + gameObject.id in RIGHT_HINGED -> DoorType.RIGHT + else -> DoorType.NOT_SUPPORTED + } + } + + /** + * Toggles a given [GameObject] orientation and position + * Stores the door state in toggleDoors class variable + */ + fun toggle() { + val world = gameObject.world + val regionRepository = world.regionRepository + + regionRepository.fromPosition(gameObject.position).removeEntity(gameObject) + + val originalDoor = toggledDoors[gameObject] + + if (originalDoor == null) { + val position = movePosition() + val orientation: Int = translateDirection()?.toOrientationInteger() ?: gameObject.orientation + + val toggledDoor = DynamicGameObject.createPublic(world, gameObject.id, position, gameObject.type, + orientation) + + regionRepository.fromPosition(position).addEntity(toggledDoor) + toggledDoors[toggledDoor] = gameObject + } else { + toggledDoors.remove(gameObject) + regionRepository.fromPosition(originalDoor.position).addEntity(originalDoor) + } + } + + /** + * Calculates the position to move the door based on orientation + */ + private fun movePosition(): Position { + return gameObject.position.step(1, Direction.WNES[gameObject.orientation]) + } + + /** + * Calculates the orientation of the door based on + * if it is right or left hinged door + */ + private fun translateDirection(): Direction? { + val direction = Direction.WNES[gameObject.orientation] + + return when (type()) { + DoorType.LEFT -> LEFT_HINGE_ORIENTATION[direction] + DoorType.RIGHT -> RIGHT_HINGE_ORIENTATION[direction] + DoorType.NOT_SUPPORTED -> null + } + } +} + +class OpenDoorAction(private val player: Player, private val door: Door, position: Position) : DistancedAction( + 0, true, player, position, DISTANCE) { + + companion object { + + /** + * The distance threshold that must be reached before the door is opened. + */ + const val DISTANCE = 1 + + /** + * Starts a [OpenDoorAction] for the specified [Player], terminating the [Message] that triggered. + */ + fun start(message: Message, player: Player, door: Door, position: Position) { + player.startAction(OpenDoorAction(player, door, position)) + message.terminate() + } + } + + override fun executeAction() { + if (player.world.submit(OpenDoorEvent(player))) { + player.turnTo(position) + door.toggle() + } + stop() + } + + override fun equals(other: Any?): Boolean { + return other is OpenDoorAction && position == other.position && player == other.player + } + + override fun hashCode(): Int = Objects.hash(position, player) +} + +class OpenDoorEvent(player: Player) : PlayerEvent(player) diff --git a/game/plugin/navigation/door/src/door.plugin.kts b/game/plugin/navigation/door/src/door.plugin.kts new file mode 100644 index 000000000..d91ceee9a --- /dev/null +++ b/game/plugin/navigation/door/src/door.plugin.kts @@ -0,0 +1,16 @@ + +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.plugin.navigation.door.Door +import org.apollo.plugin.navigation.door.OpenDoorAction + +/** + * Hook into the [ObjectActionMessage] and listens for a supported door [GameObject] + */ +on { ObjectActionMessage::class } + .where { option == 1 } + .then { + val door = Door.find(it.world, position, id) ?: return@then + if (door.supported()) { + OpenDoorAction.start(this, it, door, position) + } + } \ No newline at end of file diff --git a/game/plugin/run/build.gradle b/game/plugin/run/build.gradle new file mode 100644 index 000000000..3a9351392 --- /dev/null +++ b/game/plugin/run/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/run/src/run.plugin.kts b/game/plugin/run/src/run.plugin.kts new file mode 100644 index 000000000..fa65f0495 --- /dev/null +++ b/game/plugin/run/src/run.plugin.kts @@ -0,0 +1,10 @@ +import org.apollo.game.message.impl.ButtonMessage + +val WALK_BUTTON_ID = 152 +val RUN_BUTTON_ID = 153 + +on { ButtonMessage::class } + .where { widgetId == WALK_BUTTON_ID || widgetId == RUN_BUTTON_ID } + .then { + it.toggleRunning() + } \ No newline at end of file diff --git a/game/plugin/shops/build.gradle b/game/plugin/shops/build.gradle new file mode 100644 index 000000000..4d96c9f57 --- /dev/null +++ b/game/plugin/shops/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:api') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/Currency.kt b/game/plugin/shops/src/org/apollo/game/plugin/shops/Currency.kt new file mode 100644 index 000000000..6548f4d30 --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/Currency.kt @@ -0,0 +1,26 @@ +package org.apollo.game.plugin.shops + +import org.apollo.game.plugin.api.Definitions + +/** + * A [Shop]'s method of payment. + * + * @param id The item id of the currency. + * @param plural Whether or not the name of this currency is plural. + */ +data class Currency(val id: Int, val plural: Boolean = false) { + + val name = requireNotNull(Definitions.item(id).name?.toLowerCase()) { "Currencies must have a name." } + + fun name(amount: Int): String { + return when { + amount == 1 && plural -> name.removeSuffix("s") + else -> name + } + } + + companion object { + val COINS = Currency(995, plural = true) + } + +} \ No newline at end of file diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/OpenShopAction.kt b/game/plugin/shops/src/org/apollo/game/plugin/shops/OpenShopAction.kt new file mode 100644 index 000000000..a87a31256 --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/OpenShopAction.kt @@ -0,0 +1,69 @@ +package org.apollo.game.plugin.shops + +import org.apollo.game.action.DistancedAction +import org.apollo.game.message.handler.ItemVerificationHandler +import org.apollo.game.message.impl.SetWidgetTextMessage +import org.apollo.game.model.entity.Mob +import org.apollo.game.model.entity.Player +import org.apollo.game.model.inter.InterfaceListener +import org.apollo.game.model.inv.Inventory +import org.apollo.game.model.inv.SynchronizationInventoryListener + +/** + * A [DistancedAction] that opens a [Shop]. + */ +class OpenShopAction( + player: Player, + private val shop: Shop, + private val operator: Mob +) : DistancedAction(0, true, player, operator.position, 1) { // TODO this needs to follow the NPC if they move + + override fun executeAction() { + mob.interactingMob = operator + + val closeListener = addInventoryListeners(mob, shop.inventory) + mob.send(SetWidgetTextMessage(ShopInterfaces.SHOP_NAME, shop.name)) + + mob.interfaceSet.openWindowWithSidebar(closeListener, ShopInterfaces.SHOP_WINDOW, + ShopInterfaces.INVENTORY_SIDEBAR) + stop() + } + + /** + * Adds [SynchronizationInventoryListener]s to the [Player] and [Shop] [Inventories][Inventory], returning an + * [InterfaceListener] that removes them when the interface is closed. + */ + private fun addInventoryListeners(player: Player, shop: Inventory): InterfaceListener { + val invListener = SynchronizationInventoryListener(player, ShopInterfaces.INVENTORY_CONTAINER) + val shopListener = SynchronizationInventoryListener(player, ShopInterfaces.SHOP_CONTAINER) + + player.inventory.addListener(invListener) + player.inventory.forceRefresh() + + shop.addListener(shopListener) + shop.forceRefresh() + + return InterfaceListener { + mob.interfaceSet.close() + mob.resetInteractingMob() + + mob.inventory.removeListener(invListener) + shop.removeListener(shopListener) + } + } +} + +/** + * An [InventorySupplier] that returns a [Player]'s [Inventory] if they are browsing a shop. + */ +object PlayerInventorySupplier : ItemVerificationHandler.InventorySupplier { + + override fun getInventory(player: Player): Inventory? { + return if (Interfaces.SHOP_WINDOW in player.interfaceSet) { + player.inventory + } else { + null + } + } + +} \ No newline at end of file diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/Shop.kt b/game/plugin/shops/src/org/apollo/game/plugin/shops/Shop.kt new file mode 100644 index 000000000..08f9014c3 --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/Shop.kt @@ -0,0 +1,312 @@ +package org.apollo.game.plugin.shops + +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.Item +import org.apollo.game.model.entity.Player +import org.apollo.game.model.inv.Inventory +import org.apollo.game.model.inv.Inventory.StackMode.STACK_ALWAYS +import org.apollo.game.plugin.shops.Shop.Companion.ExchangeType.BUYING +import org.apollo.game.plugin.shops.Shop.Companion.ExchangeType.SELLING +import org.apollo.game.plugin.shops.Shop.PurchasePolicy.ANY +import org.apollo.game.plugin.shops.Shop.PurchasePolicy.NOTHING +import org.apollo.game.plugin.shops.Shop.PurchasePolicy.OWNED + +/** + * Contains shop-related interface ids. + */ +object Interfaces { + + /** + * The container interface id for the player's inventory. + */ + const val INVENTORY_CONTAINER = 3823 + + /** + * The sidebar id for the inventory, when a Shop window is open. + */ + const val INVENTORY_SIDEBAR = 3822 + + /** + * The shop window interface id. + */ + const val SHOP_WINDOW = 3824 + + /** + * The container interface id for the shop's inventory. + */ + const val SHOP_CONTAINER = 3900 + + /** + * The id of the text widget that displays a shop's name. + */ + const val SHOP_NAME = 3901 +} + +/** + * The [Map] from npc ids to [Shop]s. + */ +val SHOPS = mutableMapOf() + +/** + * An in-game shop, operated by one or more npcs. + * + * @param name The name of the shop. + * @param action The id of the NpcActionMessage sent (by the client) when a player opens this shop. + * @param sells The [Map] from item id to amount sold. + * @param operators The [List] of Npc ids that can open this shop. + * @param currency The [Currency] used when making exchanges with this [Shop]. + * @param purchases This [Shop]'s attitude towards purchasing items from players. + */ +class Shop( + val name: String, + val action: Int, + private val sells: Map, + val operators: List, + private val currency: Currency = Currency.COINS, + private val purchases: PurchasePolicy = OWNED +) { + + /** + * The [Inventory] containing this [Shop]'s current items. + */ + val inventory = Inventory(CAPACITY, STACK_ALWAYS) + + init { + sells.forEach { (id, amount) -> inventory.add(id, amount) } + } + + /** + * Restocks this [Shop], adding and removing items as necessary to move the stock closer to its initial state. + */ + fun restock() { + for (item in inventory.items.filterNotNull()) { + val id = item.id + + if (!sells(id) || item.amount > sells[id]!!) { + inventory.remove(id) + } else if (item.amount < sells[id]!!) { + inventory.add(id) + } + } + } + + /** + * Sells an item to a [Player]. + */ + fun sell(player: Player, slot: Int, option: Int) { + val item = inventory.get(slot) + val id = item.id + val itemCost = value(id, SELLING) + + if (option == VALUATION_OPTION) { + val itemId = ItemDefinition.lookup(id).name + player.sendMessage("$itemId: currently costs $itemCost ${currency.name(itemCost)}.") + return + } + + var buying = amount(option) + var unavailable = false + + val amount = item.amount + if (buying > amount) { + buying = amount + unavailable = true + } + + val stackable = item.definition.isStackable + val slotsRequired = when { + stackable && player.inventory.contains(id) -> 0 + !stackable -> buying + else -> 1 + } + + val freeSlots = player.inventory.freeSlots() + var full = false + + if (slotsRequired > freeSlots) { + buying = freeSlots + full = true + } + + val totalCost = buying * itemCost + val totalCurrency = player.inventory.getAmount(currency.id) + var unaffordable = false + + if (totalCost > totalCurrency) { + buying = totalCurrency / itemCost + unaffordable = true + } + + if (buying > 0) { + player.inventory.remove(currency.id, totalCost) + val remaining = player.inventory.add(id, buying) + + if (remaining > 0) { + player.inventory.add(currency.id, remaining * itemCost) + } + + if (buying >= amount && sells(id)) { + // If the item is from the shop's main stock, set its amount to zero so it can be restocked over time. + inventory.set(slot, Item(id, 0)) + } else { + inventory.remove(id, buying - remaining) + } + } + + val message = when { + unaffordable -> "You don't have enough ${currency.name}." + full -> "You don't have enough inventory space." + unavailable -> "The shop has run out of stock." + else -> return + } + + player.sendMessage(message) + } + + /** + * Purchases the item from the specified [Player]. + */ + fun buy(seller: Player, slot: Int, option: Int) { + val player = seller.inventory + val id = player.get(slot).id + + if (!verifyPurchase(seller, id)) { + return + } + + val value = value(id, BUYING) + if (option == VALUATION_OPTION) { + seller.sendMessage("${ItemDefinition.lookup(id).name}: shop will buy for $value ${currency.name(value)}.") + return + } + + val amount = Math.min(player.getAmount(id), amount(option)) + + player.remove(id, amount) + inventory.add(id, amount) + + if (value != 0) { + player.add(currency.id, value * amount) + } + } + + /** + * Returns the value of the item with the specified id. + * + * @param method The [ExchangeType]. + */ + private fun value(item: Int, method: ExchangeType): Int { + val value = ItemDefinition.lookup(item).value + + return when (method) { + BUYING -> when (purchases) { + NOTHING -> throw UnsupportedOperationException("Cannot get sell value in shop that doesn't buy.") + OWNED -> (value * 0.6).toInt() + ANY -> (value * 0.4).toInt() + } + SELLING -> when (purchases) { + ANY -> Math.ceil(value * 0.8).toInt() + else -> value + } + } + } + + /** + * Verifies that the [Player] can actually sell an item with the given id to this [Shop]. + * + * @param id The id of the [Item] to sell. + */ + private fun verifyPurchase(player: Player, id: Int): Boolean { + val item = ItemDefinition.lookup(id) + + if (!purchases(id) || item.isMembersOnly && !player.isMembers || item.value == 0) { + player.sendMessage("You can't sell this item to this shop.") + return false + } else if (inventory.freeSlots() == 0 && !inventory.contains(id)) { + player.sendMessage("The shop is currently full at the moment.") + return false + } + + return true + } + + /** + * Returns whether or not this [Shop] will purchase an item with the given id. + * + * @param id The id of the [Item] purchase buy. + */ + private fun purchases(id: Int): Boolean { + return id != currency.id && when (purchases) { + NOTHING -> false + OWNED -> sells.containsKey(id) + ANY -> true + } + } + + /** + * Returns whether or not this [Shop] sells the item with the given id. + * + * @param id The id of the [Item] to sell. + */ + private fun sells(id: Int): Boolean = sells.containsKey(id) + + /** + * The [Shop]s policy regarding purchasing items from players. + */ + enum class PurchasePolicy { + + /** + * Never purchase anything from players. + */ + NOTHING, + + /** + * Only purchase items that this Shop sells by default. + */ + OWNED, + + /** + * Purchase any tradeable items. + */ + ANY + } + + companion object { + + /** + * The amount of pulses between shop inventory restocking. + */ + const val RESTOCK_INTERVAL = 100 + + /** + * The capacity of a [Shop]. + */ + private const val CAPACITY = 30 + + /** + * The type of exchange occurring between the [Player] and [Shop]. + */ + private enum class ExchangeType { BUYING, SELLING } + + /** + * The option id for item valuation. + */ + private const val VALUATION_OPTION = 1 + + /** + * Returns the amount that a player tried to buy or sell. + * + * @param option The id of the option the player selected. + */ + private fun amount(option: Int): Int { + return when (option) { + 2 -> 1 + 3 -> 5 + 4 -> 10 + else -> throw IllegalArgumentException("Option must be 1-4") + } + } + + } + +} \ No newline at end of file diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/ShopInterfaces.kt b/game/plugin/shops/src/org/apollo/game/plugin/shops/ShopInterfaces.kt new file mode 100644 index 000000000..4a678bd8f --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/ShopInterfaces.kt @@ -0,0 +1,33 @@ +package org.apollo.game.plugin.shops + +/** + * Contains shop-related interface ids. + */ +internal object ShopInterfaces { + + /** + * The container interface id for the player's inventory. + */ + const val INVENTORY_CONTAINER = 3823 + + /** + * The sidebar id for the inventory, when a Shop window is open. + */ + const val INVENTORY_SIDEBAR = 3822 + + /** + * The shop window interface id. + */ + const val SHOP_WINDOW = 3824 + + /** + * The container interface id for the shop's inventory. + */ + const val SHOP_CONTAINER = 3900 + + /** + * The id of the text widget that displays a shop's name. + */ + const val SHOP_NAME = 3901 + +} \ No newline at end of file diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/Shops.plugin.kts b/game/plugin/shops/src/org/apollo/game/plugin/shops/Shops.plugin.kts new file mode 100644 index 000000000..4d3be298d --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/Shops.plugin.kts @@ -0,0 +1,47 @@ +package org.apollo.game.plugin.shops + +import org.apollo.game.message.handler.ItemVerificationHandler +import org.apollo.game.message.impl.ItemActionMessage +import org.apollo.game.message.impl.NpcActionMessage +import org.apollo.game.model.entity.Mob +import org.apollo.game.scheduling.ScheduledTask + +fun Mob.shop(): Shop? = SHOPS[definition.id] + +start { world -> + ItemVerificationHandler.addInventory(ShopInterfaces.SHOP_CONTAINER) { it.interactingMob?.shop()?.inventory } + ItemVerificationHandler.addInventory(ShopInterfaces.INVENTORY_CONTAINER, PlayerInventorySupplier) + + world.schedule(object : ScheduledTask(Shop.RESTOCK_INTERVAL, false) { + override fun execute() = SHOPS.values.distinct().forEach(Shop::restock) + }) +} + +on { NpcActionMessage::class } + .then { player -> + val npc = player.world.npcRepository.get(index) + val shop = npc.shop() ?: return@then + + if (shop.action == option) { + player.startAction(OpenShopAction(player, shop, npc)) + terminate() + } + } + + +on { ItemActionMessage::class } + .where { interfaceId == ShopInterfaces.SHOP_CONTAINER || interfaceId == ShopInterfaces.INVENTORY_CONTAINER } + .then { player -> + if (ShopInterfaces.SHOP_WINDOW !in player.interfaceSet) { + return@then + } + + val shop = player.interactingMob?.shop() ?: return@then + when (interfaceId) { + ShopInterfaces.INVENTORY_CONTAINER -> shop.buy(player, slot, option) + ShopInterfaces.SHOP_CONTAINER -> shop.sell(player, slot, option) + else -> error("Supposedly unreacheable case.") + } + + terminate() + } diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/ActionBuilder.kt b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/ActionBuilder.kt new file mode 100644 index 000000000..bfd251a11 --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/ActionBuilder.kt @@ -0,0 +1,62 @@ +package org.apollo.game.plugin.shops.builder + +import org.apollo.cache.def.NpcDefinition + +/** + * A builder to provide the action id used to open the shop. + */ +@ShopDslMarker +class ActionBuilder { + + private var action: String = "Trade" + + private var actionId: Int? = null + + /** + * Sets the name or id of the action used to open the shop interface with an npc. Defaults to "Trade". + * + * If specifying an id it must account for hidden npc menu actions (if any exist) - if "Open Shop" is the first + * action displayed when the npc is right-clicked, it does not necessarily mean that the action id is `1`. + * + * @param action The `name` (as a [String]) or `id` (as an `Int`) of the npc's action menu, to open the shop. + * @throws IllegalArgumentException If `action` is not a [String] or [Int]. + */ // TODO this is dumb, replace it + override fun equals(@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") action: Any?): Boolean { + if (action is String) { + this.action = action + return true + } else if (action is Int) { + actionId = action + return true + } + + throw IllegalArgumentException("The Npc option must be provided as a String (the option name) or an Int (the option index)\"") + } + + /** + * Returns the open shop action slot. + * + * @throws IllegalArgumentException If the action id or name is invalid. + */ + internal fun slot(npc: NpcDefinition): Int { + actionId?.let { action -> + require(npc.hasInteraction(action - 1)) { + "Npc ${npc.name} does not have an an action $action." // action - 1 because ActionMessages are 1-based + } + + return action + } + + val index = npc.interactions.indexOf(action) + require(index != -1) { "Npc ${npc.name} does not have an an action $action." } + + return index + 1 // ActionMessages are 1-based + } + + /** + * Throws [UnsupportedOperationException]. + */ + override fun hashCode(): Int = throw UnsupportedOperationException("ActionBuilder is a utility class for a DSL " + + "and improperly implements equals() - it should not be used anywhere outside of the DSL.") + +} \ No newline at end of file diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/CategoryBuilder.kt b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/CategoryBuilder.kt new file mode 100644 index 000000000..5f10c470c --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/CategoryBuilder.kt @@ -0,0 +1,59 @@ +package org.apollo.game.plugin.shops.builder + +/** + * A builder for a category - a collection of sold items that share a common prefix or suffix. + * + * ``` + * category("mould") { + * sell(10) of "Ring" + * sell(2) of "Necklace" + * sell(10) of "Amulet" + * } + * ``` + */ +@ShopDslMarker +class CategoryBuilder { + + /** + * The items that this shop sells, as a pair of item name to amount sold. + */ + private val items = mutableListOf>() + + /** + * Creates a [SellBuilder] with the specified [amount]. + */ + fun sell(amount: Int): SellBuilder = SellBuilder(amount, items) + + /** + * Builds this category into a list of sold items, represented as a pair of item name to amount sold. + */ + fun build(): List> = items + + /** + * The method of joining the item and category name. + */ + sealed class Affix(private val joiner: (item: String, category: String) -> String) { + + /** + * Appends the category after the item name (with a space between). + */ + object Suffix : Affix({ item, affix -> "$item $affix" }) + + /** + * Prepends the category before the item name (with a space between). + */ + object Prefix : Affix({ item, affix -> "$affix $item" }) + + /** + * Does not join the category at all (i.e. only returns the item name). + */ + object None : Affix({ item, _ -> item }) + + /** + * Joins the item and category name in the expected manner. + */ + fun join(item: String, category: String): String = joiner(item, category) + + } + +} \ No newline at end of file diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/CurrencyBuilder.kt b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/CurrencyBuilder.kt new file mode 100644 index 000000000..f4d812245 --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/CurrencyBuilder.kt @@ -0,0 +1,25 @@ +package org.apollo.game.plugin.shops.builder + +import org.apollo.game.plugin.shops.Currency + +/** + * A builder to provide the currency used by the [Shop]. + */ +@ShopDslMarker +class CurrencyBuilder { + + private var currency = Currency.COINS + + /** + * Overloads the `in` operator on [Currency] to achieve e.g. `trades in tokkul`. + * + * This function violates the contract for the `in` operator and is only to be used inside the Shops DSL. + */ + operator fun Currency.contains(builder: CurrencyBuilder): Boolean { + builder.currency = this + return true + } + + fun build(): Currency = currency + +} \ No newline at end of file diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/OperatorBuilder.kt b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/OperatorBuilder.kt new file mode 100644 index 000000000..7585cbbba --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/OperatorBuilder.kt @@ -0,0 +1,82 @@ +package org.apollo.game.plugin.shops.builder + +import org.apollo.game.plugin.api.Definitions + +/** + * A builder to provide the list of shop operators - the npcs that can be interacted with to access the shop. + * + * ``` + * shop("General Store.") { + * operated by "Shopkeeper"(522) and "Shop assistant"(523) and "Shop assistant"(524) + * ... + * } + * ``` + */ +@ShopDslMarker +class OperatorBuilder internal constructor(private val shopName: String) { + + /** + * The [List] of shop operator ids. + */ + private val operators = mutableListOf() + + /** + * Adds a shop operator, using the specified [name] to resolve the npc id. + */ + infix fun by(name: String): OperatorBuilder { + val npc = requireNotNull(Definitions.npc(name)) { + "Failed to resolve npc named `$name` when building shop $shopName." + } + + operators += npc.id + return this + } + + /** + * Adds a shop operator, using the specified [name] to resolve the npc id. + * + * An alias for [by]. + */ + infix fun and(name: String): OperatorBuilder = by(name) + + /** + * Adds a shop operator, using the specified [name] to resolve the npc id. + * + * An alias for [by]. + */ + operator fun plus(name: String): OperatorBuilder = and(name) + + /** + * Adds a shop operator with the specified npc id. Intended to be used with the overloaded String invokation + * operator, solely to disambiguate between npcs with the same name (e.g. `"Shopkeeper"(500) vs + * `"Shopkeeper"(501)`). Use [by(String)][by] if the npc name is unambiguous. + */ + infix fun by(pair: Pair): OperatorBuilder { + operators += pair.second + return this + } + + /** + * Adds a shop operator with the specified npc id. Intended to be used with the overloaded String invokation + * operator, solely to disambiguate between npcs with the same name (e.g. `"Shopkeeper"(500) vs + * `"Shopkeeper"(501)`). Use [by(String)][by] if the npc name is unambiguous. + * + * An alias for [by(Pair)][by]. + */ + infix fun and(pair: Pair): OperatorBuilder = by(pair) + + /** + * Adds a shop operator with the specified npc id. Intended to be used with the overloaded String invokation + * operator, solely to disambiguate between npcs with the same name (e.g. `"Shopkeeper"(500) vs + * `"Shopkeeper"(501)`). Use [by(String)][by] if the npc name is unambiguous. + * + * An alias for [by(Pair)][by]. + */ + operator fun plus(pair: Pair): OperatorBuilder = by(pair) + + /** + * Builds this [OperatorBuilder] into a [List] of operator npc ids. + */ + fun build(): List = operators + +} \ No newline at end of file diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/PurchasesBuilder.kt b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/PurchasesBuilder.kt new file mode 100644 index 000000000..9adfa8deb --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/PurchasesBuilder.kt @@ -0,0 +1,29 @@ +package org.apollo.game.plugin.shops.builder + +import org.apollo.game.plugin.shops.Shop + +/** + * A builder to provide the [Shop.PurchasePolicy]. + */ +@ShopDslMarker +class PurchasesBuilder { + + private var policy = Shop.PurchasePolicy.OWNED + + /** + * Instructs the shop to purchase no items, regardless of whether or not it sells it. + */ + infix fun no(@Suppress("UNUSED_PARAMETER") items: Unit) { + policy = Shop.PurchasePolicy.NOTHING + } + + /** + * Instructs the shop to purchase any tradeable item. + */ + infix fun any(@Suppress("UNUSED_PARAMETER") items: Unit) { + policy = Shop.PurchasePolicy.ANY + } + + fun build(): Shop.PurchasePolicy = policy + +} \ No newline at end of file diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/SellBuilder.kt b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/SellBuilder.kt new file mode 100644 index 000000000..fe9550564 --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/SellBuilder.kt @@ -0,0 +1,43 @@ +package org.apollo.game.plugin.shops.builder + +/** + * A builder to provide the items to sell. + * + * @param amount The amount to sell (of each item). + * @param items The [MutableList] to insert the given items into. + */ +@ShopDslMarker +class SellBuilder(val amount: Int, val items: MutableList>) { + + infix fun of(lambda: SellBuilder.() -> Unit) = lambda(this) + + /** + * Provides an item with the specified name. + * + * @name The item name. Must be unambiguous. + */ + infix fun of(name: String) { + items += Pair(name, amount) + } + + /** + * Overloads unary minus on Strings so that item names can be listed. + */ + operator fun String.unaryMinus() { + of(this) + } + + /** + * Overloads the unary minus on Pairs so that name+id pairs can be listed. Only intended to be used with the + * overloaded String invokation operator. + */ // ShopBuilder uses the lookup plugin, which can operate on _ids tacked on the end + operator fun Pair.unaryMinus() { + items += Pair("${first}_$second", amount) + } + + /** + * Overloads function invokation on Strings to map `"ambiguous_npc_name"(id)` to a [Pair]. + */ + operator fun String.invoke(id: Int): Pair = Pair(this, id) + +} \ No newline at end of file diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/ShopBuilder.kt b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/ShopBuilder.kt new file mode 100644 index 000000000..00b6b3b94 --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/ShopBuilder.kt @@ -0,0 +1,117 @@ +package org.apollo.game.plugin.shops.builder + +import org.apollo.cache.def.NpcDefinition +import org.apollo.game.plugin.api.Definitions +import org.apollo.game.plugin.shops.Currency +import org.apollo.game.plugin.shops.SHOPS +import org.apollo.game.plugin.shops.Shop +import org.apollo.game.plugin.shops.builder.CategoryBuilder.Affix + +/** + * Creates a [Shop]. + * + * @param name The name of the shop. + */ +fun shop(name: String, builder: ShopBuilder.() -> Unit) { + val shop = ShopBuilder(name).apply(builder).build() + + shop.operators.associateByTo(SHOPS, { it }, { shop }) +} + +/** + * A builder for a [Shop]. + */ +@ShopDslMarker +class ShopBuilder(val name: String) { + + /** + * The id on the operator npc's action menu used to open the shop. + */ + val action = ActionBuilder() + + /** + * The type of [Currency] the [Shop] makes exchanges with. + */ + var trades = CurrencyBuilder() + + /** + * The [OperatorBuilder] used to collate the [Shop]'s operators. + */ + val operated = OperatorBuilder(name) + + /** + * The [Shop]'s policy towards purchasing items from players. + */ + var buys = PurchasesBuilder() + + /** + * Redundant variable used in the purchases dsl, to complete the [PurchasesBuilder] (e.g. `buys no items`). + */ + val items = Unit + + /** + * Used in the category dsl. Places the category name before the item name (inserting a space between the names). + */ + val prefix = Affix.Prefix + + /** + * Used in the category dsl. Prevents the category name from being joined to the item name in any way. + */ + val nothing = Affix.None + + /** + * The [List] of items sold by the shop, as (name, amount) [Pair]s. + */ + private val sold = mutableListOf>() + + /** + * Overloads function invokation on strings to map `"ambiguous_npc_name"(id)` to a [Pair]. + */ + operator fun String.invoke(id: Int): Pair = Pair(this, id) + + /** + * Adds a sequence of items to this Shop, grouped together (in the DSL) for convenience. Items will be displayed + * in the same order they are provided. + * + * @param name The name of the category. + * @param affix The method of affixation between the item and category name (see [Affix]). + * @param depluralise Whether or not the category name should have the "s". + * @param builder The builder that adds items to the category. + */ + fun category( + name: String, + affix: Affix = Affix.Suffix, + depluralise: Boolean = true, // TODO search for both with and without plural + builder: CategoryBuilder.() -> Unit + ) { + val items = CategoryBuilder().apply(builder).build() + + val category = when { + depluralise -> name.removeSuffix("s") + else -> name + } + + sold += items.map { (name, amount) -> Pair(affix.join(name, category), amount) } + } + + /** + * Creates a [SellBuilder] with the specified [amount]. + */ + fun sell(amount: Int): SellBuilder = SellBuilder(amount, sold) + + /** + * Converts this builder into a [Shop]. + */ + internal fun build(): Shop { + val operators = operated.build() + val npc = NpcDefinition.lookup(operators.first()) + + val items = sold.associateBy( + { requireNotNull(Definitions.item(it.first)?.id) { "Failed to find item ${it.first} in shop $name." } }, + { it.second } + ) + + return Shop(name, action.slot(npc), items, operators, trades.build(), buys.build()) + } + +} diff --git a/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/ShopDslMarker.kt b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/ShopDslMarker.kt new file mode 100644 index 000000000..3187a23bb --- /dev/null +++ b/game/plugin/shops/src/org/apollo/game/plugin/shops/builder/ShopDslMarker.kt @@ -0,0 +1,7 @@ +package org.apollo.game.plugin.shops.builder + +/** + * A [DslMarker] for the shop DSL. + */ +@DslMarker +internal annotation class ShopDslMarker \ No newline at end of file diff --git a/game/plugin/shops/test/org/apollo/game/plugin/shops/CurrencyTests.kt b/game/plugin/shops/test/org/apollo/game/plugin/shops/CurrencyTests.kt new file mode 100644 index 000000000..de897d0c2 --- /dev/null +++ b/game/plugin/shops/test/org/apollo/game/plugin/shops/CurrencyTests.kt @@ -0,0 +1,27 @@ +package org.apollo.game.plugin.shops + +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class CurrencyTests { + + @Test + fun `items used as currencies must have names in their definitions`() { + assertThrows("Should not be able to create a Currency with an item missing a name") { + Currency(id = ITEM_MISSING_NAME) + } + } + + private companion object { + private const val ITEM_MISSING_NAME = 0 + + @ItemDefinitions + private val unnamed = listOf(ItemDefinition(ITEM_MISSING_NAME)) + } + +} \ No newline at end of file diff --git a/game/plugin/shops/test/org/apollo/game/plugin/shops/ShopActionTests.kt b/game/plugin/shops/test/org/apollo/game/plugin/shops/ShopActionTests.kt new file mode 100644 index 000000000..643fcd5ff --- /dev/null +++ b/game/plugin/shops/test/org/apollo/game/plugin/shops/ShopActionTests.kt @@ -0,0 +1,21 @@ +package org.apollo.game.plugin.shops + +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class ShopActionTests { + + @TestMock + lateinit var player: Player + + @TestMock + lateinit var action: ActionCapture + + + + +} \ No newline at end of file diff --git a/game/plugin/skills/fishing/build.gradle b/game/plugin/skills/fishing/build.gradle new file mode 100644 index 000000000..4314d6fd4 --- /dev/null +++ b/game/plugin/skills/fishing/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'kotlin' + + + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:api') + implementation project(':game:plugin:entity:spawn') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/Fish.kt b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/Fish.kt new file mode 100644 index 000000000..45f76b826 --- /dev/null +++ b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/Fish.kt @@ -0,0 +1,30 @@ +package org.apollo.game.plugin.skills.fishing + +import org.apollo.game.plugin.api.Definitions + +/** + * A fish that can be gathered using the fishing skill. + */ +enum class Fish(val id: Int, val level: Int, val experience: Double, catchSuffix: String? = null) { + SHRIMPS(id = 317, level = 1, experience = 10.0, catchSuffix = "some shrimp."), + SARDINE(id = 327, level = 5, experience = 20.0), + MACKEREL(id = 353, level = 16, experience = 20.0), + HERRING(id = 345, level = 10, experience = 30.0), + ANCHOVIES(id = 321, level = 15, experience = 40.0, catchSuffix = "some anchovies."), + TROUT(id = 335, level = 20, experience = 50.0), + COD(id = 341, level = 23, experience = 45.0), + PIKE(id = 349, level = 25, experience = 60.0), + SALMON(id = 331, level = 30, experience = 70.0), + TUNA(id = 359, level = 35, experience = 80.0), + LOBSTER(id = 377, level = 40, experience = 90.0), + BASS(id = 363, level = 46, experience = 100.0), + SWORDFISH(id = 371, level = 50, experience = 100.0), + SHARK(id = 383, level = 76, experience = 110.0, catchSuffix = "a shark!"); + + /** + * The name of this fish, formatted so it can be inserted into a message. + */ + val catchMessage by lazy { "You catch ${catchSuffix ?: "a $catchName."}" } + + private val catchName by lazy { Definitions.item(id).name.toLowerCase().removePrefix("raw ") } +} diff --git a/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/Fishing.plugin.kts b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/Fishing.plugin.kts new file mode 100644 index 000000000..597058bbe --- /dev/null +++ b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/Fishing.plugin.kts @@ -0,0 +1,20 @@ +package org.apollo.game.plugin.skills.fishing + +import org.apollo.game.message.impl.NpcActionMessage + +// TODO: moving fishing spots, seaweed and caskets, evil bob + +/** + * Intercepts the [NpcActionMessage] and starts a [FishingAction] if the npc + */ +on { NpcActionMessage::class } + .where { option == 1 || option == 3 } + .then { player -> + val entity = player.world.npcRepository[index] + val option = FishingSpot.lookup(entity.id)?.option(option) ?: return@then + + val target = FishingTarget(entity.position, option) + player.startAction(FishingAction(player, target)) + + terminate() + } diff --git a/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/FishingAction.kt b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/FishingAction.kt new file mode 100644 index 000000000..9eacf024e --- /dev/null +++ b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/FishingAction.kt @@ -0,0 +1,86 @@ +package org.apollo.game.plugin.skills.fishing + +import java.util.Objects +import org.apollo.game.action.ActionBlock +import org.apollo.game.action.AsyncDistancedAction +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.api.fishing + +class FishingAction( + player: Player, + private val target: FishingTarget +) : AsyncDistancedAction(0, true, player, target.position, SPOT_DISTANCE) { + + /** + * The [FishingTool] used for the fishing spot. + */ + private val tool = target.option.tool + + override fun action(): ActionBlock = { + if (!target.verify(mob)) { + stop() + } + + mob.turnTo(position) + mob.sendMessage(tool.message) + + while (isRunning) { + mob.playAnimation(tool.animation) + wait(FISHING_DELAY) + + val level = mob.fishing.current + val fish = target.option.sample(level) + + if (target.isSuccessful(mob, fish.level)) { + if (tool.bait != -1) { + mob.inventory.remove(tool.bait) + } + + mob.inventory.add(fish.id) + mob.sendMessage(fish.catchMessage) + mob.fishing.experience += fish.experience + + if (mob.inventory.freeSlots() == 0) { + mob.inventory.forceCapacityExceeded() + + mob.stopAnimation() + stop() + } else if (!hasBait(mob, tool.bait)) { + mob.sendMessage("You need more ${tool.baitName} to fish at this spot.") + + mob.stopAnimation() + stop() + } + } + } + } + + override fun equals(other: Any?): Boolean { + if (other is FishingAction) { + return position == other.position && target == other.target && mob == other.mob + } + + return false + } + + override fun hashCode(): Int = Objects.hash(target, position, mob) + + internal companion object { + private const val SPOT_DISTANCE = 1 + private const val FISHING_DELAY = 4 + + /** + * Returns whether or not the [Player] has (or does not need) bait. + */ + internal fun hasBait(player: Player, bait: Int): Boolean { + return bait == -1 || bait in player.inventory + } + + /** + * Returns whether or not the player has the required tool to fish at the spot. + */ + internal fun hasTool(player: Player, tool: FishingTool): Boolean { + return tool.id in player.equipment || tool.id in player.inventory + } + } +} \ No newline at end of file diff --git a/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/FishingSpot.kt b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/FishingSpot.kt new file mode 100644 index 000000000..380dd1a07 --- /dev/null +++ b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/FishingSpot.kt @@ -0,0 +1,120 @@ +package org.apollo.game.plugin.skills.fishing + +import org.apollo.game.plugin.api.rand +import org.apollo.game.plugin.skills.fishing.Fish.* + +/** + * A spot that can be fished from. + */ +enum class FishingSpot(val npc: Int, private val first: Option, private val second: Option) { + + ROD( + npc = 309, + first = Option.of(tool = FishingTool.FLY_FISHING_ROD, primary = TROUT, secondary = SALMON), + second = Option.of(tool = FishingTool.FISHING_ROD, primary = PIKE) + ), + + CAGE_HARPOON( + npc = 312, + first = Option.of(tool = FishingTool.LOBSTER_CAGE, primary = LOBSTER), + second = Option.of(tool = FishingTool.HARPOON, primary = TUNA, secondary = SWORDFISH) + ), + + NET_HARPOON( + npc = 313, + first = Option.of(tool = FishingTool.BIG_NET, primary = MACKEREL, secondary = COD), + second = Option.of(tool = FishingTool.HARPOON, primary = BASS, secondary = SHARK) + ), + + NET_ROD( + npc = 316, + first = Option.of(tool = FishingTool.SMALL_NET, primary = SHRIMPS, secondary = ANCHOVIES), + second = Option.of(tool = FishingTool.FISHING_ROD, primary = SARDINE, secondary = HERRING) + ); + + /** + * Returns the [FishingSpot.Option] associated with the specified action id. + */ + fun option(action: Int): Option { + return when (action) { + 1 -> first + 3 -> second + else -> throw UnsupportedOperationException("Unexpected fishing spot option $action.") + } + } + + /** + * An option at a [FishingSpot] (e.g. either "rod fishing" or "net fishing"). + */ + sealed class Option { + + /** + * The tool used to obtain fish + */ + abstract val tool: FishingTool + + /** + * The minimum level required to obtain fish. + */ + abstract val level: Int + + /** + * Samples a [Fish], randomly (with weighting) returning one (that can be fished by the player). + * + * @param level The fishing level of the player. + */ + abstract fun sample(level: Int): Fish + + /** + * A [FishingSpot] [Option] that can only provide a single type of fish. + */ + private data class Single(override val tool: FishingTool, val primary: Fish) : Option() { + override val level = primary.level + + override fun sample(level: Int): Fish = primary + } + + /** + * A [FishingSpot] [Option] that can provide a two different types of fish. + */ + private data class Pair(override val tool: FishingTool, val primary: Fish, val secondary: Fish) : Option() { + override val level = Math.min(primary.level, secondary.level) + + override fun sample(level: Int): Fish { + return if (level < secondary.level || rand(100) < WEIGHTING) { + primary + } else { + secondary + } + } + + private companion object { + /** + * The weighting factor that causes the lower-level fish to be returned more frequently. + */ + private const val WEIGHTING = 70 + } + } + + companion object { + + fun of(tool: FishingTool, primary: Fish): Option = Single(tool, primary) + + fun of(tool: FishingTool, primary: Fish, secondary: Fish): Option { + return when { + primary.level < secondary.level -> Pair(tool, primary, secondary) + else -> Pair(tool, secondary, primary) + } + } + } + } + + companion object { + private val FISHING_SPOTS = FishingSpot.values().associateBy(FishingSpot::npc) + + /** + * Returns the [FishingSpot] with the specified [id], or `null` if the spot does not exist. + */ + fun lookup(id: Int): FishingSpot? = FISHING_SPOTS[id] + } +} \ No newline at end of file diff --git a/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/FishingTarget.kt b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/FishingTarget.kt new file mode 100644 index 000000000..bdcb5713a --- /dev/null +++ b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/FishingTarget.kt @@ -0,0 +1,38 @@ +package org.apollo.game.plugin.skills.fishing + +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.api.fishing +import org.apollo.game.plugin.api.rand +import org.apollo.game.plugin.skills.fishing.FishingAction.Companion.hasBait +import org.apollo.game.plugin.skills.fishing.FishingAction.Companion.hasTool + +data class FishingTarget(val position: Position, val option: FishingSpot.Option) { + + /** + * Returns whether or not the catch was successful. + * TODO: We need to identify the correct algorithm for this + */ + fun isSuccessful(player: Player, req: Int): Boolean { + return minOf(player.fishing.current - req + 5, 40) > rand(100) + } + + /** + * Verifies that the [Player] can gather fish from their chosen [FishingSpot.Option]. + */ + fun verify(player: Player): Boolean { + val current = player.fishing.current + val required = option.level + val tool = option.tool + + when { + current < required -> player.sendMessage("You need a fishing level of $required to fish at this spot.") + hasTool(player, tool) -> player.sendMessage("You need a ${tool.formattedName} to fish at this spot.") + hasBait(player, tool.bait) -> player.sendMessage("You need some ${tool.baitName} to fish at this spot.") + player.inventory.freeSlots() == 0 -> player.inventory.forceCapacityExceeded() + else -> return true + } + + return false + } +} \ No newline at end of file diff --git a/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/FishingTool.kt b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/FishingTool.kt new file mode 100644 index 000000000..e49122142 --- /dev/null +++ b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/FishingTool.kt @@ -0,0 +1,32 @@ +package org.apollo.game.plugin.skills.fishing + +import org.apollo.game.model.Animation +import org.apollo.game.plugin.api.Definitions + +/** + * A tool used to gather [Fish] from a [FishingSpot]. + */ +enum class FishingTool( + val message: String, + val id: Int, + animation: Int, + val bait: Int = -1, + val baitName: String? = null +) { + LOBSTER_CAGE("You attempt to catch a lobster...", id = 301, animation = 619), + SMALL_NET("You cast out your net...", id = 303, animation = 620), + BIG_NET("You cast out your net...", id = 305, animation = 620), + HARPOON("You start harpooning fish...", id = 311, animation = 618), + FISHING_ROD("You attempt to catch a fish...", id = 307, animation = 622, bait = 313, baitName = "feathers"), + FLY_FISHING_ROD("You attempt to catch a fish...", id = 309, animation = 622, bait = 314, baitName = "fishing bait"); + + /** + * The [Animation] played when fishing with this tool. + */ + val animation: Animation = Animation(animation) + + /** + * The name of this tool, formatted so it can be inserted into a message. + */ + val formattedName by lazy { Definitions.item(id).name.toLowerCase() } +} \ No newline at end of file diff --git a/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/Spots.plugin.kts b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/Spots.plugin.kts new file mode 100644 index 000000000..3d162459a --- /dev/null +++ b/game/plugin/skills/fishing/src/org/apollo/game/plugin/skills/fishing/Spots.plugin.kts @@ -0,0 +1,180 @@ +package org.apollo.game.plugin.skills.fishing +import org.apollo.game.model.Position +import org.apollo.game.plugin.entity.spawn.spawnNpc +import org.apollo.game.plugin.skills.fishing.FishingSpot.CAGE_HARPOON +import org.apollo.game.plugin.skills.fishing.FishingSpot.NET_HARPOON +import org.apollo.game.plugin.skills.fishing.FishingSpot.NET_ROD +import org.apollo.game.plugin.skills.fishing.FishingSpot.ROD + +// Al-Kharid +NET_ROD at Position(3267, 3148) +NET_ROD at Position(3268, 3147) +NET_ROD at Position(3277, 3139) +CAGE_HARPOON at Position(3350, 3817) +CAGE_HARPOON at Position(3347, 3814) +CAGE_HARPOON at Position(3363, 3816) +CAGE_HARPOON at Position(3368, 3811) + +// Ardougne +ROD at Position(2561, 3374) +ROD at Position(2562, 3374) +ROD at Position(2568, 3365) + +// Bandit camp +NET_ROD at Position(3047, 3703) +NET_ROD at Position(3045, 3702) + +// Baxtorian falls +ROD at Position(2527, 3412) +ROD at Position(2530, 3412) +ROD at Position(2533, 3410) + +// Burgh de Rott +NET_HARPOON at Position(3497, 3175) +NET_HARPOON at Position(3496, 3178) +NET_HARPOON at Position(3499, 3178) +NET_HARPOON at Position(3489, 3184) +NET_HARPOON at Position(3496, 3176) +NET_HARPOON at Position(3486, 3184) +NET_HARPOON at Position(3479, 3189) +NET_HARPOON at Position(3476, 3191) +NET_HARPOON at Position(3472, 3196) +NET_HARPOON at Position(3496, 3180) +NET_HARPOON at Position(3512, 3178) +NET_HARPOON at Position(3515, 3180) +NET_HARPOON at Position(3518, 3177) +NET_HARPOON at Position(3528, 3172) +NET_HARPOON at Position(3531, 3169) +NET_HARPOON at Position(3531, 3172) +NET_HARPOON at Position(3531, 3167) + +// Camelot +ROD at Position(2726, 3524) +ROD at Position(2727, 3524) + +// Castle wars +ROD at Position(2461, 3151) +ROD at Position(2461, 3150) +ROD at Position(2462, 3145) +ROD at Position(2472, 3156) + +// Catherby 1 +NET_ROD at Position(2838, 3431) +CAGE_HARPOON at Position(2837, 3431) +CAGE_HARPOON at Position(2836, 3431) +NET_ROD at Position(2846, 3429) +NET_ROD at Position(2844, 3429) +CAGE_HARPOON at Position(2845, 3429) +NET_HARPOON at Position(2853, 3423) +NET_HARPOON at Position(2855, 3423) +NET_HARPOON at Position(2859, 3426) + +// Draynor village +NET_ROD at Position(3085, 3230) +NET_ROD at Position(3085, 3231) +NET_ROD at Position(3086, 3227) + +// Elf camp +ROD at Position(2210, 3243) +ROD at Position(2216, 3236) +ROD at Position(2222, 3241) + +// Entrana +NET_ROD at Position(2843, 3359) +NET_ROD at Position(2842, 3359) +NET_ROD at Position(2847, 3361) +NET_ROD at Position(2848, 3361) +NET_ROD at Position(2840, 3356) +NET_ROD at Position(2845, 3356) +NET_ROD at Position(2875, 3342) +NET_ROD at Position(2876, 3342) +NET_ROD at Position(2877, 3342) + +// Fishing guild +CAGE_HARPOON at Position(2612, 3411) +CAGE_HARPOON at Position(2607, 3410) +NET_HARPOON at Position(2612, 3414) +NET_HARPOON at Position(2612, 3415) +NET_HARPOON at Position(2609, 3416) +CAGE_HARPOON at Position(2604, 3417) +NET_HARPOON at Position(2605, 3416) +NET_HARPOON at Position(2602, 3411) +NET_HARPOON at Position(2602, 3412) +CAGE_HARPOON at Position(2602, 3414) +NET_HARPOON at Position(2603, 3417) +NET_HARPOON at Position(2599, 3419) +NET_HARPOON at Position(2601, 3422) +NET_HARPOON at Position(2605, 3421) +CAGE_HARPOON at Position(2602, 3426) +NET_HARPOON at Position(2604, 3426) +CAGE_HARPOON at Position(2605, 3425) + +// Fishing platform +NET_ROD at Position(2791, 3279) +NET_ROD at Position(2795, 3279) +NET_ROD at Position(2790, 3273) + +// Grand Tree +ROD at Position(2393, 3419) +ROD at Position(2391, 3421) +ROD at Position(2389, 3423) +ROD at Position(2388, 3423) +ROD at Position(2385, 3422) +ROD at Position(2384, 3419) +ROD at Position(2383, 3417) + +// Karamja +NET_ROD at Position(2921, 3178) +CAGE_HARPOON at Position(2923, 3179) +CAGE_HARPOON at Position(2923, 3180) +NET_ROD at Position(2924, 3181) +NET_ROD at Position(2926, 3180) +CAGE_HARPOON at Position(2926, 3179) + +// Lumbridge +ROD at Position(3239, 3244) +NET_ROD at Position(3238, 3252) + +// Miscellenia +CAGE_HARPOON at Position(2580, 3851) +CAGE_HARPOON at Position(2581, 3851) +CAGE_HARPOON at Position(2582, 3851) +CAGE_HARPOON at Position(2583, 3852) +CAGE_HARPOON at Position(2583, 3853) + +// Rellekka +NET_ROD at Position(2633, 3691) +NET_ROD at Position(2633, 3689) +CAGE_HARPOON at Position(2639, 3698) +CAGE_HARPOON at Position(2639, 3697) +CAGE_HARPOON at Position(2639, 3695) +NET_HARPOON at Position(2642, 3694) +NET_HARPOON at Position(2642, 3697) +NET_HARPOON at Position(2644, 3709) + +// Rimmington +NET_ROD at Position(2990, 3169) +NET_ROD at Position(2986, 3176) + +// Shilo Village +ROD at Position(2855, 2974) +ROD at Position(2865, 2972) +ROD at Position(2860, 2972) +ROD at Position(2835, 2974) +ROD at Position(2859, 2976) + +// Tirannwn +ROD at Position(2266, 3253) +ROD at Position(2265, 3258) +ROD at Position(2264, 3258) + +// Tutorial island +NET_ROD at Position(3101, 3092) +NET_ROD at Position(3103, 3092) + +/** + * Registers the [FishingSpot] at the specified position. + */ +infix fun FishingSpot.at(position: Position) { + spawnNpc("", position, id = npc) +} \ No newline at end of file diff --git a/game/plugin/skills/fishing/test/org/apollo/game/plugin/skills/fishing/FishingActionTests.kt b/game/plugin/skills/fishing/test/org/apollo/game/plugin/skills/fishing/FishingActionTests.kt new file mode 100644 index 000000000..42fd6cbb6 --- /dev/null +++ b/game/plugin/skills/fishing/test/org/apollo/game/plugin/skills/fishing/FishingActionTests.kt @@ -0,0 +1,92 @@ +package org.apollo.game.plugin.skills.fishing + +import io.mockk.every +import io.mockk.spyk +import io.mockk.verify +import org.apollo.cache.def.ItemDefinition +import org.apollo.cache.def.NpcDefinition +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.plugin.testing.assertions.after +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.assertions.startsWith +import org.apollo.game.plugin.testing.assertions.verifyAfter +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.NpcDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.apollo.game.plugin.testing.junit.api.interactions.spawnNpc +import org.apollo.game.plugin.testing.junit.api.interactions.spawnObject +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class FishingActionTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var player: Player + + @TestMock + lateinit var action: ActionCapture + + @Test + fun `Attempting to fish at a spot we don't have the skill to should send the player a message`() { + val obj = world.spawnObject(1, player.position) + + val option = spyk(FishingSpot.CAGE_HARPOON.option(1)) + val target = FishingTarget(obj.position, option) + + player.startAction(FishingAction(player, target)) + + every { option.level } returns Int.MAX_VALUE + + verifyAfter(action.complete()) { + player.sendMessage(contains("need a fishing level of ${Int.MAX_VALUE}")) + } + } + + @Test + fun `Fishing at a spot we have the skill to should eventually reward fish and experience`() { + val option = spyk(FishingSpot.CAGE_HARPOON.option(1)) + val obj = world.spawnNpc(FishingSpot.CAGE_HARPOON.npc, player.position) + + val target = spyk(FishingTarget(obj.position, option)) + every { target.isSuccessful(player, any()) } returns true + every { target.verify(player) } returns true + + player.skillSet.setCurrentLevel(Skill.FISHING, option.level) + player.startAction(FishingAction(player, target)) + + verifyAfter(action.ticks(1)) { + player.sendMessage(startsWith("You attempt to catch a lobster")) + } + + after(action.ticks(4)) { + verify { player.sendMessage(startsWith("You catch a .")) } + + assertTrue(player.inventory.contains(Fish.LOBSTER.id)) + assertEquals(player.skillSet.getExperience(Skill.FISHING), Fish.LOBSTER.experience) + } + } + + private companion object { + @ItemDefinitions + private val fish = Fish.values() + .map { ItemDefinition(it.id).apply { name = "" } } + + @ItemDefinitions + private val tools = FishingTool.values() + .map { ItemDefinition(it.id).apply { name = "" } } + + @NpcDefinitions + private val spots = FishingSpot.values() + .map { NpcDefinition(it.npc).apply { name = "" } } + } +} \ No newline at end of file diff --git a/game/plugin/skills/herblore/build.gradle b/game/plugin/skills/herblore/build.gradle new file mode 100644 index 000000000..4d96c9f57 --- /dev/null +++ b/game/plugin/skills/herblore/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:api') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/skills/herblore/src/CrushIngredientAction.kt b/game/plugin/skills/herblore/src/CrushIngredientAction.kt new file mode 100644 index 000000000..6c519431b --- /dev/null +++ b/game/plugin/skills/herblore/src/CrushIngredientAction.kt @@ -0,0 +1,39 @@ +import java.util.Objects +import org.apollo.game.action.ActionBlock +import org.apollo.game.action.AsyncAction +import org.apollo.game.model.Animation +import org.apollo.game.model.entity.Player + +class CrushIngredientAction( + player: Player, + private val ingredient: CrushableIngredient +) : AsyncAction(0, true, player) { + + override fun action(): ActionBlock = { + mob.playAnimation(GRINDING_ANIM) + wait(pulses = 1) + + val inventory = mob.inventory + if (inventory.remove(ingredient.uncrushed)) { + inventory.add(ingredient.id) + + mob.sendMessage("You carefully grind the ${ingredient.uncrushedName} to dust.") + } + + stop() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CrushIngredientAction + return mob == other.mob && ingredient == other.ingredient + } + + override fun hashCode(): Int = Objects.hash(mob, ingredient) + + private companion object { + private val GRINDING_ANIM = Animation(364) + } +} \ No newline at end of file diff --git a/game/plugin/skills/herblore/src/Herb.kt b/game/plugin/skills/herblore/src/Herb.kt new file mode 100644 index 000000000..5b921f204 --- /dev/null +++ b/game/plugin/skills/herblore/src/Herb.kt @@ -0,0 +1,34 @@ +import org.apollo.game.plugin.api.Definitions + +enum class Herb( + val identified: Int, + val unidentified: Int, + val level: Int, + val experience: Double +) { + GUAM_LEAF(identified = 249, unidentified = 199, level = 1, experience = 2.5), + MARRENTILL(identified = 251, unidentified = 201, level = 5, experience = 3.8), + TARROMIN(identified = 253, unidentified = 203, level = 11, experience = 5.0), + HARRALANDER(identified = 255, unidentified = 205, level = 20, experience = 6.3), + RANARR(identified = 257, unidentified = 207, level = 25, experience = 7.5), + TOADFLAX(identified = 2998, unidentified = 2998, level = 30, experience = 8.0), + IRIT_LEAF(identified = 259, unidentified = 209, level = 40, experience = 8.8), + AVANTOE(identified = 261, unidentified = 211, level = 48, experience = 10.0), + KWUARM(identified = 263, unidentified = 213, level = 54, experience = 11.3), + SNAPDRAGON(identified = 3000, unidentified = 3051, level = 59, experience = 11.8), + CADANTINE(identified = 265, unidentified = 215, level = 65, experience = 12.5), + LANTADYME(identified = 2481, unidentified = 2485, level = 67, experience = 13.1), + DWARF_WEED(identified = 267, unidentified = 217, level = 70, experience = 13.8), + TORSTOL(identified = 269, unidentified = 219, level = 75, experience = 15.0); + + val identifiedName by lazy { Definitions.item(identified)!!.name } + + companion object { + private val identified = Herb.values().map(Herb::identified).toHashSet() + private val herbs = Herb.values().associateBy(Herb::unidentified) + + operator fun get(id: Int): Herb? = herbs[id] + internal fun Int.isUnidentified(): Boolean = this in herbs + internal fun Int.isIdentified(): Boolean = this in identified + } +} \ No newline at end of file diff --git a/game/plugin/skills/herblore/src/Herblore.plugin.kts b/game/plugin/skills/herblore/src/Herblore.plugin.kts new file mode 100644 index 000000000..f575a6532 --- /dev/null +++ b/game/plugin/skills/herblore/src/Herblore.plugin.kts @@ -0,0 +1,48 @@ + +import CrushableIngredient.Companion.isCrushable +import CrushableIngredient.Companion.isGrindingTool +import Herb.Companion.isIdentified +import Herb.Companion.isUnidentified +import Ingredient.Companion.isIngredient +import UnfinishedPotion.Companion.isUnfinished +import org.apollo.game.message.impl.ItemOnItemMessage +import org.apollo.game.message.impl.ItemOptionMessage + +on { ItemOptionMessage::class } + .where { option == IdentifyHerbAction.IDENTIFY_OPTION && id.isUnidentified() } + .then { player -> + val unidentified = Herb[id]!! + + player.startAction(IdentifyHerbAction(player, slot, unidentified)) + terminate() + } + +on { ItemOnItemMessage::class } + .where { (id.isGrindingTool() && targetId.isCrushable() || id.isCrushable() && targetId.isGrindingTool()) } + .then { player -> + val crushableId = if (id.isCrushable()) id else targetId + val raw = CrushableIngredient[crushableId]!! + + player.startAction(CrushIngredientAction(player, raw)) + terminate() + } + +on { ItemOnItemMessage::class } + .where { id == VIAL_OF_WATER && targetId.isIdentified() || id.isIdentified() && targetId == VIAL_OF_WATER } + .then { player -> + val herbId = if (id.isIdentified()) id else targetId + val unfinished = UnfinishedPotion[herbId]!! + + player.startAction(MakeUnfinishedPotionAction(player, unfinished)) + terminate() + } + +on { ItemOnItemMessage::class } + .where { id.isUnfinished() && targetId.isIngredient() || id.isIngredient() && targetId.isUnfinished() } + .then { player -> + val key = if (id.isUnfinished()) Pair(id, targetId) else Pair(targetId, id) + val finished = FinishedPotion[key]!! + + player.startAction(MakeFinishedPotionAction(player, finished)) + terminate() + } \ No newline at end of file diff --git a/game/plugin/skills/herblore/src/IdentifyHerbAction.kt b/game/plugin/skills/herblore/src/IdentifyHerbAction.kt new file mode 100644 index 000000000..bd9bd4b75 --- /dev/null +++ b/game/plugin/skills/herblore/src/IdentifyHerbAction.kt @@ -0,0 +1,49 @@ + +import java.util.Objects +import org.apollo.game.action.ActionBlock +import org.apollo.game.action.AsyncAction +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.api.herblore +import org.apollo.util.LanguageUtil + +class IdentifyHerbAction( + player: Player, + private val slot: Int, + private val herb: Herb +) : AsyncAction(0, true, player) { + + override fun action(): ActionBlock = { + if (mob.herblore.current < herb.level) { + mob.sendMessage("You need a Herblore level of ${herb.level} to clean this herb.") + stop() + } + + val inventory = mob.inventory + + if (inventory.removeSlot(slot, 1) > 0) { + inventory.add(herb.identified) + mob.herblore.experience += herb.experience + + val name = herb.identifiedName + val article = LanguageUtil.getIndefiniteArticle(name) + + mob.sendMessage("You identify the herb as $article $name.") + } + + stop() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as IdentifyHerbAction + return mob == other.mob && herb == other.herb && slot == other.slot + } + + override fun hashCode(): Int = Objects.hash(mob, herb, slot) + + companion object { + const val IDENTIFY_OPTION = 1 + } +} \ No newline at end of file diff --git a/game/plugin/skills/herblore/src/Ingredient.kt b/game/plugin/skills/herblore/src/Ingredient.kt new file mode 100644 index 000000000..475436945 --- /dev/null +++ b/game/plugin/skills/herblore/src/Ingredient.kt @@ -0,0 +1,60 @@ +import CrushableIngredient.Companion.isCrushable +import NormalIngredient.Companion.isNormalIngredient +import org.apollo.game.plugin.api.Definitions + +/** + * A secondary ingredient in a potion. + */ +interface Ingredient { + val id: Int + + companion object { + internal fun Int.isIngredient(): Boolean = isNormalIngredient() || isCrushable() + } +} + +enum class CrushableIngredient(val uncrushed: Int, override val id: Int) : Ingredient { + UNICORN_HORN(uncrushed = 237, id = 235), + DRAGON_SCALE(uncrushed = 243, id = 241), + CHOCOLATE_BAR(uncrushed = 1973, id = 1975), + BIRDS_NEST(uncrushed = 5075, id = 6693); + + val uncrushedName by lazy { Definitions.item(uncrushed)!!.name } + + companion object { + private const val PESTLE_AND_MORTAR = 233 + private const val KNIFE = 5605 + + private val ingredients = CrushableIngredient.values().associateBy(CrushableIngredient::uncrushed) + operator fun get(id: Int): CrushableIngredient? = ingredients[id] + + internal fun Int.isCrushable(): Boolean = this in ingredients + internal fun Int.isGrindingTool(): Boolean = this == KNIFE || this == PESTLE_AND_MORTAR + } +} + +enum class NormalIngredient(override val id: Int) : Ingredient { + EYE_NEWT(id = 221), + RED_SPIDERS_EGGS(id = 223), + LIMPWURT_ROOT(id = 225), + SNAPE_GRASS(id = 231), + WHITE_BERRIES(id = 239), + WINE_ZAMORAK(id = 245), + JANGERBERRIES(id = 247), + TOADS_LEGS(id = 2152), + MORT_MYRE_FUNGI(id = 2970), + POTATO_CACTUS(id = 3138), + PHOENIX_FEATHER(id = 4621), + FROG_SPAWN(id = 5004), + PAPAYA_FRUIT(id = 5972), + POISON_IVY_BERRIES(id = 6018), + YEW_ROOTS(id = 6049), + MAGIC_ROOTS(id = 6051); + + companion object { + private val ingredients = NormalIngredient.values().associateBy(NormalIngredient::id) + operator fun get(id: Int): NormalIngredient? = ingredients[id] + + internal fun Int.isNormalIngredient(): Boolean = this in ingredients + } +} \ No newline at end of file diff --git a/game/plugin/skills/herblore/src/MakePotionAction.kt b/game/plugin/skills/herblore/src/MakePotionAction.kt new file mode 100644 index 000000000..19f5ef94c --- /dev/null +++ b/game/plugin/skills/herblore/src/MakePotionAction.kt @@ -0,0 +1,74 @@ +import java.util.* +import org.apollo.game.action.ActionBlock +import org.apollo.game.action.AsyncAction +import org.apollo.game.model.Animation +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.plugin.api.herblore + +abstract class MakePotionAction( + player: Player, + private val potion: Potion +) : AsyncAction(1, true, player) { + + override fun action(): ActionBlock = { + val level = mob.herblore.current + + if (level < potion.level) { + mob.sendMessage("You need a Herblore level of ${potion.level} to make this.") + stop() + } + + val inventory = mob.inventory + + if (inventory.containsAll(*ingredients)) { + ingredients.forEach { inventory.remove(it) } + inventory.add(potion.id) + + mob.playAnimation(MIXING_ANIMATION) + mob.sendMessage(message) + reward() + } + } + + abstract val ingredients: IntArray + abstract val message: String + + open fun reward() {} + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MakePotionAction + return mob == other.mob && potion == other.potion + } + + override fun hashCode(): Int = Objects.hash(mob, potion) + + private companion object { + private val MIXING_ANIMATION = Animation(363) + } +} + +class MakeFinishedPotionAction( + player: Player, + private val potion: FinishedPotion +) : MakePotionAction(player, potion) { + + override val ingredients = intArrayOf(potion.unfinished.id, potion.ingredient) + override val message by lazy { "You mix the ${potion.ingredientName} into your potion." } + + override fun reward() { + mob.skillSet.addExperience(Skill.HERBLORE, potion.experience) + } +} + +class MakeUnfinishedPotionAction( + player: Player, + private val potion: UnfinishedPotion +) : MakePotionAction(player, potion) { + + override val ingredients = intArrayOf(VIAL_OF_WATER, potion.herb) + override val message by lazy { "You put the ${potion.herbName} into the vial of water." } +} \ No newline at end of file diff --git a/game/plugin/skills/herblore/src/Potion.kt b/game/plugin/skills/herblore/src/Potion.kt new file mode 100644 index 000000000..ce9053ed5 --- /dev/null +++ b/game/plugin/skills/herblore/src/Potion.kt @@ -0,0 +1,78 @@ +import CrushableIngredient.CHOCOLATE_BAR +import CrushableIngredient.DRAGON_SCALE +import CrushableIngredient.UNICORN_HORN +import NormalIngredient.* +import UnfinishedPotion.* +import org.apollo.game.plugin.api.Definitions + +const val VIAL_OF_WATER = 227 + +interface Potion { + val id: Int + val level: Int +} + +enum class UnfinishedPotion(override val id: Int, herb: Herb, override val level: Int) : Potion { + GUAM(id = 91, herb = Herb.GUAM_LEAF, level = 1), + MARRENTILL(id = 93, herb = Herb.MARRENTILL, level = 5), + TARROMIN(id = 95, herb = Herb.TARROMIN, level = 12), + HARRALANDER(id = 97, herb = Herb.HARRALANDER, level = 22), + RANARR(id = 99, herb = Herb.RANARR, level = 30), + TOADFLAX(id = 3002, herb = Herb.TOADFLAX, level = 34), + IRIT(id = 101, herb = Herb.IRIT_LEAF, level = 45), + AVANTOE(id = 103, herb = Herb.AVANTOE, level = 50), + KWUARM(id = 105, herb = Herb.KWUARM, level = 55), + SNAPDRAGON(id = 3004, herb = Herb.SNAPDRAGON, level = 63), + CADANTINE(id = 107, herb = Herb.CADANTINE, level = 66), + LANTADYME(id = 2483, herb = Herb.LANTADYME, level = 69), + DWARF_WEED(id = 109, herb = Herb.DWARF_WEED, level = 72), + TORSTOL(id = 111, herb = Herb.TORSTOL, level = 78); + + val herb = herb.identified + val herbName: String = Definitions.item(herb.identified)!!.name + + companion object { + private val ids = values().map(UnfinishedPotion::id).toHashSet() + private val potions = values().associateBy(UnfinishedPotion::herb) + + operator fun get(id: Int) = potions[id] + internal fun Int.isUnfinished(): Boolean = this in ids + } +} + +enum class FinishedPotion( + override val id: Int, + val unfinished: UnfinishedPotion, + ingredient: Ingredient, + override val level: Int, + val experience: Double +) : Potion { + ATTACK(id = 121, unfinished = GUAM, ingredient = EYE_NEWT, level = 1, experience = 25.0), + ANTIPOISON(id = 175, unfinished = MARRENTILL, ingredient = UNICORN_HORN, level = 5, experience = 37.5), + STRENGTH(id = 115, unfinished = TARROMIN, ingredient = LIMPWURT_ROOT, level = 12, experience = 50.0), + RESTORE(id = 127, unfinished = HARRALANDER, ingredient = RED_SPIDERS_EGGS, level = 18, experience = 62.5), + ENERGY(id = 3010, unfinished = HARRALANDER, ingredient = CHOCOLATE_BAR, level = 26, experience = 67.5), + DEFENCE(id = 133, unfinished = RANARR, ingredient = WHITE_BERRIES, level = 30, experience = 75.0), + AGILITY(id = 3034, unfinished = TOADFLAX, ingredient = TOADS_LEGS, level = 34, experience = 80.0), + PRAYER(id = 139, unfinished = RANARR, ingredient = SNAPE_GRASS, level = 38, experience = 87.5), + SUPER_ATTACK(id = 145, unfinished = IRIT, ingredient = EYE_NEWT, level = 45, experience = 100.0), + SUPER_ANTIPOISON(id = 181, unfinished = IRIT, ingredient = UNICORN_HORN, level = 48, experience = 106.3), + FISHING(id = 151, unfinished = AVANTOE, ingredient = SNAPE_GRASS, level = 50, experience = 112.5), + SUPER_ENERGY(id = 3018, unfinished = AVANTOE, ingredient = MORT_MYRE_FUNGI, level = 52, experience = 117.5), + SUPER_STRENGTH(id = 157, unfinished = KWUARM, ingredient = LIMPWURT_ROOT, level = 55, experience = 125.0), + WEAPON_POISON(id = 187, unfinished = KWUARM, ingredient = DRAGON_SCALE, level = 60, experience = 137.5), + SUPER_RESTORE(id = 3026, unfinished = SNAPDRAGON, ingredient = RED_SPIDERS_EGGS, level = 63, experience = 142.5), + SUPER_DEFENCE(id = 163, unfinished = CADANTINE, ingredient = WHITE_BERRIES, level = 66, experience = 150.0), + ANTIFIRE(id = 2428, unfinished = LANTADYME, ingredient = DRAGON_SCALE, level = 69, experience = 157.5), + RANGING(id = 169, unfinished = DWARF_WEED, ingredient = WINE_ZAMORAK, level = 72, experience = 162.5), + MAGIC(id = 3042, unfinished = LANTADYME, ingredient = POTATO_CACTUS, level = 76, experience = 172.5), + ZAMORAK_BREW(id = 189, unfinished = TORSTOL, ingredient = JANGERBERRIES, level = 78, experience = 175.0); + + val ingredientName = Definitions.item(ingredient.id)!!.name.toLowerCase() + val ingredient = ingredient.id + + companion object { + private val potions = FinishedPotion.values().associateBy { Pair(it.unfinished.id, it.ingredient) } + operator fun get(key: Pair) = potions[key] + } +} diff --git a/game/plugin/skills/herblore/test/CrushIngredientActionTests.kt b/game/plugin/skills/herblore/test/CrushIngredientActionTests.kt new file mode 100644 index 000000000..a81154a6d --- /dev/null +++ b/game/plugin/skills/herblore/test/CrushIngredientActionTests.kt @@ -0,0 +1,63 @@ +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.testing.assertions.after +import org.apollo.game.plugin.testing.assertions.startsWith +import org.apollo.game.plugin.testing.assertions.verifyAfter +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +internal class CrushIngredientActionTests { + + @TestMock + lateinit var player: Player + + @TestMock + lateinit var action: ActionCapture + + private val ingredient = CrushableIngredient.BIRDS_NEST + + @BeforeEach + internal fun startAction() { + player.inventory.add(ingredient.uncrushed) + player.startAction(CrushIngredientAction(player, ingredient)) + } + + @Test + internal fun `Preparing an uncrushed ingredient rewards a new ingredient after 2 ticks`() { + after(action.ticks(2), "ingredient removed and new ingredient added") { + assertEquals(0, player.inventory.getAmount(ingredient.uncrushed)) + assertEquals(1, player.inventory.getAmount(ingredient.id)) + } + } + + @Test + internal fun `Preparing an uncrushed ingredient should send a message to the player after 2 ticks`() { + verifyAfter(action.ticks(2), "notification message sent to the player") { + player.sendMessage(startsWith("You carefully grind the to dust")) + } + } + + @Test + internal fun `Preparing an uncrushed ingredient should play an animation on the first tick`() { + verifyAfter(action.ticks(1), "grinding animation played") { + player.playAnimation(match { it.id == 364 }) + } + } + + private companion object { + @ItemDefinitions + private val ingredients = CrushableIngredient.values() + .map { ItemDefinition(it.uncrushed).apply { name = "" } } + + @ItemDefinitions + private val prepared = CrushableIngredient.values() + .map { ItemDefinition(it.id).apply { name = "" } } + } +} \ No newline at end of file diff --git a/game/plugin/skills/herblore/test/IdentifyHerbActionTests.kt b/game/plugin/skills/herblore/test/IdentifyHerbActionTests.kt new file mode 100644 index 000000000..b5273c184 --- /dev/null +++ b/game/plugin/skills/herblore/test/IdentifyHerbActionTests.kt @@ -0,0 +1,66 @@ +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.Item +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.plugin.testing.assertions.after +import org.apollo.game.plugin.testing.assertions.startsWith +import org.apollo.game.plugin.testing.assertions.verifyAfter +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +internal class IdentifyHerbActionTests { + + @TestMock + lateinit var player: Player + + @TestMock + lateinit var action: ActionCapture + + private val herb = Herb.GUAM_LEAF + + @BeforeEach + internal fun startAction() { + player.inventory.set(0, Item(herb.unidentified)) + player.startAction(IdentifyHerbAction(player, 0, herb)) + } + + @Test + internal fun `Identifying a herb should send a message if the player doesnt have the required level`() { + player.skillSet.setCurrentLevel(Skill.HERBLORE, 0) + + verifyAfter(action.complete(), "level requirement message sent to player") { + player.sendMessage(startsWith("You need a Herblore level of")) + } + } + + @Test + internal fun `Identifying a herb should remove the undentified herb`() { + after(action.complete()) { + assertEquals(0, player.inventory.getAmount(herb.unidentified)) + } + } + + @Test + internal fun `Identifying a herb should add the identified herb to the players inventory`() { + after(action.complete()) { + assertEquals(1, player.inventory.getAmount(herb.identified)) + } + } + + private companion object { + @ItemDefinitions + val identifiedHerbs = Herb.values() + .map { ItemDefinition(it.identified).apply { name = "" } } + + @ItemDefinitions + val unidentifiedHerbs = Herb.values() + .map { ItemDefinition(it.unidentified).apply { name = "" } } + } +} \ No newline at end of file diff --git a/game/plugin/skills/mining/build.gradle b/game/plugin/skills/mining/build.gradle new file mode 100644 index 000000000..4d96c9f57 --- /dev/null +++ b/game/plugin/skills/mining/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:api') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/skills/mining/src/ExpiredProspectingAction.kt b/game/plugin/skills/mining/src/ExpiredProspectingAction.kt new file mode 100644 index 000000000..41f3229e6 --- /dev/null +++ b/game/plugin/skills/mining/src/ExpiredProspectingAction.kt @@ -0,0 +1,41 @@ +import java.util.* +import org.apollo.game.action.DistancedAction +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player + +class ExpiredProspectingAction( + mob: Player, + position: Position +) : DistancedAction(DELAY, true, mob, position, ORE_SIZE) { + + companion object { + private const val DELAY = 0 + private const val ORE_SIZE = 1 + + /** + * Starts a [ExpiredProspectingAction] for the specified [Player], terminating the [Message] that triggered it. + */ + fun start(message: ObjectActionMessage, player: Player) { + val action = ExpiredProspectingAction(player, message.position) + player.startAction(action) + + message.terminate() + } + } + + override fun executeAction() { + mob.sendMessage("There is currently no ore available in this rock.") + stop() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ExpiredProspectingAction + return mob == other.mob && position == other.position + } + + override fun hashCode(): Int = Objects.hash(mob, position) +} \ No newline at end of file diff --git a/game/plugin/skills/mining/src/Gem.kt b/game/plugin/skills/mining/src/Gem.kt new file mode 100644 index 000000000..2252ea4e4 --- /dev/null +++ b/game/plugin/skills/mining/src/Gem.kt @@ -0,0 +1,13 @@ +package org.apollo.game.plugin.skills.mining + +enum class Gem(val id: Int) { // TODO add gem drop chances + UNCUT_SAPPHIRE(1623), + UNCUT_EMERALD(1605), + UNCUT_RUBY(1619), + UNCUT_DIAMOND(1617); + + companion object { + private val GEMS = Gem.values().associateBy({ it.id }, { it }) + operator fun get(id: Int): Gem? = GEMS[id] + } +} \ No newline at end of file diff --git a/game/plugin/skills/mining/src/Mining.plugin.kts b/game/plugin/skills/mining/src/Mining.plugin.kts new file mode 100644 index 000000000..8b3f61d1e --- /dev/null +++ b/game/plugin/skills/mining/src/Mining.plugin.kts @@ -0,0 +1,27 @@ +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.game.plugin.skills.mining.Ore + +on { ObjectActionMessage::class } + .where { option == Actions.MINING } + .then { player -> + Ore.fromRock(id)?.let { ore -> + MiningAction.start(this, player, ore) + } + } + +on { ObjectActionMessage::class } + .where { option == Actions.PROSPECTING } + .then { player -> + val ore = Ore.fromRock(id) + + if (ore != null) { + ProspectingAction.start(this, player, ore) + } else if (Ore.fromExpiredRock(id) != null) { + ExpiredProspectingAction.start(this, player) + } + } + +private object Actions { + const val MINING = 1 + const val PROSPECTING = 2 +} \ No newline at end of file diff --git a/game/plugin/skills/mining/src/MiningAction.kt b/game/plugin/skills/mining/src/MiningAction.kt new file mode 100644 index 000000000..0f159b388 --- /dev/null +++ b/game/plugin/skills/mining/src/MiningAction.kt @@ -0,0 +1,83 @@ +import java.util.* +import org.apollo.game.action.ActionBlock +import org.apollo.game.action.AsyncDistancedAction +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.api.* +import org.apollo.game.plugin.skills.mining.Ore +import org.apollo.game.plugin.skills.mining.Pickaxe +import org.apollo.net.message.Message + +class MiningAction( + player: Player, + private val tool: Pickaxe, + private val target: MiningTarget +) : AsyncDistancedAction(PULSES, true, player, target.position, ORE_SIZE) { + + companion object { + private const val PULSES = 0 + private const val ORE_SIZE = 1 + + /** + * Starts a [MiningAction] for the specified [Player], terminating the [Message] that triggered it. + */ + fun start(message: ObjectActionMessage, player: Player, ore: Ore) { + val pickaxe = Pickaxe.bestFor(player) + + if (pickaxe == null) { + player.sendMessage("You do not have a pickaxe for which you have the level to use.") + } else { + val target = MiningTarget(message.id, message.position, ore) + val action = MiningAction(player, pickaxe, target) + + player.startAction(action) + } + + message.terminate() + } + } + + override fun action(): ActionBlock = { + mob.turnTo(position) + + if (!target.skillRequirementsMet(mob)) { + mob.sendMessage("You do not have the required level to mine this rock.") + stop() + } + + mob.sendMessage("You swing your pick at the rock.") + mob.playAnimation(tool.animation) + + wait(tool.pulses) + + if (!target.isValid(mob.world)) { + stop() + } + + val successChance = rand(100) + + if (target.isSuccessful(mob, successChance)) { + if (mob.inventory.freeSlots() == 0) { + mob.inventory.forceCapacityExceeded() + stop() + } + + if (target.reward(mob)) { + mob.sendMessage("You manage to mine some ${target.oreName()}") + target.deplete(mob.world) + + stop() + } + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MiningAction + return mob == other.mob && target == other.target + } + + override fun hashCode(): Int = Objects.hash(mob, target) +} diff --git a/game/plugin/skills/mining/src/MiningTarget.kt b/game/plugin/skills/mining/src/MiningTarget.kt new file mode 100644 index 000000000..5bbe1b4a1 --- /dev/null +++ b/game/plugin/skills/mining/src/MiningTarget.kt @@ -0,0 +1,58 @@ +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.api.Definitions +import org.apollo.game.plugin.api.findObject +import org.apollo.game.plugin.api.mining +import org.apollo.game.plugin.api.replaceObject +import org.apollo.game.plugin.skills.mining.Ore + +data class MiningTarget(val objectId: Int, val position: Position, val ore: Ore) { + + /** + * Deplete this mining resource from the [World], and schedule it to be respawned + * in a number of ticks specified by the [Ore]. + */ + fun deplete(world: World) { + val obj = world.findObject(position, objectId)!! + + world.replaceObject(obj, ore.objects[objectId]!!, ore.respawn) + } + + /** + * Check if the [Player] was successful in mining this ore with a random success [chance] value between 0 and 100. + */ + fun isSuccessful(mob: Player, chance: Int): Boolean { + val percent = (ore.chance * mob.mining.current + ore.chanceOffset) * 100 + return chance < percent + } + + /** + * Check if this target is still valid in the [World] (i.e. has not been [deplete]d). + */ + fun isValid(world: World) = world.findObject(position, objectId) != null + + /** + * Get the normalized name of the [Ore] represented by this target. + */ + fun oreName() = Definitions.item(ore.id).name.toLowerCase() + + /** + * Reward a [player] with experience and ore if they have the inventory capacity to take a new ore. + */ + fun reward(player: Player): Boolean { + val hasInventorySpace = player.inventory.add(ore.id) + + if (hasInventorySpace) { + player.mining.experience += ore.exp + } + + return hasInventorySpace + } + + /** + * Check if the [mob] has met the skill requirements to mine te [Ore] represented by + * this [MiningTarget]. + */ + fun skillRequirementsMet(mob: Player) = mob.mining.current < ore.level +} \ No newline at end of file diff --git a/game/plugin/skills/mining/src/Ore.kt b/game/plugin/skills/mining/src/Ore.kt new file mode 100644 index 000000000..1f4a94ff9 --- /dev/null +++ b/game/plugin/skills/mining/src/Ore.kt @@ -0,0 +1,153 @@ +package org.apollo.game.plugin.skills.mining + +/* +Thanks to Mikey` for helping +to find some of the item/object IDs, minimum levels and experiences. +Thanks to Clifton for helping +to find some of the expired object IDs. + */ +/** + * Chance values thanks to: http://runescape.wikia.com/wiki/Talk:Mining#Mining_success_rate_formula + * Respawn times and xp thanks to: http://oldschoolrunescape.wikia.com/wiki/ + */ +enum class Ore( + val objects: Map, + val id: Int, + val level: Int, + val exp: Double, + val respawn: Int, + val chance: Double, + val chanceOffset: Double = 0.0 +) { + CLAY(CLAY_OBJECTS, id = 434, level = 1, exp = 5.0, respawn = 1, chance = 0.0085, chanceOffset = 0.45), + COPPER(COPPER_OBJECTS, id = 436, level = 1, exp = 17.5, respawn = 4, chance = 0.0085, chanceOffset = 0.45), + TIN(TIN_OBJECTS, id = 438, level = 1, exp = 17.5, respawn = 4, chance = 0.0085, chanceOffset = 0.45), + IRON(IRON_OBJECTS, id = 440, level = 15, exp = 35.0, respawn = 9, chance = 0.0085, chanceOffset = 0.45), + COAL(COAL_OBJECTS, id = 453, level = 30, exp = 50.0, respawn = 50, chance = 0.004), + GOLD(GOLD_OBJECTS, id = 444, level = 40, exp = 65.0, respawn = 100, chance = 0.003), + SILVER(SILVER_OBJECTS, id = 442, level = 20, exp = 40.0, respawn = 100, chance = 0.0085), + MITHRIL(MITHRIL_OBJECTS, id = 447, level = 55, exp = 80.0, respawn = 200, chance = 0.002), + ADAMANT(ADAMANT_OBJECTS, id = 449, level = 70, exp = 95.0, respawn = 800, chance = 0.001), + RUNITE(RUNITE_OBJECTS, id = 451, level = 85, exp = 125.0, respawn = 1_200, chance = 0.0008); + + companion object { + private val ORE_ROCKS = Ore.values().flatMap { ore -> ore.objects.map { Pair(it.key, ore) } }.toMap() + private val EXPIRED_ORE = Ore.values().flatMap { ore -> ore.objects.map { Pair(it.value, ore) } }.toMap() + + fun fromRock(id: Int): Ore? = ORE_ROCKS[id] + fun fromExpiredRock(id: Int): Ore? = EXPIRED_ORE[id] + } +} + +// Maps from regular rock id to expired rock id. + +val CLAY_OBJECTS = mapOf( + 2108 to 450, + 2109 to 451, + 14904 to 14896, + 14905 to 14897 +) + +val COPPER_OBJECTS = mapOf( + 11960 to 11555, + 11961 to 11556, + 11962 to 11557, + 11936 to 11552, + 11937 to 11553, + 11938 to 11554, + 2090 to 450, + 2091 to 451, + 14906 to 14898, + 14907 to 14899, + 14856 to 14832, + 14857 to 14833, + 14858 to 14834 +) + +val TIN_OBJECTS = mapOf( + 11597 to 11555, + 11958 to 11556, + 11959 to 11557, + 11933 to 11552, + 11934 to 11553, + 11935 to 11554, + 2094 to 450, + 2095 to 451, + 14092 to 14894, + 14903 to 14895 +) + +val IRON_OBJECTS = mapOf( + 11954 to 11555, + 11955 to 11556, + 11956 to 11557, + 2092 to 450, + 2093 to 451, + 14900 to 14892, + 14901 to 14893, + 14913 to 14915, + 14914 to 14916 +) + +val COAL_OBJECTS = mapOf( + 11963 to 11555, + 11964 to 11556, + 11965 to 11557, + 11930 to 11552, + 11931 to 11553, + 11932 to 11554, + 2096 to 450, + 2097 to 451, + 14850 to 14832, + 14851 to 14833, + 14852 to 14834 +) + +val SILVER_OBJECTS = mapOf( + 11948 to 11555, + 11949 to 11556, + 11950 to 11557, + 2100 to 450, + 2101 to 451 +) + +val GOLD_OBJECTS = mapOf( + 11951 to 11555, + 11952 to 11556, + 11953 to 11557, + 2098 to 450, + 2099 to 451 +) + +val MITHRIL_OBJECTS = mapOf( + 11945 to 11555, + 11946 to 11556, + 11947 to 11557, + 11942 to 11552, + 11943 to 11553, + 11944 to 11554, + 2102 to 450, + 2103 to 451, + 14853 to 14832, + 14854 to 14833, + 14855 to 14834 +) + +val ADAMANT_OBJECTS = mapOf( + 11939 to 11552, + 11940 to 11553, + 11941 to 11554, + 2104 to 450, + 2105 to 451, + 14862 to 14832, + 14863 to 14833, + 14864 to 14834 +) + +val RUNITE_OBJECTS = mapOf( + 2106 to 450, + 2107 to 451, + 14859 to 14832, + 14860 to 14833, + 14861 to 14834 +) \ No newline at end of file diff --git a/game/plugin/skills/mining/src/Pickaxe.kt b/game/plugin/skills/mining/src/Pickaxe.kt new file mode 100644 index 000000000..45071f0c9 --- /dev/null +++ b/game/plugin/skills/mining/src/Pickaxe.kt @@ -0,0 +1,27 @@ +package org.apollo.game.plugin.skills.mining + +import org.apollo.game.model.Animation +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.api.mining + +enum class Pickaxe(val id: Int, val level: Int, animation: Int, val pulses: Int) { + BRONZE(id = 1265, level = 1, animation = 625, pulses = 8), + ITRON(id = 1267, level = 1, animation = 626, pulses = 7), + STEEL(id = 1269, level = 6, animation = 627, pulses = 6), + MITHRIL(id = 1273, level = 21, animation = 629, pulses = 5), + ADAMANT(id = 1271, level = 31, animation = 628, pulses = 4), + RUNE(id = 1275, level = 41, animation = 624, pulses = 3); + + val animation = Animation(animation) + + companion object { + private val PICKAXES = Pickaxe.values().sortedByDescending { it.level } + + fun bestFor(player: Player): Pickaxe? { + return PICKAXES.asSequence() + .filter { it.level <= player.mining.current } + .filter { player.equipment.contains(it.id) || player.inventory.contains(it.id) } + .firstOrNull() + } + } +} diff --git a/game/plugin/skills/mining/src/ProspectingAction.kt b/game/plugin/skills/mining/src/ProspectingAction.kt new file mode 100644 index 000000000..10bf83a11 --- /dev/null +++ b/game/plugin/skills/mining/src/ProspectingAction.kt @@ -0,0 +1,53 @@ +import java.util.Objects +import org.apollo.game.action.ActionBlock +import org.apollo.game.action.AsyncDistancedAction +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.api.Definitions +import org.apollo.game.plugin.skills.mining.Ore +import org.apollo.net.message.Message + +class ProspectingAction( + player: Player, + position: Position, + private val ore: Ore +) : AsyncDistancedAction(DELAY, true, player, position, ORE_SIZE) { + + companion object { + private const val DELAY = 3 + private const val ORE_SIZE = 1 + + /** + * Starts a [MiningAction] for the specified [Player], terminating the [Message] that triggered it. + */ + fun start(message: ObjectActionMessage, player: Player, ore: Ore) { + val action = ProspectingAction(player, message.position, ore) + player.startAction(action) + + message.terminate() + } + } + + override fun action(): ActionBlock = { + mob.sendMessage("You examine the rock for ores...") + mob.turnTo(position) + + wait() + + val oreName = Definitions.item(ore.id)?.name?.toLowerCase() + mob.sendMessage("This rock contains $oreName.") + + stop() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ProspectingAction + return mob == other.mob && position == other.position && ore == other.ore + } + + override fun hashCode(): Int = Objects.hash(mob, position, ore) +} diff --git a/game/plugin/skills/mining/test/MiningActionTests.kt b/game/plugin/skills/mining/test/MiningActionTests.kt new file mode 100644 index 000000000..59e9a6bec --- /dev/null +++ b/game/plugin/skills/mining/test/MiningActionTests.kt @@ -0,0 +1,90 @@ + +import io.mockk.every +import io.mockk.spyk +import io.mockk.staticMockk +import io.mockk.verify +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.plugin.api.replaceObject +import org.apollo.game.plugin.skills.mining.Ore +import org.apollo.game.plugin.skills.mining.Pickaxe +import org.apollo.game.plugin.skills.mining.TIN_OBJECTS +import org.apollo.game.plugin.testing.assertions.after +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.assertions.verifyAfter +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.apollo.game.plugin.testing.junit.api.interactions.spawnObject +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class MiningActionTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var player: Player + + @TestMock + lateinit var action: ActionCapture + + @Test + fun `Attempting to mine a rock we don't have the skill to should send the player a message`() { + val obj = world.spawnObject(1, player.position) + val target = spyk(MiningTarget(obj.id, obj.position, Ore.TIN)) + + every { target.skillRequirementsMet(player) } returns false + + player.startAction(MiningAction(player, Pickaxe.BRONZE, target)) + + verifyAfter(action.complete()) { + player.sendMessage(contains("do not have the required level")) + } + } + + @Test + fun `Mining a rock we have the skill to mine should eventually reward ore and experience`() { + val (tinId, expiredTinId) = TIN_OBJ_IDS + val obj = world.spawnObject(tinId, player.position) + val target = spyk(MiningTarget(obj.id, obj.position, Ore.TIN)) + staticMockk("org.apollo.game.plugin.api.WorldKt").mock() + + every { target.skillRequirementsMet(player) } returns true + every { target.isSuccessful(player, any()) } returns true + every { world.replaceObject(obj, any(), any()) } answers { } + + player.skillSet.setCurrentLevel(Skill.MINING, Ore.TIN.level) + player.startAction(MiningAction(player, Pickaxe.BRONZE, target)) + + verifyAfter(action.ticks(1)) { + player.sendMessage(contains("You swing your pick")) + } + + after(action.complete()) { + verify { player.sendMessage("You manage to mine some ") } + verify { world.replaceObject(obj, expiredTinId, Ore.TIN.respawn) } + + assertTrue(player.inventory.contains(Ore.TIN.id)) + assertEquals(player.skillSet.getExperience(Skill.MINING), Ore.TIN.exp) + } + } + + private companion object { + private val TIN_OBJ_IDS = TIN_OBJECTS.entries.first() + + @ItemDefinitions + fun ores() = Ore.values() + .map { ItemDefinition(it.id).apply { name = "" } } + + @ItemDefinitions + fun pickaxes() = listOf(ItemDefinition(Pickaxe.BRONZE.id)) + } +} \ No newline at end of file diff --git a/game/plugin/skills/mining/test/PickaxeTests.kt b/game/plugin/skills/mining/test/PickaxeTests.kt new file mode 100644 index 000000000..70112ec14 --- /dev/null +++ b/game/plugin/skills/mining/test/PickaxeTests.kt @@ -0,0 +1,73 @@ +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.plugin.skills.mining.Pickaxe +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +@ExtendWith(ApolloTestingExtension::class) +class PickaxeTests { + + @TestMock + lateinit var player: Player + + @ParameterizedTest + @EnumSource(Pickaxe::class) + fun `No pickaxe is chosen if none are available`(pickaxe: Pickaxe) { + player.skillSet.setCurrentLevel(Skill.MINING, pickaxe.level) + + assertEquals(null, Pickaxe.bestFor(player)) + } + + @ParameterizedTest + @EnumSource(Pickaxe::class) + fun `The highest level pickaxe is chosen when available`(pickaxe: Pickaxe) { + player.skillSet.setCurrentLevel(Skill.MINING, pickaxe.level) + player.inventory.add(pickaxe.id) + + assertEquals(pickaxe, Pickaxe.bestFor(player)) + } + + @ParameterizedTest + @EnumSource(Pickaxe::class) + fun `Only pickaxes the player has are chosen`(pickaxe: Pickaxe) { + player.skillSet.setCurrentLevel(Skill.MINING, pickaxe.level) + player.inventory.add(Pickaxe.BRONZE.id) + + assertEquals(Pickaxe.BRONZE, Pickaxe.bestFor(player)) + } + + @ParameterizedTest + @EnumSource(value = Pickaxe::class) + fun `Pickaxes can be chosen from equipment as well as inventory`(pickaxe: Pickaxe) { + player.skillSet.setCurrentLevel(Skill.MINING, pickaxe.level) + player.inventory.add(pickaxe.id) + + assertEquals(pickaxe, Pickaxe.bestFor(player)) + } + + @ParameterizedTest + @EnumSource(value = Pickaxe::class) + fun `Pickaxes with a level requirement higher than the player's are ignored`(pickaxe: Pickaxe) { + player.skillSet.setCurrentLevel(Skill.MINING, pickaxe.level) + player.inventory.add(pickaxe.id) + + Pickaxe.values() + .filter { it.level > pickaxe.level } + .forEach { player.inventory.add(it.id) } + + assertEquals(pickaxe, Pickaxe.bestFor(player)) + } + + private companion object { + @ItemDefinitions + fun pickaxes() = Pickaxe.values().map { + ItemDefinition(it.id).apply { isStackable = false } + } + } +} \ No newline at end of file diff --git a/game/plugin/skills/mining/test/ProspectingTests.kt b/game/plugin/skills/mining/test/ProspectingTests.kt new file mode 100644 index 000000000..27d11c712 --- /dev/null +++ b/game/plugin/skills/mining/test/ProspectingTests.kt @@ -0,0 +1,48 @@ + +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.skills.mining.Ore +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.assertions.verifyAfter +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.apollo.game.plugin.testing.junit.api.interactions.interactWithObject +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource + +@ExtendWith(ApolloTestingExtension::class) +class ProspectingTests { + + @TestMock + lateinit var player: Player + + @TestMock + lateinit var action: ActionCapture + + @ParameterizedTest + @ArgumentsSource(MiningTestDataProvider::class) + fun `Prospecting a rock should reveal the type of ore it contains`(data: MiningTestData) { + player.interactWithObject(data.rockId, 2) + + verifyAfter(action.ticks(1)) { player.sendMessage(contains("examine the rock")) } + verifyAfter(action.complete()) { player.sendMessage(contains("This rock contains ")) } + } + + @ParameterizedTest + @ArgumentsSource(MiningTestDataProvider::class) + fun `Prospecting an expired rock should reveal it contains no ore`(data: MiningTestData) { + player.interactWithObject(data.expiredRockId, 2) + + verifyAfter(action.complete()) { player.sendMessage(contains("no ore available in this rock")) } + } + + private companion object { + @ItemDefinitions + fun ores() = Ore.values().map { + ItemDefinition(it.id).also { it.name = "" } + } + } +} \ No newline at end of file diff --git a/game/plugin/skills/mining/test/TestData.kt b/game/plugin/skills/mining/test/TestData.kt new file mode 100644 index 000000000..aa798d24a --- /dev/null +++ b/game/plugin/skills/mining/test/TestData.kt @@ -0,0 +1,17 @@ +import java.util.stream.Stream +import org.apollo.game.plugin.skills.mining.Ore +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider + +data class MiningTestData(val rockId: Int, val expiredRockId: Int, val ore: Ore) + +fun miningTestData(): Collection = Ore.values() + .flatMap { ore -> ore.objects.map { MiningTestData(it.key, it.value, ore) } } + .toList() + +class MiningTestDataProvider : ArgumentsProvider { + override fun provideArguments(context: ExtensionContext?): Stream { + return miningTestData().map { Arguments { arrayOf(it) } }.stream() + } +} \ No newline at end of file diff --git a/game/plugin/skills/prayer/build.gradle b/game/plugin/skills/prayer/build.gradle new file mode 100644 index 000000000..4d96c9f57 --- /dev/null +++ b/game/plugin/skills/prayer/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:api') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/skills/prayer/src/Bone.kt b/game/plugin/skills/prayer/src/Bone.kt new file mode 100644 index 000000000..9f3c73c13 --- /dev/null +++ b/game/plugin/skills/prayer/src/Bone.kt @@ -0,0 +1,28 @@ +enum class Bone(val id: Int, val xp: Double) { + REGULAR_BONES(id = 526, xp = 5.0), + BURNT_BONES(id = 528, xp = 5.0), + BAT_BONES(id = 530, xp = 4.0), + BIG_BONES(id = 532, xp = 45.0), + BABY_DRAGON_BONES(id = 534, xp = 30.0), + DRAGON_BONES(id = 536, xp = 72.0), + WOLF_BONES(id = 2859, xp = 14.0), + SHAIKAHAN_BONES(id = 3123, xp = 25.0), + JOGRE_BONES(id = 3125, xp = 15.0), + BURNT_ZOGRE_BONES(id = 3127, xp = 25.0), + MONKEY_BONES_SMALL_0(id = 3179, xp = 14.0), + MONKEY_BONES_MEDIUM(id = 3180, xp = 14.0), + MONKEY_BONES_LARGE_0(id = 3181, xp = 14.0), + MONKEY_BONES_LARGE_1(id = 3182, xp = 14.0), + MONKEY_BONES_SMALL_1(id = 3183, xp = 14.0), + SHAKING_BONES(id = 3187, xp = 14.0), + FAYRG_BONES(id = 4830, xp = 84.0), + RAURG_BONES(id = 4832, xp = 96.0), + OURG_BONES(id = 4834, xp = 140.0); + + companion object { + private val BONES = Bone.values().associateBy(Bone::id) + + operator fun get(id: Int) = BONES[id] + internal fun Int.isBone(): Boolean = this in BONES + } +} \ No newline at end of file diff --git a/game/plugin/skills/prayer/src/BuryBoneAction.kt b/game/plugin/skills/prayer/src/BuryBoneAction.kt new file mode 100644 index 000000000..3b6720caf --- /dev/null +++ b/game/plugin/skills/prayer/src/BuryBoneAction.kt @@ -0,0 +1,31 @@ +import org.apollo.game.action.ActionBlock +import org.apollo.game.action.AsyncAction +import org.apollo.game.model.Animation +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.api.prayer + +class BuryBoneAction( + player: Player, + private val slot: Int, + private val bone: Bone +) : AsyncAction(0, true, player) { + + override fun action(): ActionBlock = { + if (mob.inventory.removeSlot(slot, 1) > 0) { + mob.sendMessage("You dig a hole in the ground...") + mob.playAnimation(BURY_BONE_ANIMATION) + + wait(pulses = 1) + + mob.sendMessage("You bury the bones.") + mob.prayer.experience += bone.xp + } + + stop() + } + + companion object { + public val BURY_BONE_ANIMATION = Animation(827) + internal const val BURY_OPTION = 1 + } +} \ No newline at end of file diff --git a/game/plugin/skills/prayer/src/Prayer.kt b/game/plugin/skills/prayer/src/Prayer.kt new file mode 100644 index 000000000..3f281cc05 --- /dev/null +++ b/game/plugin/skills/prayer/src/Prayer.kt @@ -0,0 +1,44 @@ +import com.google.common.collect.MultimapBuilder +import com.google.common.collect.SetMultimap +import org.apollo.game.message.impl.ConfigMessage +import org.apollo.game.model.entity.Player + +enum class Prayer(val button: Int, val level: Int, val setting: Int, val drain: Double) { + THICK_SKIN(button = 5609, level = 1, setting = 83, drain = 0.01), + BURST_OF_STRENGTH(button = 5610, level = 4, setting = 84, drain = 0.01), + CLARITY_OF_THOUGHT(button = 5611, level = 7, setting = 85, drain = 0.01), + ROCK_SKIN(button = 5612, level = 10, setting = 86, drain = 0.04), + SUPERHUMAN_STRENGTH(button = 5613, level = 13, setting = 87, drain = 0.04), + IMPROVED_REFLEXES(button = 5614, level = 16, setting = 88, drain = 0.04), + RAPID_RESTORE(button = 5615, level = 19, setting = 89, drain = 0.01), + RAPID_HEAL(button = 5615, level = 22, setting = 90, drain = 0.01), + PROTECT_ITEM(button = 5617, level = 25, setting = 91, drain = 0.01), + STEEL_SKIN(button = 5618, level = 28, setting = 92, drain = 0.1), + ULTIMATE_STRENGTH(button = 5619, level = 31, setting = 93, drain = 0.1), + INCREDIBLE_REFLEXES(button = 5620, level = 34, setting = 94, drain = 0.1), + PROTECT_FROM_MAGIC(button = 5621, level = 37, setting = 95, drain = 0.15), + PROTECT_FROM_MISSILES(button = 5622, level = 40, setting = 96, drain = 0.15), + PROTECT_FROM_MELEE(button = 5623, level = 43, setting = 97, drain = 0.15), + RETRIBUTION(button = 683, level = 46, setting = 98, drain = 0.15), + REDEMPTION(button = 684, level = 49, setting = 99, drain = 0.15), + SMITE(button = 685, level = 52, setting = 100, drain = 0.2); + + companion object { + private val prayers = Prayer.values().associateBy(Prayer::button) + + fun forButton(button: Int) = prayers[button] + internal fun Int.isPrayerButton(): Boolean = this in prayers + } +} + +val Player.currentPrayers: Set + get() = playerPrayers[this] + +fun Player.updatePrayer(prayer: Prayer) { + val value = if (currentPrayers.contains(prayer)) 1 else 0 + send(ConfigMessage(prayer.setting, value)) +} + +internal val playerPrayers: SetMultimap = MultimapBuilder.hashKeys() + .enumSetValues(Prayer::class.java) + .build() \ No newline at end of file diff --git a/game/plugin/skills/prayer/src/Prayer.plugin.kts b/game/plugin/skills/prayer/src/Prayer.plugin.kts new file mode 100644 index 000000000..2338d8332 --- /dev/null +++ b/game/plugin/skills/prayer/src/Prayer.plugin.kts @@ -0,0 +1,37 @@ +import Bone.Companion.isBone +import Prayer.Companion.isPrayerButton +import org.apollo.game.message.impl.ButtonMessage +import org.apollo.game.message.impl.ItemOptionMessage +import org.apollo.game.model.event.impl.LogoutEvent +import org.apollo.game.plugin.api.prayer + +// Clear the player's prayer(s) on logout +on_player_event { LogoutEvent::class } + .then { + playerPrayers.removeAll(it) + } + +on { ButtonMessage::class } + .where { widgetId.isPrayerButton() } + .then { player -> + val prayer = Prayer.forButton(widgetId)!! + val level = prayer.level + + if (level > player.prayer.current) { + player.sendMessage("You need a prayer level of $level to use this prayer.") + terminate() + return@then + } + + player.updatePrayer(prayer) + terminate() + } + +on { ItemOptionMessage::class } + .where { option == BuryBoneAction.BURY_OPTION && id.isBone() } + .then { player -> + val bone = Bone[id]!! + + player.startAction(BuryBoneAction(player, slot, bone)) + terminate() + } diff --git a/game/plugin/skills/prayer/test/BuryBoneTests.kt b/game/plugin/skills/prayer/test/BuryBoneTests.kt new file mode 100644 index 000000000..23d57f684 --- /dev/null +++ b/game/plugin/skills/prayer/test/BuryBoneTests.kt @@ -0,0 +1,73 @@ + +import BuryBoneAction.Companion.BURY_BONE_ANIMATION +import io.mockk.verify +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.api.prayer +import org.apollo.game.plugin.testing.assertions.after +import org.apollo.game.plugin.testing.assertions.startsWith +import org.apollo.game.plugin.testing.assertions.verifyAfter +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.apollo.game.plugin.testing.junit.api.interactions.interactWithItem +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +@ExtendWith(ApolloTestingExtension::class) +class BuryBoneTests { + + @TestMock + lateinit var player: Player + + @TestMock + lateinit var action: ActionCapture + + @ParameterizedTest + @EnumSource(value = Bone::class) + fun `Burying a bone should send a message`(bone: Bone) { + player.inventory.add(bone.id) + player.interactWithItem(bone.id, option = 1) + + verifyAfter(action.ticks(1), "message is sent") { + player.sendMessage(startsWith("You dig a hole")) + } + } + + @ParameterizedTest + @EnumSource(value = Bone::class) + fun `Burying a bone should play an animation`(bone: Bone) { + player.inventory.add(bone.id) + player.interactWithItem(bone.id, option = 1) + + verifyAfter(action.ticks(1), "animation is played") { + player.playAnimation(eq(BURY_BONE_ANIMATION)) + } + } + + @ParameterizedTest + @EnumSource(value = Bone::class) + fun `Burying a bone should give the player experience`(bone: Bone) { + player.inventory.add(bone.id) + player.interactWithItem(bone.id, option = 1) + + action.ticks(1) + + after(action.complete(), "experience is granted after bone burial") { + verify { player.sendMessage(startsWith("You bury the bones")) } + + assertEquals(bone.xp, player.prayer.experience) + assertEquals(player.inventory.getAmount(bone.id), 0) + } + } + + private companion object { + @ItemDefinitions + fun bones(): Collection { + return Bone.values().map { ItemDefinition(it.id) } + } + } +} \ No newline at end of file diff --git a/game/plugin/skills/runecrafting/build.gradle b/game/plugin/skills/runecrafting/build.gradle new file mode 100644 index 000000000..4d96c9f57 --- /dev/null +++ b/game/plugin/skills/runecrafting/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:api') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/skills/runecrafting/src/Altar.kt b/game/plugin/skills/runecrafting/src/Altar.kt new file mode 100644 index 000000000..f336b2b1d --- /dev/null +++ b/game/plugin/skills/runecrafting/src/Altar.kt @@ -0,0 +1,25 @@ +package org.apollo.game.plugin.skill.runecrafting + +import org.apollo.game.model.Position + +enum class Altar(val entranceId: Int, val craftingId: Int, val portalId: Int, val entrance: Position, val exit: Position, val center: Position) { + AIR_ALTAR(2452, 2478, 2465, Position(2841, 4829), Position(2983, 3292), Position(2844, 4834)), + MIND_ALTAR(2453, 2479, 2466, Position(2793, 4828), Position(2980, 3514), Position(2786, 4841)), + WATER_ALTAR(2454, 2480, 2467, Position(2726, 4832), Position(3187, 3166), Position(2716, 4836)), + EARTH_ALTAR(2455, 2481, 2468, Position(2655, 4830), Position(3304, 3474), Position(2658, 4841)), + FIRE_ALTAR(2456, 2482, 2469, Position(2574, 4849), Position(3311, 3256), Position(2585, 4838)), + BODY_ALTAR(2457, 2483, 2470, Position(2524, 4825), Position(3051, 3445), Position(2525, 4832)), + COSMIC_ALTAR(2458, 2484, 2471, Position(2142, 4813), Position(2408, 4379), Position(2142, 4833)), + LAW_ALTAR(2459, 2485, 2472, Position(2464, 4818), Position(2858, 3379), Position(2464, 4832)), + NATURE_ALTAR(2460, 2486, 2473, Position(2400, 4835), Position(2867, 3019), Position(2400, 4841)), + CHAOS_ALTAR(2461, 2487, 2474, Position(2268, 4842), Position(3058, 3591), Position(2271, 4842)), + DEATH_ALTAR(2462, 2488, 2475, Position(2208, 4830), Position(3222, 3222), Position(2205, 4836)); + + companion object { + private val ALTARS = Altar.values() + + fun findByEntranceId(id: Int): Altar? = ALTARS.find { Altar -> Altar.entranceId == id } + fun findByPortalId(id: Int): Altar? = ALTARS.find { Altar -> Altar.portalId == id } + fun findByCraftingId(id: Int): Altar? = ALTARS.find { Altar -> Altar.craftingId == id } + } +} \ No newline at end of file diff --git a/game/plugin/skills/runecrafting/src/CreateTiaraAction.kt b/game/plugin/skills/runecrafting/src/CreateTiaraAction.kt new file mode 100644 index 000000000..f6bcc43fe --- /dev/null +++ b/game/plugin/skills/runecrafting/src/CreateTiaraAction.kt @@ -0,0 +1,25 @@ +package org.apollo.game.plugin.skill.runecrafting + +import org.apollo.game.action.DistancedAction +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.api.runecraft + +class CreateTiaraAction(val player: Player, val position: Position, val tiara: Tiara, val altar: Altar) : DistancedAction(0, true, player, position, 2) { + override fun executeAction() { + if (tiara.altar != altar) { + player.sendMessage("You can't use that talisman on this altar.") + stop() + return + } + + if (player.inventory.contains(blankTiaraId)) { + player.inventory.remove(blankTiaraId) + player.inventory.add(tiara.id) + player.runecraft.experience += tiara.xp + player.playAnimation(runecraftingAnimation) + player.playGraphic(runecraftingGraphic) + stop() + } + } +} \ No newline at end of file diff --git a/game/plugin/skills/runecrafting/src/Rune.kt b/game/plugin/skills/runecrafting/src/Rune.kt new file mode 100644 index 000000000..5717442b5 --- /dev/null +++ b/game/plugin/skills/runecrafting/src/Rune.kt @@ -0,0 +1,65 @@ +package org.apollo.game.plugin.skill.runecrafting + +import org.apollo.game.plugin.skill.runecrafting.Altar.* + +interface Rune { + /** + * The item id of the rune. + */ + val id: Int + + /** + * The [Altar] this rune must be crafted at. + */ + val altar: Altar + + /** + * The runecrafting level required to craft runes of this type. + */ + val level: Int + + /** + * The amount of experience rewarded from crafting a single rune of this type. + */ + val xp: Double + + /** + * Get the multiplier that is applied to the number of runes the player crafts for their runecrafting level. + * + * [playerLevel] - The players current runecrafting level. + */ + fun getBonusMultiplier(playerLevel: Int): Double +} + +enum class DefaultRune( + override val id: Int, + override val altar: Altar, + override val level: Int, + override val xp: Double +) : Rune { + AIR_RUNE(556, AIR_ALTAR, 1, 5.0), + MIND_RUNE(558, MIND_ALTAR, 1, 5.5), + WATER_RUNE(555, WATER_ALTAR, 5, 6.0), + EARTH_RUNE(557, EARTH_ALTAR, 9, 6.5), + FIRE_RUNE(554, FIRE_ALTAR, 14, 7.0), + BODY_RUNE(559, BODY_ALTAR, 20, 7.5), + COSMIC_RUNE(564, COSMIC_ALTAR, 27, 8.0), + CHAOS_RUNE(562, CHAOS_ALTAR, 35, 8.5), + NATURE_RUNE(561, NATURE_ALTAR, 44, 9.0), + LAW_RUNE(563, LAW_ALTAR, 54, 9.5), + DEATH_RUNE(560, DEATH_ALTAR, 65, 10.0); + + override fun getBonusMultiplier(playerLevel: Int): Double = when (this) { + DefaultRune.AIR_RUNE -> (Math.floor((playerLevel / 11.0)) + 1) + DefaultRune.MIND_RUNE -> (Math.floor((playerLevel / 14.0)) + 1) + DefaultRune.WATER_RUNE -> (Math.floor((playerLevel / 19.0)) + 1) + DefaultRune.EARTH_RUNE -> (Math.floor((playerLevel / 26.0)) + 1) + DefaultRune.FIRE_RUNE -> (Math.floor((playerLevel / 35.0)) + 1) + DefaultRune.BODY_RUNE -> (Math.floor((playerLevel / 46.0)) + 1) + DefaultRune.COSMIC_RUNE -> (Math.floor((playerLevel / 59.0)) + 1) + DefaultRune.CHAOS_RUNE -> (Math.floor((playerLevel / 74.0)) + 1) + DefaultRune.NATURE_RUNE -> (Math.floor((playerLevel / 91.0)) + 1) + DefaultRune.LAW_RUNE -> 1.0 + DefaultRune.DEATH_RUNE -> 1.0 + } +} diff --git a/game/plugin/skills/runecrafting/src/Runecrafting.plugin.kts b/game/plugin/skills/runecrafting/src/Runecrafting.plugin.kts new file mode 100644 index 000000000..88f983f77 --- /dev/null +++ b/game/plugin/skills/runecrafting/src/Runecrafting.plugin.kts @@ -0,0 +1,93 @@ +package org.apollo.game.plugin.skill.runecrafting + +import org.apollo.game.message.impl.* +import org.apollo.game.model.entity.EquipmentConstants +import org.apollo.game.model.event.impl.LoginEvent + +private val changeAltarObjectConfigId = 491 + +internal val RUNES = mutableListOf() + +fun List.findById(id: Int): Rune? { + return find { rune -> rune.id == id } +} + +start { + RUNES.addAll(DefaultRune.values()) +} + +on_player_event { LoginEvent::class } + .then { + val equippedHat = player.equipment.get(EquipmentConstants.HAT) + val equippedTiaraConfig = equippedHat?.let { item -> Tiara.findById(item.id)?.configId } ?: 0 + val configValue = 1 shl equippedTiaraConfig + + player.send(ConfigMessage(changeAltarObjectConfigId, configValue)) + } + +on { ObjectActionMessage::class } + .where { option == 2 } + .then { + val tiara = Tiara.findByAltarId(id) ?: return@then + val hat = it.equipment.get(EquipmentConstants.HAT) ?: return@then + + if (hat.id == tiara.id && tiara.altar.entranceId == id) { + it.startAction(TeleportToAltarAction(it, position, 2, tiara.altar.entrance)) + terminate() + } + } + +on { ItemActionMessage::class } + .where { option == 1 } + .then { player -> + Tiara.findById(id)?.let { + player.send(ConfigMessage(changeAltarObjectConfigId, 0)) + terminate() + } + } + +on { ItemOnObjectMessage::class } + .then { + val tiara = Tiara.findByTalismanId(id) ?: return@then + val altar = Altar.findByCraftingId(objectId) ?: return@then + + it.startAction(CreateTiaraAction(it, position, tiara, altar)) + terminate() + } + +on { ItemOptionMessage::class } + .where { option == 4 } + .then { + val talisman = Talisman.findById(id) ?: return@then + + talisman.sendProximityMessageTo(it) + terminate() + } + +on { ItemOnObjectMessage::class } + .then { + val talisman = Talisman.findById(id) ?: return@then + val altar = Altar.findByEntranceId(objectId) ?: return@then + + it.startAction(TeleportToAltarAction(it, position, 2, altar.entrance)) + terminate() + } + +on { ObjectActionMessage::class } + .where { option == 1 } + .then { + val altar = Altar.findByPortalId(id) ?: return@then + + it.startAction(TeleportToAltarAction(it, altar.entrance, 1, altar.exit)) + terminate() + } + +on { ObjectActionMessage::class } + .where { option == 1 } + .then { + val rune = RUNES.findById(id) ?: return@then + val craftingAltar = Altar.findByCraftingId(id) ?: return@then + + it.startAction(RunecraftingAction(it, rune, craftingAltar)) + terminate() + } \ No newline at end of file diff --git a/game/plugin/skills/runecrafting/src/RunecraftingAction.kt b/game/plugin/skills/runecrafting/src/RunecraftingAction.kt new file mode 100644 index 000000000..ec8c04381 --- /dev/null +++ b/game/plugin/skills/runecrafting/src/RunecraftingAction.kt @@ -0,0 +1,39 @@ +package org.apollo.game.plugin.skill.runecrafting + +import org.apollo.game.action.ActionBlock +import org.apollo.game.action.AsyncDistancedAction +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.api.Definitions +import org.apollo.game.plugin.api.runecraft +import org.apollo.util.LanguageUtil + +class RunecraftingAction(val player: Player, val rune: Rune, altar: Altar) : AsyncDistancedAction(0, true, player, altar.center, 3) { + override fun action(): ActionBlock = { + if (player.runecraft.current < rune.level) { + player.sendMessage("You need a runecrafting level of ${rune.level} to craft this rune.") + stop() + } + + if (!player.inventory.contains(runeEssenceId)) { + player.sendMessage("You need rune essence to craft runes.") + stop() + } + + player.turnTo(position) + player.playAnimation(runecraftingAnimation) + player.playGraphic(runecraftingGraphic) + + wait(1) + + val name = Definitions.item(rune.id).name + val nameArticle = LanguageUtil.getIndefiniteArticle(name) + val essenceAmount = player.inventory.removeAll(runeEssenceId) + val runeAmount = essenceAmount * rune.getBonusMultiplier(player.runecraft.current) + val runesDescription = if (runeAmount > 1) "some ${name}s" else "$nameArticle $name" + + player.sendMessage("You craft the rune essence into $runesDescription") + player.inventory.add(rune.id, runeAmount.toInt()) + player.runecraft.experience += rune.xp * essenceAmount + stop() + } +} diff --git a/game/plugin/skills/runecrafting/src/Talisman.kt b/game/plugin/skills/runecrafting/src/Talisman.kt new file mode 100644 index 000000000..bd4cc542e --- /dev/null +++ b/game/plugin/skills/runecrafting/src/Talisman.kt @@ -0,0 +1,36 @@ +package org.apollo.game.plugin.skill.runecrafting + +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player + +enum class Talisman(val id: Int, val altar: Position) { + AIR_TALISMAN(1438, Position(2985, 3292)), + EARTH_TALISMAN(1440, Position(3306, 3474)), + FIRE_TALISMAN(1442, Position(3313, 3255)), + WATER_TALISMAN(1444, Position(3185, 3165)), + BODY_TALISMAN(1446, Position(3053, 3445)), + MIND_TALISMAN(1448, Position(2982, 3514)), + CHAOS_TALISMAN(1452, Position(3059, 3590)), + COSMIC_TALISMAN(1454, Position(2408, 4377)), + DEATH_TALISMAN(1456, Position(0, 0)), + LAW_TALISMAN(1458, Position(2858, 3381)), + NATURE_TALISMAN(1462, Position(2869, 3019)); + + companion object { + private val TALISMANS = Talisman.values() + + fun findById(id: Int): Talisman? = TALISMANS.find { talisman -> talisman.id == id } + } + + fun sendProximityMessageTo(player: Player) { + if (altar.isWithinDistance(player.position, 10)) { + player.sendMessage("Your talisman glows brightly.") + return + } + + var direction = if (player.position.y > altar.y) "North" else "South" + direction += if (player.position.x > altar.x) "-East" else "-West" + + player.sendMessage("The talisman pulls toward the $direction") + } +} \ No newline at end of file diff --git a/game/plugin/skills/runecrafting/src/TeleportToAltarAction.kt b/game/plugin/skills/runecrafting/src/TeleportToAltarAction.kt new file mode 100644 index 000000000..ec6ff9a8c --- /dev/null +++ b/game/plugin/skills/runecrafting/src/TeleportToAltarAction.kt @@ -0,0 +1,12 @@ +package org.apollo.game.plugin.skill.runecrafting + +import org.apollo.game.action.DistancedAction +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player + +class TeleportToAltarAction(val player: Player, val start: Position, val distance: Int, val end: Position) : DistancedAction(0, true, player, start, distance) { + override fun executeAction() { + player.teleport(end) + stop() + } +} \ No newline at end of file diff --git a/game/plugin/skills/runecrafting/src/Tiara.kt b/game/plugin/skills/runecrafting/src/Tiara.kt new file mode 100644 index 000000000..d9e002c0e --- /dev/null +++ b/game/plugin/skills/runecrafting/src/Tiara.kt @@ -0,0 +1,26 @@ +package org.apollo.game.plugin.skill.runecrafting + +import org.apollo.game.plugin.skill.runecrafting.Altar.* +import org.apollo.game.plugin.skill.runecrafting.Talisman.* + +enum class Tiara(val id: Int, val altar: Altar, val talisman: Talisman, val configId: Int, val xp: Double) { + AIR_TIARA(5527, AIR_ALTAR, AIR_TALISMAN, 0, 25.0), + MIND_TIARA(5529, MIND_ALTAR, MIND_TALISMAN, 1, 27.5), + WATER_TIARA(5531, WATER_ALTAR, WATER_TALISMAN, 2, 30.0), + BODY_TIARA(5533, BODY_ALTAR, BODY_TALISMAN, 5, 37.5), + EARTH_TIARA(5535, EARTH_ALTAR, EARTH_TALISMAN, 3, 32.5), + FIRE_TIARA(5537, FIRE_ALTAR, FIRE_TALISMAN, 4, 35.0), + COSMIC_TIARA(5539, COSMIC_ALTAR, COSMIC_TALISMAN, 6, 40.0), + NATURE_TIARA(5541, NATURE_ALTAR, NATURE_TALISMAN, 8, 45.0), + CHAOS_TIARA(5543, CHAOS_ALTAR, CHAOS_TALISMAN, 9, 42.5), + LAW_TIARA(5545, LAW_ALTAR, LAW_TALISMAN, 7, 47.5), + DEATH_TIARA(5548, DEATH_ALTAR, DEATH_TALISMAN, 10, 50.0); + + companion object { + private val TIARAS = Tiara.values() + + fun findById(id: Int): Tiara? = TIARAS.find { tiara -> tiara.id == id } + fun findByAltarId(id: Int): Tiara? = TIARAS.find { tiara -> tiara.altar.entranceId == id } + fun findByTalismanId(id: Int): Tiara? = TIARAS.find { tiara -> tiara.talisman.id == id } + } +} \ No newline at end of file diff --git a/game/plugin/skills/runecrafting/src/constants.kt b/game/plugin/skills/runecrafting/src/constants.kt new file mode 100644 index 000000000..73f77cf52 --- /dev/null +++ b/game/plugin/skills/runecrafting/src/constants.kt @@ -0,0 +1,9 @@ +package org.apollo.game.plugin.skill.runecrafting + +import org.apollo.game.model.Animation +import org.apollo.game.model.Graphic + +const val blankTiaraId = 5525 +val runecraftingAnimation = Animation(791) +val runecraftingGraphic = Graphic(186, 0, 100) +const val runeEssenceId = 1436 \ No newline at end of file diff --git a/game/plugin/skills/runecrafting/test/CreateTiaraActionTests.kt b/game/plugin/skills/runecrafting/test/CreateTiaraActionTests.kt new file mode 100644 index 000000000..98af45f22 --- /dev/null +++ b/game/plugin/skills/runecrafting/test/CreateTiaraActionTests.kt @@ -0,0 +1,68 @@ +package org.apollo.game.plugin.skill.runecrafting + +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.plugin.testing.assertions.after +import org.apollo.game.plugin.testing.assertions.verifyAfter +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class CreateTiaraActionTests { + + @TestMock + lateinit var world: World + + @TestMock + lateinit var player: Player + + @TestMock + lateinit var action: ActionCapture + + @Test + fun `A tiara should be rewarded after action completion`() { + player.inventory.add(blankTiaraId) + player.startAction(CreateTiaraAction(player, player.position, Tiara.AIR_TIARA, Altar.AIR_ALTAR)) + + after(action.complete(), "tiara added to inventory") { + assertEquals(1, player.inventory.getAmount(Tiara.AIR_TIARA.id)) + } + } + + @Test + fun `Tiaras can only be enchanted on compatible altars`() { + player.inventory.add(blankTiaraId) + player.startAction(CreateTiaraAction(player, player.position, Tiara.AIR_TIARA, Altar.BODY_ALTAR)) + + verifyAfter(action.complete(), "error message sent") { + player.sendMessage("You can't use that talisman on this altar.") + } + } + + @Test + fun `Experience is rewarded for enchanting tiaras`() { + player.inventory.add(blankTiaraId) + player.skillSet.setExperience(Skill.RUNECRAFT, 0.0) + player.startAction(CreateTiaraAction(player, player.position, Tiara.AIR_TIARA, Altar.AIR_ALTAR)) + + after(action.complete(), "experience gained") { + assertEquals(Tiara.AIR_TIARA.xp, player.skillSet.getExperience(Skill.RUNECRAFT)) + } + } + + companion object { + @ItemDefinitions + private val tiaras = Tiara.values() + .map { ItemDefinition(it.id).apply { name = "" } } + + @ItemDefinitions + private val blankTiara = listOf(ItemDefinition(blankTiaraId)) + } +} \ No newline at end of file diff --git a/game/plugin/skills/runecrafting/test/RunecraftingActionTests.kt b/game/plugin/skills/runecrafting/test/RunecraftingActionTests.kt new file mode 100644 index 000000000..9751696bc --- /dev/null +++ b/game/plugin/skills/runecrafting/test/RunecraftingActionTests.kt @@ -0,0 +1,115 @@ +package org.apollo.game.plugin.skill.runecrafting + +import io.mockk.verify +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.plugin.testing.assertions.after +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class RunecraftingActionTests { + + @TestMock + lateinit var player: Player + + @TestMock + lateinit var action: ActionCapture + + @BeforeEach + fun setupPlayer() { + player.position = TEST_ALTAR.center + player.inventory.add(runeEssenceId, 25) + } + + @Test + fun `Bonus runes are rewarded depending on the multiplier returned by the rune type`() { + player.startAction(RunecraftingAction(player, `rune with 1xp and bonus multiplier of 2`, TEST_ALTAR)) + + after(action.complete()) { + assertEquals(50, player.inventory.getAmount(1)) + } + } + + @Test + fun `Experience does not stack with bonus multiplier`() { + player.skillSet.setExperience(Skill.RUNECRAFT, 0.0) + player.startAction(RunecraftingAction(player, `rune with 1xp and bonus multiplier of 2`, TEST_ALTAR)) + + after(action.complete()) { + assertEquals(25.0, player.skillSet.getExperience(Skill.RUNECRAFT)) + } + } + + @Test + fun `Experience is rewarded for each rune essence used`() { + player.skillSet.setExperience(Skill.RUNECRAFT, 0.0) + player.startAction(RunecraftingAction(player, `rune with 1xp and bonus multiplier of 1`, TEST_ALTAR)) + + after(action.complete()) { + assertEquals(25.0, player.skillSet.getExperience(Skill.RUNECRAFT)) + } + } + + @Test + fun `Cannot create runes that are too high of a level`() { + player.skillSet.setCurrentLevel(Skill.RUNECRAFT, 1) + player.startAction(RunecraftingAction(player, `rune with required level of 99`, TEST_ALTAR)) + + after(action.complete()) { + verify { player.sendMessage("You need a runecrafting level of 99 to craft this rune.") } + + assertEquals(25, player.inventory.getAmount(runeEssenceId)) + assertEquals(0, player.inventory.getAmount(1)) + } + } + + companion object { + val TEST_ALTAR = Altar.AIR_ALTAR + + val `rune with required level of 99` = object : Rune { + override val id = 1 + override val altar: Altar = TEST_ALTAR + override val level = 99 + override val xp = 1.0 + + override fun getBonusMultiplier(playerLevel: Int) = 1.0 + } + + val `rune with 1xp and bonus multiplier of 1` = object : Rune { + override val id = 1 + override val altar: Altar = TEST_ALTAR + override val level = 1 + override val xp = 1.0 + + override fun getBonusMultiplier(playerLevel: Int) = 1.0 + } + + val `rune with 1xp and bonus multiplier of 2` = object : Rune { + override val id = 1 + override val altar: Altar = TEST_ALTAR + override val level = 1 + override val xp = 1.0 + + override fun getBonusMultiplier(playerLevel: Int) = 2.0 + } + + @ItemDefinitions + private val runeEssence = listOf(ItemDefinition(runeEssenceId).apply { + isStackable = true + }) + + @ItemDefinitions + private val runes = listOf(ItemDefinition(1).apply { + name = "" + isStackable = true + }) + } +} \ No newline at end of file diff --git a/game/plugin/skills/woodcutting/build.gradle b/game/plugin/skills/woodcutting/build.gradle new file mode 100644 index 000000000..4d96c9f57 --- /dev/null +++ b/game/plugin/skills/woodcutting/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'kotlin' + +dependencies { + implementation project(':game') + implementation project(':cache') + implementation project(':net') + implementation project(':util') + implementation project(':game:plugin:api') + testImplementation project(':game:plugin-testing') +} diff --git a/game/plugin/skills/woodcutting/src/Axe.kt b/game/plugin/skills/woodcutting/src/Axe.kt new file mode 100644 index 000000000..ab51ec0da --- /dev/null +++ b/game/plugin/skills/woodcutting/src/Axe.kt @@ -0,0 +1,28 @@ +package org.apollo.game.plugin.skills.woodcutting + +import org.apollo.game.model.Animation +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.api.woodcutting + +enum class Axe(val id: Int, val level: Int, animation: Int, val pulses: Int) { + BRONZE(id = 1351, level = 1, animation = 879, pulses = 8), + IRON(id = 1349, level = 1, animation = 877, pulses = 7), + STEEL(id = 1353, level = 6, animation = 875, pulses = 6), + BLACK(id = 1361, level = 11, animation = 873, pulses = 6), + MITHRIL(id = 1355, level = 21, animation = 871, pulses = 5), + ADAMANT(id = 1357, level = 31, animation = 869, pulses = 4), + RUNE(id = 1359, level = 41, animation = 867, pulses = 3); + + val animation = Animation(animation) + + companion object { + private val AXES = Axe.values().sortedByDescending { it.level } + + fun bestFor(player: Player): Axe? { + return AXES.asSequence() + .filter { it.level <= player.woodcutting.current } + .filter { player.equipment.contains(it.id) || player.inventory.contains(it.id) } + .firstOrNull() + } + } +} diff --git a/game/plugin/skills/woodcutting/src/Tree.kt b/game/plugin/skills/woodcutting/src/Tree.kt new file mode 100644 index 000000000..51a274a0e --- /dev/null +++ b/game/plugin/skills/woodcutting/src/Tree.kt @@ -0,0 +1,45 @@ +package org.apollo.game.plugin.skills.woodcutting + +/* + * Values thanks to: http://oldschoolrunescape.wikia.com/wiki/Woodcutting + * https://twitter.com/JagexKieren/status/713409506273787904 + */ + +enum class Tree( + val objects: Set, + val id: Int, + val stump: Int, + val level: Int, + val exp: Double, + val chance: Double +) { + NORMAL(NORMAL_OBJECTS, id = 1511, stump = 1342, level = 1, exp = 25.0, chance = 100.0), + ACHEY(ACHEY_OBJECTS, id = 2862, stump = 3371, level = 1, exp = 25.0, chance = 100.0), + OAK(OAK_OBJECTS, id = 1521, stump = 1342, level = 15, exp = 37.5, chance = 12.5), + WILLOW(WILLOW_OBJECTS, id = 1519, stump = 1342, level = 30, exp = 67.5, chance = 12.5), + TEAK(TEAK_OBJECTS, id = 6333, stump = 1342, level = 35, exp = 85.0, chance = 12.5), + MAPLE(MAPLE_OBJECTS, id = 1517, stump = 1342, level = 45, exp = 100.0, chance = 12.5), + MAHOGANY(MAHOGANY_OBJECTS, id = 6332, stump = 1342, level = 50, exp = 125.0, chance = 12.5), + YEW(YEW_OBJECTS, id = 1515, stump = 1342, level = 60, exp = 175.0, chance = 12.5), + MAGIC(MAGIC_OBJECTS, id = 1513, stump = 1324, level = 75, exp = 250.0, chance = 12.5); + + companion object { + private val TREES = Tree.values().flatMap { tree -> tree.objects.map { Pair(it, tree) } }.toMap() + fun lookup(id: Int): Tree? = TREES[id] + } +} + +private val NORMAL_OBJECTS = hashSetOf( + 1276, 1277, 1278, 1279, 1280, 1282, 1283, 1284, 1285, 1285, 1286, 1289, 1290, 1291, 1315, + 1316, 1318, 1330, 1331, 1332, 1365, 1383, 1384, 2409, 3033, 3034, 3035, 3036, 3881, 3882, + 3883, 5902, 5903, 5904, 10041 +) + +private val ACHEY_OBJECTS = hashSetOf(2023) +private val OAK_OBJECTS = hashSetOf(1281, 3037) +private val WILLOW_OBJECTS = hashSetOf(5551, 5552, 5553) +private val TEAK_OBJECTS = hashSetOf(9036) +private val MAPLE_OBJECTS = hashSetOf(1307, 4674) +private val MAHOGANY_OBJECTS = hashSetOf(9034) +private val YEW_OBJECTS = hashSetOf(1309) +private val MAGIC_OBJECTS = hashSetOf(1292, 1306) \ No newline at end of file diff --git a/game/plugin/skills/woodcutting/src/Woodcutting.plugin.kts b/game/plugin/skills/woodcutting/src/Woodcutting.plugin.kts new file mode 100644 index 000000000..f0d962395 --- /dev/null +++ b/game/plugin/skills/woodcutting/src/Woodcutting.plugin.kts @@ -0,0 +1,104 @@ + +import java.util.concurrent.TimeUnit +import org.apollo.game.GameConstants +import org.apollo.game.action.ActionBlock +import org.apollo.game.action.AsyncDistancedAction +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.obj.GameObject +import org.apollo.game.plugin.api.* +import org.apollo.game.plugin.skills.woodcutting.Axe +import org.apollo.game.plugin.skills.woodcutting.Tree + +// TODO Accurate chopping rates, e.g. https://twitter.com/JagexKieren/status/713403124464107520 + +on { ObjectActionMessage::class } + .where { option == 1 } + .then { player -> + Tree.lookup(id)?.let { WoodcuttingAction.start(this, player, it) } + } + +class WoodcuttingTarget(private val objectId: Int, val position: Position, val tree: Tree) { + + /** + * Get the tree object in the world + */ + fun getObject(world: World): GameObject? { + return world.findObject(position, objectId) + } + + /** + * Returns whether or not the tree was cut down. + */ + fun isCutDown(): Boolean = rand(100) <= tree.chance * 100 +} + +class WoodcuttingAction( + player: Player, + private val tool: Axe, + private val target: WoodcuttingTarget +) : AsyncDistancedAction(DELAY, true, player, target.position, TREE_SIZE) { + + companion object { + private const val DELAY = 0 + private const val TREE_SIZE = 2 + private const val MINIMUM_RESPAWN_TIME = 30L // In seconds + + /** + * Starts a [WoodcuttingAction] for the specified [Player], terminating the [ObjectActionMessage] that triggered + * it. + */ + fun start(message: ObjectActionMessage, player: Player, wood: Tree) { + val axe = Axe.bestFor(player) + if (axe != null) { + if (player.inventory.freeSlots() == 0) { + player.inventory.forceCapacityExceeded() + return + } + + val action = WoodcuttingAction(player, axe, WoodcuttingTarget(message.id, message.position, wood)) + player.startAction(action) + } else { + player.sendMessage("You do not have an axe for which you have the level to use.") + } + + message.terminate() + } + } + + override fun action(): ActionBlock = { + mob.turnTo(position) + + val level = mob.woodcutting.current + if (level < target.tree.level) { + mob.sendMessage("You do not have the required level to cut down this tree.") + stop() + } + + while (isRunning) { + mob.sendMessage("You swing your axe at the tree.") + mob.playAnimation(tool.animation) + + wait(tool.pulses) + + // Check that the object exists in the world + val obj = target.getObject(mob.world) ?: stop() + + if (mob.inventory.add(target.tree.id)) { + val logName = Definitions.item(target.tree.id).name.toLowerCase() + mob.sendMessage("You managed to cut some $logName.") + mob.woodcutting.experience += target.tree.exp + } + + if (target.isCutDown()) { + // respawn time: http://runescape.wikia.com/wiki/Trees + val respawn = TimeUnit.SECONDS.toMillis(MINIMUM_RESPAWN_TIME + rand(150)) / GameConstants.PULSE_DELAY + + mob.world.replaceObject(obj, target.tree.stump, respawn.toInt()) + stop() + } + } + } +} \ No newline at end of file diff --git a/game/plugin/skills/woodcutting/test/AxeTests.kt b/game/plugin/skills/woodcutting/test/AxeTests.kt new file mode 100644 index 000000000..4f271f489 --- /dev/null +++ b/game/plugin/skills/woodcutting/test/AxeTests.kt @@ -0,0 +1,73 @@ +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.plugin.skills.woodcutting.Axe +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +@ExtendWith(ApolloTestingExtension::class) +class AxeTests { + + @TestMock + lateinit var player: Player + + @ParameterizedTest + @EnumSource(Axe::class) + fun `No axe is chosen if none are available`(axe: Axe) { + player.skillSet.setCurrentLevel(Skill.WOODCUTTING, axe.level) + + assertEquals(null, Axe.bestFor(player)) + } + + @ParameterizedTest + @EnumSource(Axe::class) + fun `The highest level axe is chosen when available`(axe: Axe) { + player.skillSet.setCurrentLevel(Skill.WOODCUTTING, axe.level) + player.inventory.add(axe.id) + + assertEquals(axe, Axe.bestFor(player)) + } + + @ParameterizedTest + @EnumSource(Axe::class) + fun `Only axes the player has are chosen`(axe: Axe) { + player.skillSet.setCurrentLevel(Skill.WOODCUTTING, axe.level) + player.inventory.add(Axe.BRONZE.id) + + assertEquals(Axe.BRONZE, Axe.bestFor(player)) + } + + @ParameterizedTest + @EnumSource(Axe::class) + fun `Axes can be chosen from equipment as well as inventory`(axe: Axe) { + player.skillSet.setCurrentLevel(Skill.WOODCUTTING, axe.level) + player.inventory.add(axe.id) + + assertEquals(axe, Axe.bestFor(player)) + } + + @ParameterizedTest + @EnumSource(Axe::class) + fun `Axes with a level requirement higher than the player's are ignored`(axe: Axe) { + player.skillSet.setCurrentLevel(Skill.WOODCUTTING, axe.level) + player.inventory.add(axe.id) + + Axe.values() + .filter { it.level > axe.level } + .forEach { player.inventory.add(it.id) } + + assertEquals(axe, Axe.bestFor(player)) + } + + private companion object { + @ItemDefinitions + fun axes() = Axe.values().map { + ItemDefinition(it.id).apply { isStackable = false } + } + } +} \ No newline at end of file diff --git a/game/plugin/skills/woodcutting/test/TestData.kt b/game/plugin/skills/woodcutting/test/TestData.kt new file mode 100644 index 000000000..80c82e76f --- /dev/null +++ b/game/plugin/skills/woodcutting/test/TestData.kt @@ -0,0 +1,17 @@ +import java.util.stream.Stream +import org.apollo.game.plugin.skills.woodcutting.Tree +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider + +data class WoodcuttingTestData(val treeId: Int, val stumpId: Int, val tree: Tree) + +fun woodcuttingTestData(): Collection = Tree.values() + .flatMap { tree -> tree.objects.map { WoodcuttingTestData(it, tree.stump, tree) } } + .toList() + +class WoodcuttingTestDataProvider : ArgumentsProvider { + override fun provideArguments(context: ExtensionContext?): Stream { + return woodcuttingTestData().map { Arguments { arrayOf(it) } }.stream() + } +} \ No newline at end of file diff --git a/game/plugin/skills/woodcutting/test/WoodcuttingTests.kt b/game/plugin/skills/woodcutting/test/WoodcuttingTests.kt new file mode 100644 index 000000000..f98ab24f4 --- /dev/null +++ b/game/plugin/skills/woodcutting/test/WoodcuttingTests.kt @@ -0,0 +1,90 @@ + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import java.util.Random +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.plugin.skills.woodcutting.Axe +import org.apollo.game.plugin.testing.assertions.after +import org.apollo.game.plugin.testing.assertions.contains +import org.apollo.game.plugin.testing.assertions.verifyAfter +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.ActionCapture +import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.apollo.game.plugin.testing.junit.api.interactions.interactWithObject +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assumptions.assumeTrue +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource + +@ExtendWith(ApolloTestingExtension::class) +class WoodcuttingTests { + + @TestMock + lateinit var action: ActionCapture + + @TestMock + lateinit var player: Player + + @ParameterizedTest + @ArgumentsSource(WoodcuttingTestDataProvider::class) + fun `Attempting to cut a tree when the player has no axe should send a message`(data: WoodcuttingTestData) { + player.interactWithObject(data.treeId, 1) + + verify { player.sendMessage(contains("do not have an axe")) } + } + + @ParameterizedTest + @ArgumentsSource(WoodcuttingTestDataProvider::class) + fun `Attempting to cut a tree when the player is too low levelled should send a message`(data: WoodcuttingTestData) { + assumeTrue(data.tree.level > 1, "Normal trees are covered by axe requirements") + + player.inventory.add(Axe.BRONZE.id) + player.skillSet.setCurrentLevel(Skill.WOODCUTTING, data.tree.level - 1) + + player.interactWithObject(data.treeId, 1) + + verifyAfter(action.complete()) { player.sendMessage(contains("do not have the required level")) } + } + + @Disabled("Mocking constructors is not supported in mockk. Update WoodcuttingAction to pass a chance value") + @ParameterizedTest + @ArgumentsSource(WoodcuttingTestDataProvider::class) + fun `Cutting a tree we have the skill to cut should eventually reward logs and experience`( + data: WoodcuttingTestData + ) { + // Mock RNG instances used by mining internally to determine success + // @todo - improve this so we don't have to mock Random + val rng = mockk() + every { rng.nextInt(100) } answers { 0 } + + player.inventory.add(Axe.BRONZE.id) + player.skillSet.setCurrentLevel(Skill.WOODCUTTING, data.tree.level) + + player.interactWithObject(data.treeId, 1) + + verifyAfter(action.ticks(1)) { player.sendMessage(contains("You swing your axe")) } + + after(action.ticks(Axe.BRONZE.pulses)) { + // @todo - cummulative ticks() calls? + verify { player.sendMessage("You manage to cut some ") } + assertEquals(data.tree.exp, player.skillSet.getExperience(Skill.WOODCUTTING)) + assertEquals(1, player.inventory.getAmount(data.tree.id)) + } + } + + private companion object { + @ItemDefinitions + fun logs() = woodcuttingTestData().map { + ItemDefinition(it.tree.id).also { it.name = "" } + } + + @ItemDefinitions + fun tools() = listOf(ItemDefinition(Axe.BRONZE.id)) + } +} \ No newline at end of file diff --git a/game/src/main/org/apollo/Server.java b/game/src/main/java/org/apollo/Server.java similarity index 100% rename from game/src/main/org/apollo/Server.java rename to game/src/main/java/org/apollo/Server.java diff --git a/game/src/main/org/apollo/ServerContext.java b/game/src/main/java/org/apollo/ServerContext.java similarity index 100% rename from game/src/main/org/apollo/ServerContext.java rename to game/src/main/java/org/apollo/ServerContext.java diff --git a/game/src/main/org/apollo/Service.java b/game/src/main/java/org/apollo/Service.java similarity index 100% rename from game/src/main/org/apollo/Service.java rename to game/src/main/java/org/apollo/Service.java diff --git a/game/src/main/org/apollo/ServiceManager.java b/game/src/main/java/org/apollo/ServiceManager.java similarity index 100% rename from game/src/main/org/apollo/ServiceManager.java rename to game/src/main/java/org/apollo/ServiceManager.java diff --git a/game/src/main/org/apollo/game/GameConstants.java b/game/src/main/java/org/apollo/game/GameConstants.java similarity index 100% rename from game/src/main/org/apollo/game/GameConstants.java rename to game/src/main/java/org/apollo/game/GameConstants.java diff --git a/game/src/main/org/apollo/game/GamePulseHandler.java b/game/src/main/java/org/apollo/game/GamePulseHandler.java similarity index 100% rename from game/src/main/org/apollo/game/GamePulseHandler.java rename to game/src/main/java/org/apollo/game/GamePulseHandler.java diff --git a/game/src/main/org/apollo/game/action/Action.java b/game/src/main/java/org/apollo/game/action/Action.java similarity index 100% rename from game/src/main/org/apollo/game/action/Action.java rename to game/src/main/java/org/apollo/game/action/Action.java diff --git a/game/src/main/org/apollo/game/action/DistancedAction.java b/game/src/main/java/org/apollo/game/action/DistancedAction.java similarity index 100% rename from game/src/main/org/apollo/game/action/DistancedAction.java rename to game/src/main/java/org/apollo/game/action/DistancedAction.java diff --git a/game/src/main/org/apollo/game/action/package-info.java b/game/src/main/java/org/apollo/game/action/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/action/package-info.java rename to game/src/main/java/org/apollo/game/action/package-info.java diff --git a/game/src/main/org/apollo/game/command/Command.java b/game/src/main/java/org/apollo/game/command/Command.java similarity index 100% rename from game/src/main/org/apollo/game/command/Command.java rename to game/src/main/java/org/apollo/game/command/Command.java diff --git a/game/src/main/org/apollo/game/command/CommandDispatcher.java b/game/src/main/java/org/apollo/game/command/CommandDispatcher.java similarity index 100% rename from game/src/main/org/apollo/game/command/CommandDispatcher.java rename to game/src/main/java/org/apollo/game/command/CommandDispatcher.java diff --git a/game/src/main/org/apollo/game/command/CommandListener.java b/game/src/main/java/org/apollo/game/command/CommandListener.java similarity index 100% rename from game/src/main/org/apollo/game/command/CommandListener.java rename to game/src/main/java/org/apollo/game/command/CommandListener.java diff --git a/game/src/main/org/apollo/game/command/CreditsCommandListener.java b/game/src/main/java/org/apollo/game/command/CreditsCommandListener.java similarity index 100% rename from game/src/main/org/apollo/game/command/CreditsCommandListener.java rename to game/src/main/java/org/apollo/game/command/CreditsCommandListener.java diff --git a/game/src/main/org/apollo/game/command/package-info.java b/game/src/main/java/org/apollo/game/command/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/command/package-info.java rename to game/src/main/java/org/apollo/game/command/package-info.java diff --git a/game/src/main/org/apollo/game/fs/decoder/SynchronousDecoder.java b/game/src/main/java/org/apollo/game/fs/decoder/SynchronousDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/fs/decoder/SynchronousDecoder.java rename to game/src/main/java/org/apollo/game/fs/decoder/SynchronousDecoder.java diff --git a/game/src/main/org/apollo/game/fs/decoder/SynchronousDecoderException.java b/game/src/main/java/org/apollo/game/fs/decoder/SynchronousDecoderException.java similarity index 100% rename from game/src/main/org/apollo/game/fs/decoder/SynchronousDecoderException.java rename to game/src/main/java/org/apollo/game/fs/decoder/SynchronousDecoderException.java diff --git a/game/src/main/org/apollo/game/fs/decoder/WorldMapDecoder.java b/game/src/main/java/org/apollo/game/fs/decoder/WorldMapDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/fs/decoder/WorldMapDecoder.java rename to game/src/main/java/org/apollo/game/fs/decoder/WorldMapDecoder.java diff --git a/game/src/main/org/apollo/game/fs/decoder/WorldObjectsDecoder.java b/game/src/main/java/org/apollo/game/fs/decoder/WorldObjectsDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/fs/decoder/WorldObjectsDecoder.java rename to game/src/main/java/org/apollo/game/fs/decoder/WorldObjectsDecoder.java diff --git a/game/src/main/org/apollo/game/fs/decoder/package-info.java b/game/src/main/java/org/apollo/game/fs/decoder/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/fs/decoder/package-info.java rename to game/src/main/java/org/apollo/game/fs/decoder/package-info.java diff --git a/game/src/main/org/apollo/game/io/EquipmentDefinitionParser.java b/game/src/main/java/org/apollo/game/io/EquipmentDefinitionParser.java similarity index 100% rename from game/src/main/org/apollo/game/io/EquipmentDefinitionParser.java rename to game/src/main/java/org/apollo/game/io/EquipmentDefinitionParser.java diff --git a/game/src/main/org/apollo/game/io/MessageHandlerChainSetParser.java b/game/src/main/java/org/apollo/game/io/MessageHandlerChainSetParser.java similarity index 100% rename from game/src/main/org/apollo/game/io/MessageHandlerChainSetParser.java rename to game/src/main/java/org/apollo/game/io/MessageHandlerChainSetParser.java diff --git a/game/src/main/org/apollo/game/io/PluginMetaDataParser.java b/game/src/main/java/org/apollo/game/io/PluginMetaDataParser.java similarity index 100% rename from game/src/main/org/apollo/game/io/PluginMetaDataParser.java rename to game/src/main/java/org/apollo/game/io/PluginMetaDataParser.java diff --git a/game/src/main/org/apollo/game/io/package-info.java b/game/src/main/java/org/apollo/game/io/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/io/package-info.java rename to game/src/main/java/org/apollo/game/io/package-info.java diff --git a/game/src/main/org/apollo/game/io/player/BinaryPlayerSerializer.java b/game/src/main/java/org/apollo/game/io/player/BinaryPlayerSerializer.java similarity index 100% rename from game/src/main/org/apollo/game/io/player/BinaryPlayerSerializer.java rename to game/src/main/java/org/apollo/game/io/player/BinaryPlayerSerializer.java diff --git a/game/src/main/org/apollo/game/io/player/DummyPlayerSerializer.java b/game/src/main/java/org/apollo/game/io/player/DummyPlayerSerializer.java similarity index 100% rename from game/src/main/org/apollo/game/io/player/DummyPlayerSerializer.java rename to game/src/main/java/org/apollo/game/io/player/DummyPlayerSerializer.java diff --git a/game/src/main/org/apollo/game/io/player/JdbcPlayerSerializer.java b/game/src/main/java/org/apollo/game/io/player/JdbcPlayerSerializer.java similarity index 100% rename from game/src/main/org/apollo/game/io/player/JdbcPlayerSerializer.java rename to game/src/main/java/org/apollo/game/io/player/JdbcPlayerSerializer.java diff --git a/game/src/main/org/apollo/game/io/player/PlayerLoaderResponse.java b/game/src/main/java/org/apollo/game/io/player/PlayerLoaderResponse.java similarity index 100% rename from game/src/main/org/apollo/game/io/player/PlayerLoaderResponse.java rename to game/src/main/java/org/apollo/game/io/player/PlayerLoaderResponse.java diff --git a/game/src/main/org/apollo/game/io/player/PlayerSerializer.java b/game/src/main/java/org/apollo/game/io/player/PlayerSerializer.java similarity index 100% rename from game/src/main/org/apollo/game/io/player/PlayerSerializer.java rename to game/src/main/java/org/apollo/game/io/player/PlayerSerializer.java diff --git a/game/src/main/org/apollo/game/io/player/package-info.java b/game/src/main/java/org/apollo/game/io/player/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/io/player/package-info.java rename to game/src/main/java/org/apollo/game/io/player/package-info.java diff --git a/game/src/main/org/apollo/game/login/PlayerLoaderWorker.java b/game/src/main/java/org/apollo/game/login/PlayerLoaderWorker.java similarity index 100% rename from game/src/main/org/apollo/game/login/PlayerLoaderWorker.java rename to game/src/main/java/org/apollo/game/login/PlayerLoaderWorker.java diff --git a/game/src/main/org/apollo/game/login/PlayerSaverWorker.java b/game/src/main/java/org/apollo/game/login/PlayerSaverWorker.java similarity index 100% rename from game/src/main/org/apollo/game/login/PlayerSaverWorker.java rename to game/src/main/java/org/apollo/game/login/PlayerSaverWorker.java diff --git a/game/src/main/org/apollo/game/login/package-info.java b/game/src/main/java/org/apollo/game/login/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/login/package-info.java rename to game/src/main/java/org/apollo/game/login/package-info.java diff --git a/game/src/main/org/apollo/game/message/handler/BankButtonMessageHandler.java b/game/src/main/java/org/apollo/game/message/handler/BankButtonMessageHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/BankButtonMessageHandler.java rename to game/src/main/java/org/apollo/game/message/handler/BankButtonMessageHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/BankMessageHandler.java b/game/src/main/java/org/apollo/game/message/handler/BankMessageHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/BankMessageHandler.java rename to game/src/main/java/org/apollo/game/message/handler/BankMessageHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/ClosedInterfaceMessageHandler.java b/game/src/main/java/org/apollo/game/message/handler/ClosedInterfaceMessageHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/ClosedInterfaceMessageHandler.java rename to game/src/main/java/org/apollo/game/message/handler/ClosedInterfaceMessageHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/CommandMessageHandler.java b/game/src/main/java/org/apollo/game/message/handler/CommandMessageHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/CommandMessageHandler.java rename to game/src/main/java/org/apollo/game/message/handler/CommandMessageHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/DialogueButtonHandler.java b/game/src/main/java/org/apollo/game/message/handler/DialogueButtonHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/DialogueButtonHandler.java rename to game/src/main/java/org/apollo/game/message/handler/DialogueButtonHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/DialogueContinueMessageHandler.java b/game/src/main/java/org/apollo/game/message/handler/DialogueContinueMessageHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/DialogueContinueMessageHandler.java rename to game/src/main/java/org/apollo/game/message/handler/DialogueContinueMessageHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/EnteredAmountMessageHandler.java b/game/src/main/java/org/apollo/game/message/handler/EnteredAmountMessageHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/EnteredAmountMessageHandler.java rename to game/src/main/java/org/apollo/game/message/handler/EnteredAmountMessageHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/EquipItemHandler.java b/game/src/main/java/org/apollo/game/message/handler/EquipItemHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/EquipItemHandler.java rename to game/src/main/java/org/apollo/game/message/handler/EquipItemHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/ItemOnItemVerificationHandler.java b/game/src/main/java/org/apollo/game/message/handler/ItemOnItemVerificationHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/ItemOnItemVerificationHandler.java rename to game/src/main/java/org/apollo/game/message/handler/ItemOnItemVerificationHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/ItemOnObjectVerificationHandler.java b/game/src/main/java/org/apollo/game/message/handler/ItemOnObjectVerificationHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/ItemOnObjectVerificationHandler.java rename to game/src/main/java/org/apollo/game/message/handler/ItemOnObjectVerificationHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/ItemVerificationHandler.java b/game/src/main/java/org/apollo/game/message/handler/ItemVerificationHandler.java similarity index 97% rename from game/src/main/org/apollo/game/message/handler/ItemVerificationHandler.java rename to game/src/main/java/org/apollo/game/message/handler/ItemVerificationHandler.java index 5f61d8043..a6e96c9c2 100644 --- a/game/src/main/org/apollo/game/message/handler/ItemVerificationHandler.java +++ b/game/src/main/java/org/apollo/game/message/handler/ItemVerificationHandler.java @@ -31,7 +31,7 @@ public interface InventorySupplier { * Gets the appropriate {@link Inventory}. * * @param player The {@link Player} who prompted the verification call. - * @return The inventory. Must not be {@code null}. + * @return The inventory, or {@code null} to immediately fail verification. */ Inventory getInventory(Player player); diff --git a/game/src/main/java/org/apollo/game/message/handler/MagicOnMobVerificationHandler.java b/game/src/main/java/org/apollo/game/message/handler/MagicOnMobVerificationHandler.java new file mode 100644 index 000000000..7df2ba757 --- /dev/null +++ b/game/src/main/java/org/apollo/game/message/handler/MagicOnMobVerificationHandler.java @@ -0,0 +1,51 @@ +package org.apollo.game.message.handler; + +import org.apollo.game.message.impl.MagicOnMobMessage; +import org.apollo.game.model.World; +import org.apollo.game.model.entity.EntityType; +import org.apollo.game.model.entity.Mob; +import org.apollo.game.model.entity.MobRepository; +import org.apollo.game.model.entity.Player; + +/** + * A verification {@link MessageHandler} for the {@link MagicOnMobMessage}. + * + * @author Tom + */ +public final class MagicOnMobVerificationHandler extends MessageHandler{ + + /** + * Creates the MessageListener. + * + * @param world The {@link World} the {@link MagicOnMobMessage} occurred in. + */ + public MagicOnMobVerificationHandler(World world) { + super(world); + } + + @Override + public void handle(Player player, MagicOnMobMessage message) { + int index = message.getIndex(); + MobRepository repository; + + if (message.getType() == EntityType.NPC) { + repository = world.getNpcRepository(); + } else if (message.getType() == EntityType.PLAYER) { + repository = world.getPlayerRepository(); + } else { + throw new IllegalStateException("Invalid mob type for message: " + message.toString()); + } + + if (index < 0 || index >= repository.capacity()) { + message.terminate(); + return; + } + + Mob mob = repository.get(index); + + if (mob == null || !player.getPosition().isWithinDistance(mob.getPosition(), player.getViewingDistance() + 1)) { + // +1 in case it was decremented after the player clicked the action. + message.terminate(); + } + } +} diff --git a/game/src/main/org/apollo/game/message/handler/MessageHandler.java b/game/src/main/java/org/apollo/game/message/handler/MessageHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/MessageHandler.java rename to game/src/main/java/org/apollo/game/message/handler/MessageHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/MessageHandlerChain.java b/game/src/main/java/org/apollo/game/message/handler/MessageHandlerChain.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/MessageHandlerChain.java rename to game/src/main/java/org/apollo/game/message/handler/MessageHandlerChain.java diff --git a/game/src/main/org/apollo/game/message/handler/MessageHandlerChainSet.java b/game/src/main/java/org/apollo/game/message/handler/MessageHandlerChainSet.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/MessageHandlerChainSet.java rename to game/src/main/java/org/apollo/game/message/handler/MessageHandlerChainSet.java diff --git a/game/src/main/org/apollo/game/message/handler/NpcActionVerificationHandler.java b/game/src/main/java/org/apollo/game/message/handler/NpcActionVerificationHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/NpcActionVerificationHandler.java rename to game/src/main/java/org/apollo/game/message/handler/NpcActionVerificationHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/ObjectActionVerificationHandler.java b/game/src/main/java/org/apollo/game/message/handler/ObjectActionVerificationHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/ObjectActionVerificationHandler.java rename to game/src/main/java/org/apollo/game/message/handler/ObjectActionVerificationHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/PlayerActionVerificationHandler.java b/game/src/main/java/org/apollo/game/message/handler/PlayerActionVerificationHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/PlayerActionVerificationHandler.java rename to game/src/main/java/org/apollo/game/message/handler/PlayerActionVerificationHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/PlayerDesignMessageHandler.java b/game/src/main/java/org/apollo/game/message/handler/PlayerDesignMessageHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/PlayerDesignMessageHandler.java rename to game/src/main/java/org/apollo/game/message/handler/PlayerDesignMessageHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/PlayerDesignVerificationHandler.java b/game/src/main/java/org/apollo/game/message/handler/PlayerDesignVerificationHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/PlayerDesignVerificationHandler.java rename to game/src/main/java/org/apollo/game/message/handler/PlayerDesignVerificationHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/PublicChatMessageHandler.java b/game/src/main/java/org/apollo/game/message/handler/PublicChatMessageHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/PublicChatMessageHandler.java rename to game/src/main/java/org/apollo/game/message/handler/PublicChatMessageHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/PublicChatVerificationHandler.java b/game/src/main/java/org/apollo/game/message/handler/PublicChatVerificationHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/PublicChatVerificationHandler.java rename to game/src/main/java/org/apollo/game/message/handler/PublicChatVerificationHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/RemoveEquippedItemHandler.java b/game/src/main/java/org/apollo/game/message/handler/RemoveEquippedItemHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/RemoveEquippedItemHandler.java rename to game/src/main/java/org/apollo/game/message/handler/RemoveEquippedItemHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/SwitchItemMessageHandler.java b/game/src/main/java/org/apollo/game/message/handler/SwitchItemMessageHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/SwitchItemMessageHandler.java rename to game/src/main/java/org/apollo/game/message/handler/SwitchItemMessageHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/WalkMessageHandler.java b/game/src/main/java/org/apollo/game/message/handler/WalkMessageHandler.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/WalkMessageHandler.java rename to game/src/main/java/org/apollo/game/message/handler/WalkMessageHandler.java diff --git a/game/src/main/org/apollo/game/message/handler/package-info.java b/game/src/main/java/org/apollo/game/message/handler/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/message/handler/package-info.java rename to game/src/main/java/org/apollo/game/message/handler/package-info.java diff --git a/game/src/main/org/apollo/game/message/impl/AddFriendMessage.java b/game/src/main/java/org/apollo/game/message/impl/AddFriendMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/AddFriendMessage.java rename to game/src/main/java/org/apollo/game/message/impl/AddFriendMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/AddIgnoreMessage.java b/game/src/main/java/org/apollo/game/message/impl/AddIgnoreMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/AddIgnoreMessage.java rename to game/src/main/java/org/apollo/game/message/impl/AddIgnoreMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ArrowKeyMessage.java b/game/src/main/java/org/apollo/game/message/impl/ArrowKeyMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ArrowKeyMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ArrowKeyMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ButtonMessage.java b/game/src/main/java/org/apollo/game/message/impl/ButtonMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ButtonMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ButtonMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ChatMessage.java b/game/src/main/java/org/apollo/game/message/impl/ChatMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ChatMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ChatMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ClearRegionMessage.java b/game/src/main/java/org/apollo/game/message/impl/ClearRegionMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ClearRegionMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ClearRegionMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/CloseInterfaceMessage.java b/game/src/main/java/org/apollo/game/message/impl/CloseInterfaceMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/CloseInterfaceMessage.java rename to game/src/main/java/org/apollo/game/message/impl/CloseInterfaceMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ClosedInterfaceMessage.java b/game/src/main/java/org/apollo/game/message/impl/ClosedInterfaceMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ClosedInterfaceMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ClosedInterfaceMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/CommandMessage.java b/game/src/main/java/org/apollo/game/message/impl/CommandMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/CommandMessage.java rename to game/src/main/java/org/apollo/game/message/impl/CommandMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ConfigMessage.java b/game/src/main/java/org/apollo/game/message/impl/ConfigMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ConfigMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ConfigMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/DialogueContinueMessage.java b/game/src/main/java/org/apollo/game/message/impl/DialogueContinueMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/DialogueContinueMessage.java rename to game/src/main/java/org/apollo/game/message/impl/DialogueContinueMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/DisplayCrossbonesMessage.java b/game/src/main/java/org/apollo/game/message/impl/DisplayCrossbonesMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/DisplayCrossbonesMessage.java rename to game/src/main/java/org/apollo/game/message/impl/DisplayCrossbonesMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/DisplayTabInterfaceMessage.java b/game/src/main/java/org/apollo/game/message/impl/DisplayTabInterfaceMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/DisplayTabInterfaceMessage.java rename to game/src/main/java/org/apollo/game/message/impl/DisplayTabInterfaceMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/EnterAmountMessage.java b/game/src/main/java/org/apollo/game/message/impl/EnterAmountMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/EnterAmountMessage.java rename to game/src/main/java/org/apollo/game/message/impl/EnterAmountMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/EnteredAmountMessage.java b/game/src/main/java/org/apollo/game/message/impl/EnteredAmountMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/EnteredAmountMessage.java rename to game/src/main/java/org/apollo/game/message/impl/EnteredAmountMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/FlaggedMouseEventMessage.java b/game/src/main/java/org/apollo/game/message/impl/FlaggedMouseEventMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/FlaggedMouseEventMessage.java rename to game/src/main/java/org/apollo/game/message/impl/FlaggedMouseEventMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/FlashTabInterfaceMessage.java b/game/src/main/java/org/apollo/game/message/impl/FlashTabInterfaceMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/FlashTabInterfaceMessage.java rename to game/src/main/java/org/apollo/game/message/impl/FlashTabInterfaceMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/FlashingTabClickedMessage.java b/game/src/main/java/org/apollo/game/message/impl/FlashingTabClickedMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/FlashingTabClickedMessage.java rename to game/src/main/java/org/apollo/game/message/impl/FlashingTabClickedMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/FocusUpdateMessage.java b/game/src/main/java/org/apollo/game/message/impl/FocusUpdateMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/FocusUpdateMessage.java rename to game/src/main/java/org/apollo/game/message/impl/FocusUpdateMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ForwardPrivateChatMessage.java b/game/src/main/java/org/apollo/game/message/impl/ForwardPrivateChatMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ForwardPrivateChatMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ForwardPrivateChatMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/FriendServerStatusMessage.java b/game/src/main/java/org/apollo/game/message/impl/FriendServerStatusMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/FriendServerStatusMessage.java rename to game/src/main/java/org/apollo/game/message/impl/FriendServerStatusMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/GroupedRegionUpdateMessage.java b/game/src/main/java/org/apollo/game/message/impl/GroupedRegionUpdateMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/GroupedRegionUpdateMessage.java rename to game/src/main/java/org/apollo/game/message/impl/GroupedRegionUpdateMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/HintIconMessage.java b/game/src/main/java/org/apollo/game/message/impl/HintIconMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/HintIconMessage.java rename to game/src/main/java/org/apollo/game/message/impl/HintIconMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/IdAssignmentMessage.java b/game/src/main/java/org/apollo/game/message/impl/IdAssignmentMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/IdAssignmentMessage.java rename to game/src/main/java/org/apollo/game/message/impl/IdAssignmentMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/IgnoreListMessage.java b/game/src/main/java/org/apollo/game/message/impl/IgnoreListMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/IgnoreListMessage.java rename to game/src/main/java/org/apollo/game/message/impl/IgnoreListMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/InventoryItemMessage.java b/game/src/main/java/org/apollo/game/message/impl/InventoryItemMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/InventoryItemMessage.java rename to game/src/main/java/org/apollo/game/message/impl/InventoryItemMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ItemActionMessage.java b/game/src/main/java/org/apollo/game/message/impl/ItemActionMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ItemActionMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ItemActionMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ItemOnItemMessage.java b/game/src/main/java/org/apollo/game/message/impl/ItemOnItemMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ItemOnItemMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ItemOnItemMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ItemOnNpcMessage.java b/game/src/main/java/org/apollo/game/message/impl/ItemOnNpcMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ItemOnNpcMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ItemOnNpcMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ItemOnObjectMessage.java b/game/src/main/java/org/apollo/game/message/impl/ItemOnObjectMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ItemOnObjectMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ItemOnObjectMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ItemOptionMessage.java b/game/src/main/java/org/apollo/game/message/impl/ItemOptionMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ItemOptionMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ItemOptionMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/KeepAliveMessage.java b/game/src/main/java/org/apollo/game/message/impl/KeepAliveMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/KeepAliveMessage.java rename to game/src/main/java/org/apollo/game/message/impl/KeepAliveMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/LogoutMessage.java b/game/src/main/java/org/apollo/game/message/impl/LogoutMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/LogoutMessage.java rename to game/src/main/java/org/apollo/game/message/impl/LogoutMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/MagicOnItemMessage.java b/game/src/main/java/org/apollo/game/message/impl/MagicOnItemMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/MagicOnItemMessage.java rename to game/src/main/java/org/apollo/game/message/impl/MagicOnItemMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/MagicOnMobMessage.java b/game/src/main/java/org/apollo/game/message/impl/MagicOnMobMessage.java similarity index 84% rename from game/src/main/org/apollo/game/message/impl/MagicOnMobMessage.java rename to game/src/main/java/org/apollo/game/message/impl/MagicOnMobMessage.java index f408f9b3f..fabdc16b8 100644 --- a/game/src/main/org/apollo/game/message/impl/MagicOnMobMessage.java +++ b/game/src/main/java/org/apollo/game/message/impl/MagicOnMobMessage.java @@ -1,5 +1,6 @@ package org.apollo.game.message.impl; +import com.google.common.base.MoreObjects; import org.apollo.game.model.entity.EntityType; import org.apollo.net.message.Message; @@ -65,4 +66,8 @@ public int getSpellId() { return spellId; } + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("type", getType()).add("index", getIndex()).add("spellId", getSpellId()).toString(); + } } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/message/impl/MagicOnNpcMessage.java b/game/src/main/java/org/apollo/game/message/impl/MagicOnNpcMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/MagicOnNpcMessage.java rename to game/src/main/java/org/apollo/game/message/impl/MagicOnNpcMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/MagicOnPlayerMessage.java b/game/src/main/java/org/apollo/game/message/impl/MagicOnPlayerMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/MagicOnPlayerMessage.java rename to game/src/main/java/org/apollo/game/message/impl/MagicOnPlayerMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/MobAnimationResetMessage.java b/game/src/main/java/org/apollo/game/message/impl/MobAnimationResetMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/MobAnimationResetMessage.java rename to game/src/main/java/org/apollo/game/message/impl/MobAnimationResetMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/MobHintIconMessage.java b/game/src/main/java/org/apollo/game/message/impl/MobHintIconMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/MobHintIconMessage.java rename to game/src/main/java/org/apollo/game/message/impl/MobHintIconMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/MouseClickedMessage.java b/game/src/main/java/org/apollo/game/message/impl/MouseClickedMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/MouseClickedMessage.java rename to game/src/main/java/org/apollo/game/message/impl/MouseClickedMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/NpcActionMessage.java b/game/src/main/java/org/apollo/game/message/impl/NpcActionMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/NpcActionMessage.java rename to game/src/main/java/org/apollo/game/message/impl/NpcActionMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/NpcSynchronizationMessage.java b/game/src/main/java/org/apollo/game/message/impl/NpcSynchronizationMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/NpcSynchronizationMessage.java rename to game/src/main/java/org/apollo/game/message/impl/NpcSynchronizationMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ObjectActionMessage.java b/game/src/main/java/org/apollo/game/message/impl/ObjectActionMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ObjectActionMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ObjectActionMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/OpenDialogueInterfaceMessage.java b/game/src/main/java/org/apollo/game/message/impl/OpenDialogueInterfaceMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/OpenDialogueInterfaceMessage.java rename to game/src/main/java/org/apollo/game/message/impl/OpenDialogueInterfaceMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/OpenDialogueOverlayMessage.java b/game/src/main/java/org/apollo/game/message/impl/OpenDialogueOverlayMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/OpenDialogueOverlayMessage.java rename to game/src/main/java/org/apollo/game/message/impl/OpenDialogueOverlayMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/OpenInterfaceMessage.java b/game/src/main/java/org/apollo/game/message/impl/OpenInterfaceMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/OpenInterfaceMessage.java rename to game/src/main/java/org/apollo/game/message/impl/OpenInterfaceMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/OpenInterfaceSidebarMessage.java b/game/src/main/java/org/apollo/game/message/impl/OpenInterfaceSidebarMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/OpenInterfaceSidebarMessage.java rename to game/src/main/java/org/apollo/game/message/impl/OpenInterfaceSidebarMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/OpenOverlayMessage.java b/game/src/main/java/org/apollo/game/message/impl/OpenOverlayMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/OpenOverlayMessage.java rename to game/src/main/java/org/apollo/game/message/impl/OpenOverlayMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/OpenSidebarMessage.java b/game/src/main/java/org/apollo/game/message/impl/OpenSidebarMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/OpenSidebarMessage.java rename to game/src/main/java/org/apollo/game/message/impl/OpenSidebarMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/PlayerActionMessage.java b/game/src/main/java/org/apollo/game/message/impl/PlayerActionMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/PlayerActionMessage.java rename to game/src/main/java/org/apollo/game/message/impl/PlayerActionMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/PlayerDesignMessage.java b/game/src/main/java/org/apollo/game/message/impl/PlayerDesignMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/PlayerDesignMessage.java rename to game/src/main/java/org/apollo/game/message/impl/PlayerDesignMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/PlayerSynchronizationMessage.java b/game/src/main/java/org/apollo/game/message/impl/PlayerSynchronizationMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/PlayerSynchronizationMessage.java rename to game/src/main/java/org/apollo/game/message/impl/PlayerSynchronizationMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/PositionHintIconMessage.java b/game/src/main/java/org/apollo/game/message/impl/PositionHintIconMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/PositionHintIconMessage.java rename to game/src/main/java/org/apollo/game/message/impl/PositionHintIconMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/PrivacyOptionMessage.java b/game/src/main/java/org/apollo/game/message/impl/PrivacyOptionMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/PrivacyOptionMessage.java rename to game/src/main/java/org/apollo/game/message/impl/PrivacyOptionMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/PrivateChatMessage.java b/game/src/main/java/org/apollo/game/message/impl/PrivateChatMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/PrivateChatMessage.java rename to game/src/main/java/org/apollo/game/message/impl/PrivateChatMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/PublicChatMessage.java b/game/src/main/java/org/apollo/game/message/impl/PublicChatMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/PublicChatMessage.java rename to game/src/main/java/org/apollo/game/message/impl/PublicChatMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/RegionChangeMessage.java b/game/src/main/java/org/apollo/game/message/impl/RegionChangeMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/RegionChangeMessage.java rename to game/src/main/java/org/apollo/game/message/impl/RegionChangeMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/RegionUpdateMessage.java b/game/src/main/java/org/apollo/game/message/impl/RegionUpdateMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/RegionUpdateMessage.java rename to game/src/main/java/org/apollo/game/message/impl/RegionUpdateMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/RemoveFriendMessage.java b/game/src/main/java/org/apollo/game/message/impl/RemoveFriendMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/RemoveFriendMessage.java rename to game/src/main/java/org/apollo/game/message/impl/RemoveFriendMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/RemoveIgnoreMessage.java b/game/src/main/java/org/apollo/game/message/impl/RemoveIgnoreMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/RemoveIgnoreMessage.java rename to game/src/main/java/org/apollo/game/message/impl/RemoveIgnoreMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/RemoveObjectMessage.java b/game/src/main/java/org/apollo/game/message/impl/RemoveObjectMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/RemoveObjectMessage.java rename to game/src/main/java/org/apollo/game/message/impl/RemoveObjectMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/RemoveTileItemMessage.java b/game/src/main/java/org/apollo/game/message/impl/RemoveTileItemMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/RemoveTileItemMessage.java rename to game/src/main/java/org/apollo/game/message/impl/RemoveTileItemMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ReportAbuseMessage.java b/game/src/main/java/org/apollo/game/message/impl/ReportAbuseMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ReportAbuseMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ReportAbuseMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SendFriendMessage.java b/game/src/main/java/org/apollo/game/message/impl/SendFriendMessage.java similarity index 81% rename from game/src/main/org/apollo/game/message/impl/SendFriendMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SendFriendMessage.java index 2900ab247..b86353c1a 100644 --- a/game/src/main/org/apollo/game/message/impl/SendFriendMessage.java +++ b/game/src/main/java/org/apollo/game/message/impl/SendFriendMessage.java @@ -27,7 +27,7 @@ public final class SendFriendMessage extends Message { */ public SendFriendMessage(String username, int world) { this.username = username; - this.world = world == 0 ? 0 : world + 9; + this.world = world; } /** @@ -48,4 +48,12 @@ public int getWorld() { return world; } + /** + * Gets the encoded world id to be sent to the client. + * + * @return The encoded world id. + */ + public int getEncodedWorld() { + return world == 0 ? 0 : world + 9; + } } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/message/impl/SendObjectMessage.java b/game/src/main/java/org/apollo/game/message/impl/SendObjectMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SendObjectMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SendObjectMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SendProjectileMessage.java b/game/src/main/java/org/apollo/game/message/impl/SendProjectileMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SendProjectileMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SendProjectileMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SendPublicTileItemMessage.java b/game/src/main/java/org/apollo/game/message/impl/SendPublicTileItemMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SendPublicTileItemMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SendPublicTileItemMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SendTileItemMessage.java b/game/src/main/java/org/apollo/game/message/impl/SendTileItemMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SendTileItemMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SendTileItemMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/ServerChatMessage.java b/game/src/main/java/org/apollo/game/message/impl/ServerChatMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/ServerChatMessage.java rename to game/src/main/java/org/apollo/game/message/impl/ServerChatMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SetPlayerActionMessage.java b/game/src/main/java/org/apollo/game/message/impl/SetPlayerActionMessage.java similarity index 76% rename from game/src/main/org/apollo/game/message/impl/SetPlayerActionMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SetPlayerActionMessage.java index 4ebae8c32..4db7ceefc 100644 --- a/game/src/main/org/apollo/game/message/impl/SetPlayerActionMessage.java +++ b/game/src/main/java/org/apollo/game/message/impl/SetPlayerActionMessage.java @@ -2,6 +2,8 @@ import org.apollo.net.message.Message; +import java.util.Objects; + /** * A {@link Message} sent by the client to add an action to the menu when a player right-clicks another. * @@ -37,8 +39,8 @@ public SetPlayerActionMessage(String text, int slot) { /** * Creates the set player action message. * - * @param text The action text. - * @param slot The menu slot. + * @param text The action text. + * @param slot The menu slot. * @param primaryInteraction Whether or not the action is the primary action. */ public SetPlayerActionMessage(String text, int slot, boolean primaryInteraction) { @@ -75,4 +77,20 @@ public boolean isPrimaryAction() { return primaryAction; } + @Override + public boolean equals(Object o) { + if (o instanceof SetPlayerActionMessage) { + SetPlayerActionMessage other = (SetPlayerActionMessage) o; + return slot == other.slot && primaryAction == other.primaryAction && Objects.equals(text, other.text); + + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hash(text, slot, primaryAction); + } + } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/message/impl/SetUpdatedRegionMessage.java b/game/src/main/java/org/apollo/game/message/impl/SetUpdatedRegionMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SetUpdatedRegionMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SetUpdatedRegionMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SetWidgetItemModelMessage.java b/game/src/main/java/org/apollo/game/message/impl/SetWidgetItemModelMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SetWidgetItemModelMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SetWidgetItemModelMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SetWidgetModelAnimationMessage.java b/game/src/main/java/org/apollo/game/message/impl/SetWidgetModelAnimationMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SetWidgetModelAnimationMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SetWidgetModelAnimationMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SetWidgetModelMessage.java b/game/src/main/java/org/apollo/game/message/impl/SetWidgetModelMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SetWidgetModelMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SetWidgetModelMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SetWidgetNpcModelMessage.java b/game/src/main/java/org/apollo/game/message/impl/SetWidgetNpcModelMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SetWidgetNpcModelMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SetWidgetNpcModelMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SetWidgetPlayerModelMessage.java b/game/src/main/java/org/apollo/game/message/impl/SetWidgetPlayerModelMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SetWidgetPlayerModelMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SetWidgetPlayerModelMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SetWidgetTextMessage.java b/game/src/main/java/org/apollo/game/message/impl/SetWidgetTextMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SetWidgetTextMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SetWidgetTextMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SetWidgetVisibilityMessage.java b/game/src/main/java/org/apollo/game/message/impl/SetWidgetVisibilityMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SetWidgetVisibilityMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SetWidgetVisibilityMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SpamPacketMessage.java b/game/src/main/java/org/apollo/game/message/impl/SpamPacketMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SpamPacketMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SpamPacketMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SwitchItemMessage.java b/game/src/main/java/org/apollo/game/message/impl/SwitchItemMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SwitchItemMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SwitchItemMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/SwitchTabInterfaceMessage.java b/game/src/main/java/org/apollo/game/message/impl/SwitchTabInterfaceMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/SwitchTabInterfaceMessage.java rename to game/src/main/java/org/apollo/game/message/impl/SwitchTabInterfaceMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/TakeTileItemMessage.java b/game/src/main/java/org/apollo/game/message/impl/TakeTileItemMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/TakeTileItemMessage.java rename to game/src/main/java/org/apollo/game/message/impl/TakeTileItemMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/UpdateItemsMessage.java b/game/src/main/java/org/apollo/game/message/impl/UpdateItemsMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/UpdateItemsMessage.java rename to game/src/main/java/org/apollo/game/message/impl/UpdateItemsMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/UpdateRunEnergyMessage.java b/game/src/main/java/org/apollo/game/message/impl/UpdateRunEnergyMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/UpdateRunEnergyMessage.java rename to game/src/main/java/org/apollo/game/message/impl/UpdateRunEnergyMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/UpdateSkillMessage.java b/game/src/main/java/org/apollo/game/message/impl/UpdateSkillMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/UpdateSkillMessage.java rename to game/src/main/java/org/apollo/game/message/impl/UpdateSkillMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/UpdateSlottedItemsMessage.java b/game/src/main/java/org/apollo/game/message/impl/UpdateSlottedItemsMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/UpdateSlottedItemsMessage.java rename to game/src/main/java/org/apollo/game/message/impl/UpdateSlottedItemsMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/UpdateTileItemMessage.java b/game/src/main/java/org/apollo/game/message/impl/UpdateTileItemMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/UpdateTileItemMessage.java rename to game/src/main/java/org/apollo/game/message/impl/UpdateTileItemMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/UpdateWeightMessage.java b/game/src/main/java/org/apollo/game/message/impl/UpdateWeightMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/UpdateWeightMessage.java rename to game/src/main/java/org/apollo/game/message/impl/UpdateWeightMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/WalkMessage.java b/game/src/main/java/org/apollo/game/message/impl/WalkMessage.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/WalkMessage.java rename to game/src/main/java/org/apollo/game/message/impl/WalkMessage.java diff --git a/game/src/main/org/apollo/game/message/impl/package-info.java b/game/src/main/java/org/apollo/game/message/impl/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/message/impl/package-info.java rename to game/src/main/java/org/apollo/game/message/impl/package-info.java diff --git a/game/src/main/org/apollo/game/model/Animation.java b/game/src/main/java/org/apollo/game/model/Animation.java similarity index 61% rename from game/src/main/org/apollo/game/model/Animation.java rename to game/src/main/java/org/apollo/game/model/Animation.java index 57df88a9b..0fe52750b 100644 --- a/game/src/main/org/apollo/game/model/Animation.java +++ b/game/src/main/java/org/apollo/game/model/Animation.java @@ -1,5 +1,8 @@ package org.apollo.game.model; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + /** * Represents an animation. * @@ -60,4 +63,28 @@ public int getId() { return id; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Animation animation = (Animation) o; + return delay == animation.delay && id == animation.id; + } + + @Override + public int hashCode() { + return Objects.hashCode(delay, id); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("delay", delay) + .add("id", id) + .toString(); + } } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/model/Appearance.java b/game/src/main/java/org/apollo/game/model/Appearance.java similarity index 100% rename from game/src/main/org/apollo/game/model/Appearance.java rename to game/src/main/java/org/apollo/game/model/Appearance.java diff --git a/game/src/main/org/apollo/game/model/Direction.java b/game/src/main/java/org/apollo/game/model/Direction.java similarity index 98% rename from game/src/main/org/apollo/game/model/Direction.java rename to game/src/main/java/org/apollo/game/model/Direction.java index dc03c5585..d1a13ffbd 100644 --- a/game/src/main/org/apollo/game/model/Direction.java +++ b/game/src/main/java/org/apollo/game/model/Direction.java @@ -85,7 +85,7 @@ public static Direction between(Position current, Position next) { int deltaX = next.getX() - current.getX(); int deltaY = next.getY() - current.getY(); - return fromDeltas(deltaX, deltaY); + return fromDeltas(Integer.signum(deltaX), Integer.signum(deltaY)); } /** diff --git a/game/src/main/org/apollo/game/model/Graphic.java b/game/src/main/java/org/apollo/game/model/Graphic.java similarity index 100% rename from game/src/main/org/apollo/game/model/Graphic.java rename to game/src/main/java/org/apollo/game/model/Graphic.java diff --git a/game/src/main/org/apollo/game/model/Item.java b/game/src/main/java/org/apollo/game/model/Item.java similarity index 100% rename from game/src/main/org/apollo/game/model/Item.java rename to game/src/main/java/org/apollo/game/model/Item.java diff --git a/game/src/main/org/apollo/game/model/Position.java b/game/src/main/java/org/apollo/game/model/Position.java similarity index 100% rename from game/src/main/org/apollo/game/model/Position.java rename to game/src/main/java/org/apollo/game/model/Position.java diff --git a/game/src/main/org/apollo/game/model/World.java b/game/src/main/java/org/apollo/game/model/World.java similarity index 98% rename from game/src/main/org/apollo/game/model/World.java rename to game/src/main/java/org/apollo/game/model/World.java index 991a41fc6..5d5adb0f8 100644 --- a/game/src/main/org/apollo/game/model/World.java +++ b/game/src/main/java/org/apollo/game/model/World.java @@ -299,7 +299,7 @@ public void register(Player player) { playerRepository.add(player); players.put(NameUtil.encodeBase37(username), player); - logger.info("Registered player: " + player + " [count=" + playerRepository.size() + "]"); + logger.finest("Registered player: " + player + " [count=" + playerRepository.size() + "]"); } /** @@ -359,7 +359,7 @@ public void unregister(final Player player) { region.removeEntity(player); playerRepository.remove(player); - logger.info("Unregistered player: " + player + " [count=" + playerRepository.size() + "]"); + logger.finest("Unregistered player: " + player + " [count=" + playerRepository.size() + "]"); } /** diff --git a/game/src/main/org/apollo/game/model/WorldConstants.java b/game/src/main/java/org/apollo/game/model/WorldConstants.java similarity index 100% rename from game/src/main/org/apollo/game/model/WorldConstants.java rename to game/src/main/java/org/apollo/game/model/WorldConstants.java diff --git a/game/src/main/org/apollo/game/model/area/EntityUpdateType.java b/game/src/main/java/org/apollo/game/model/area/EntityUpdateType.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/EntityUpdateType.java rename to game/src/main/java/org/apollo/game/model/area/EntityUpdateType.java diff --git a/game/src/main/org/apollo/game/model/area/Region.java b/game/src/main/java/org/apollo/game/model/area/Region.java similarity index 99% rename from game/src/main/org/apollo/game/model/area/Region.java rename to game/src/main/java/org/apollo/game/model/area/Region.java index 7eafb952c..df7a9a170 100644 --- a/game/src/main/org/apollo/game/model/area/Region.java +++ b/game/src/main/java/org/apollo/game/model/area/Region.java @@ -413,7 +413,9 @@ private void record(T entity, EntityUpdateT } else { // TODO should this really be possible? removedObjects.get(height).remove(inverse); } - } else if (update == EntityUpdateType.REMOVE && !type.isTransient()) { + } + + if (update == EntityUpdateType.REMOVE && !type.isTransient()) { updates.remove(inverse); } diff --git a/game/src/main/org/apollo/game/model/area/RegionCoordinates.java b/game/src/main/java/org/apollo/game/model/area/RegionCoordinates.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/RegionCoordinates.java rename to game/src/main/java/org/apollo/game/model/area/RegionCoordinates.java diff --git a/game/src/main/org/apollo/game/model/area/RegionListener.java b/game/src/main/java/org/apollo/game/model/area/RegionListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/RegionListener.java rename to game/src/main/java/org/apollo/game/model/area/RegionListener.java diff --git a/game/src/main/org/apollo/game/model/area/RegionRepository.java b/game/src/main/java/org/apollo/game/model/area/RegionRepository.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/RegionRepository.java rename to game/src/main/java/org/apollo/game/model/area/RegionRepository.java diff --git a/game/src/main/org/apollo/game/model/area/collision/CollisionFlag.java b/game/src/main/java/org/apollo/game/model/area/collision/CollisionFlag.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/collision/CollisionFlag.java rename to game/src/main/java/org/apollo/game/model/area/collision/CollisionFlag.java diff --git a/game/src/main/org/apollo/game/model/area/collision/CollisionManager.java b/game/src/main/java/org/apollo/game/model/area/collision/CollisionManager.java similarity index 84% rename from game/src/main/org/apollo/game/model/area/collision/CollisionManager.java rename to game/src/main/java/org/apollo/game/model/area/collision/CollisionManager.java index 4b54b36ac..d8a428ff7 100644 --- a/game/src/main/org/apollo/game/model/area/collision/CollisionManager.java +++ b/game/src/main/java/org/apollo/game/model/area/collision/CollisionManager.java @@ -1,19 +1,21 @@ package org.apollo.game.model.area.collision; import com.google.common.base.Preconditions; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import org.apollo.game.model.Direction; import org.apollo.game.model.Position; import org.apollo.game.model.area.Region; +import org.apollo.game.model.area.RegionCoordinates; import org.apollo.game.model.area.RegionRepository; import org.apollo.game.model.area.collision.CollisionUpdate.DirectionFlag; import org.apollo.game.model.entity.EntityType; import org.apollo.game.model.entity.obj.GameObject; import java.util.Collection; -import java.util.Comparator; +import java.util.HashSet; import java.util.Map; -import java.util.SortedSet; -import java.util.TreeSet; +import java.util.Set; import static org.apollo.game.model.entity.EntityType.DYNAMIC_OBJECT; import static org.apollo.game.model.entity.EntityType.STATIC_OBJECT; @@ -25,20 +27,14 @@ public final class CollisionManager { /** - * A comparator that sorts {@link Position}s by their X coordinate, then Y, then height. + * A {@code HashMultimap} of region coordinates mapped to positions where the tile is completely blocked. */ - private static final Comparator POSITION_COMPARATOR = - Comparator.comparingInt(Position::getX).thenComparingInt(Position::getY).thenComparingInt(Position::getHeight); + private final Multimap blocked = HashMultimap.create(); /** - * A {@code SortedSet} of positions where the tile is part of a bridged structure. + * A {@code HashSet} of positions where the tile is part of a bridged structure. */ - private final SortedSet bridges = new TreeSet<>(POSITION_COMPARATOR); - - /** - * A {@code SortedSet} of positions where the tile is completely blocked. - */ - private final SortedSet blocked = new TreeSet<>(POSITION_COMPARATOR); + private final Set bridges = new HashSet<>(); /** * The {@link RegionRepository} used to lookup {@link CollisionMatrix} objects. @@ -69,31 +65,33 @@ public void build(boolean rebuilding) { } } - CollisionUpdate.Builder builder = new CollisionUpdate.Builder(); - builder.type(CollisionUpdateType.ADDING); + regions.getRegions().forEach(region -> { + CollisionUpdate.Builder builder = new CollisionUpdate.Builder(); + builder.type(CollisionUpdateType.ADDING); - for (Position tile : blocked) { - int x = tile.getX(), y = tile.getY(); - int height = tile.getHeight(); + blocked.get(region.getCoordinates()).forEach(tile -> { + int x = tile.getX(), y = tile.getY(); + int height = tile.getHeight(); - if (bridges.contains(new Position(x, y, 1))) { - height--; - } + if (bridges.contains(new Position(x, y, 1))) { + height--; + } - if (height >= 0) { - builder.tile(new Position(x, y, height), false, Direction.NESW); - } - } + if (height >= 0) { + builder.tile(new Position(x, y, height), false, Direction.NESW); + } + }); - apply(builder.build()); + apply(builder.build()); - for (Region region : regions.getRegions()) { CollisionUpdate.Builder objects = new CollisionUpdate.Builder(); objects.type(CollisionUpdateType.ADDING); - region.getEntities(STATIC_OBJECT, DYNAMIC_OBJECT).forEach(entity -> objects.object((GameObject) entity)); + region.getEntities(STATIC_OBJECT, DYNAMIC_OBJECT) + .forEach(entity -> objects.object((GameObject) entity)); + apply(objects.build()); - } + }); } /** @@ -255,7 +253,7 @@ private void flag(CollisionUpdateType type, CollisionMatrix matrix, int localX, * @param position The {@link Position} of the tile. */ public void block(Position position) { - blocked.add(position); + blocked.put(position.getRegionCoordinates(), position); } /** diff --git a/game/src/main/org/apollo/game/model/area/collision/CollisionMatrix.java b/game/src/main/java/org/apollo/game/model/area/collision/CollisionMatrix.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/collision/CollisionMatrix.java rename to game/src/main/java/org/apollo/game/model/area/collision/CollisionMatrix.java diff --git a/game/src/main/org/apollo/game/model/area/collision/CollisionUpdate.java b/game/src/main/java/org/apollo/game/model/area/collision/CollisionUpdate.java similarity index 95% rename from game/src/main/org/apollo/game/model/area/collision/CollisionUpdate.java rename to game/src/main/java/org/apollo/game/model/area/collision/CollisionUpdate.java index cd8fb73ab..1c4047842 100644 --- a/game/src/main/org/apollo/game/model/area/collision/CollisionUpdate.java +++ b/game/src/main/java/org/apollo/game/model/area/collision/CollisionUpdate.java @@ -195,23 +195,16 @@ public void object(GameObject object) { } int x = position.getX(), y = position.getY(), height = position.getHeight(); - int width = definition.getWidth(), length = definition.getLength(); boolean impenetrable = definition.isImpenetrable(); int orientation = object.getOrientation(); - // north / south for walls, north east / south west for corners - if (orientation == 1 || orientation == 3) { - width = definition.getLength(); - length = definition.getWidth(); - } - if (type == FLOOR_DECORATION.getValue()) { if (definition.isInteractive() && definition.isSolid()) { tile(new Position(x, y, height), impenetrable, Direction.NESW); } } else if (type >= DIAGONAL_WALL.getValue() && type < FLOOR_DECORATION.getValue()) { - for (int dx = 0; dx < width; dx++) { - for (int dy = 0; dy < length; dy++) { + for (int dx = 0; dx < object.getWidth(); dx++) { + for (int dy = 0; dy < object.getLength(); dy++) { tile(new Position(x + dx, y + dy, height), impenetrable, Direction.NESW); } } diff --git a/game/src/main/org/apollo/game/model/area/collision/CollisionUpdateListener.java b/game/src/main/java/org/apollo/game/model/area/collision/CollisionUpdateListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/collision/CollisionUpdateListener.java rename to game/src/main/java/org/apollo/game/model/area/collision/CollisionUpdateListener.java diff --git a/game/src/main/org/apollo/game/model/area/collision/CollisionUpdateType.java b/game/src/main/java/org/apollo/game/model/area/collision/CollisionUpdateType.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/collision/CollisionUpdateType.java rename to game/src/main/java/org/apollo/game/model/area/collision/CollisionUpdateType.java diff --git a/game/src/main/org/apollo/game/model/area/collision/package-info.java b/game/src/main/java/org/apollo/game/model/area/collision/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/collision/package-info.java rename to game/src/main/java/org/apollo/game/model/area/collision/package-info.java diff --git a/game/src/main/org/apollo/game/model/area/package-info.java b/game/src/main/java/org/apollo/game/model/area/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/package-info.java rename to game/src/main/java/org/apollo/game/model/area/package-info.java diff --git a/game/src/main/org/apollo/game/model/area/update/GroupableEntity.java b/game/src/main/java/org/apollo/game/model/area/update/GroupableEntity.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/update/GroupableEntity.java rename to game/src/main/java/org/apollo/game/model/area/update/GroupableEntity.java diff --git a/game/src/main/org/apollo/game/model/area/update/ItemUpdateOperation.java b/game/src/main/java/org/apollo/game/model/area/update/ItemUpdateOperation.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/update/ItemUpdateOperation.java rename to game/src/main/java/org/apollo/game/model/area/update/ItemUpdateOperation.java diff --git a/game/src/main/org/apollo/game/model/area/update/ObjectUpdateOperation.java b/game/src/main/java/org/apollo/game/model/area/update/ObjectUpdateOperation.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/update/ObjectUpdateOperation.java rename to game/src/main/java/org/apollo/game/model/area/update/ObjectUpdateOperation.java diff --git a/game/src/main/org/apollo/game/model/area/update/ProjectileUpdateOperation.java b/game/src/main/java/org/apollo/game/model/area/update/ProjectileUpdateOperation.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/update/ProjectileUpdateOperation.java rename to game/src/main/java/org/apollo/game/model/area/update/ProjectileUpdateOperation.java diff --git a/game/src/main/org/apollo/game/model/area/update/UpdateOperation.java b/game/src/main/java/org/apollo/game/model/area/update/UpdateOperation.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/update/UpdateOperation.java rename to game/src/main/java/org/apollo/game/model/area/update/UpdateOperation.java diff --git a/game/src/main/org/apollo/game/model/area/update/package-info.java b/game/src/main/java/org/apollo/game/model/area/update/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/area/update/package-info.java rename to game/src/main/java/org/apollo/game/model/area/update/package-info.java diff --git a/game/src/main/org/apollo/game/model/entity/Entity.java b/game/src/main/java/org/apollo/game/model/entity/Entity.java similarity index 69% rename from game/src/main/org/apollo/game/model/entity/Entity.java rename to game/src/main/java/org/apollo/game/model/entity/Entity.java index 97e35349f..3f5a955a9 100644 --- a/game/src/main/org/apollo/game/model/entity/Entity.java +++ b/game/src/main/java/org/apollo/game/model/entity/Entity.java @@ -20,6 +20,11 @@ public abstract class Entity { */ protected final World world; + /** + * The EntityBounds for this Entity. + */ + private EntityBounds bounds; + /** * Creates the Entity. * @@ -34,6 +39,20 @@ public Entity(World world, Position position) { @Override public abstract boolean equals(Object obj); + /** + * Gets the {@link EntityBounds} for this Entity. + * + * @return The EntityBounds. + */ + public EntityBounds getBounds() { + + if(bounds == null) { + bounds = new EntityBounds(this); + } + + return bounds; + } + /** * Gets the {@link Position} of this Entity. * @@ -59,6 +78,20 @@ public World getWorld() { */ public abstract EntityType getEntityType(); + /** + * Gets the length of this Entity. + * + * @return The length. + */ + public abstract int getLength(); + + /** + * Gets the width of this Entity. + * + * @return The width. + */ + public abstract int getWidth(); + @Override public abstract int hashCode(); diff --git a/game/src/main/java/org/apollo/game/model/entity/EntityBounds.java b/game/src/main/java/org/apollo/game/model/entity/EntityBounds.java new file mode 100644 index 000000000..3d4c3a9ae --- /dev/null +++ b/game/src/main/java/org/apollo/game/model/entity/EntityBounds.java @@ -0,0 +1,48 @@ +package org.apollo.game.model.entity; + +import org.apollo.game.model.Position; + +/** + * The bounds of an {@link Entity}. + * + * @author Steve Soltys + */ +public class EntityBounds { + + /** + * The {@link Entity}. + */ + private final Entity entity; + + /** + * Creates an EntityBounds. + * + * @param entity The entity. + */ + EntityBounds(Entity entity) { + this.entity = entity; + } + + /** + * Checks whether the given position is within the Entity's bounds. + * + * @param position The position. + * @return A flag indicating whether or not the position exists within the Entity's bounds. + */ + public boolean contains(Position position) { + int positionX = position.getX(); + int positionY = position.getY(); + int positionHeight = position.getHeight(); + + int entityX = entity.getPosition().getX(); + int entityY = entity.getPosition().getY(); + int entityHeight = entity.getPosition().getHeight(); + + int width = entity.getWidth(); + int length = entity.getLength(); + + return positionX >= entityX && positionX < entityX + width && + positionY >= entityY && positionY < entityY + length && + positionHeight == entityHeight; + } +} diff --git a/game/src/main/org/apollo/game/model/entity/EntityType.java b/game/src/main/java/org/apollo/game/model/entity/EntityType.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/EntityType.java rename to game/src/main/java/org/apollo/game/model/entity/EntityType.java diff --git a/game/src/main/org/apollo/game/model/entity/EquipmentConstants.java b/game/src/main/java/org/apollo/game/model/entity/EquipmentConstants.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/EquipmentConstants.java rename to game/src/main/java/org/apollo/game/model/entity/EquipmentConstants.java diff --git a/game/src/main/org/apollo/game/model/entity/GroundItem.java b/game/src/main/java/org/apollo/game/model/entity/GroundItem.java similarity index 96% rename from game/src/main/org/apollo/game/model/entity/GroundItem.java rename to game/src/main/java/org/apollo/game/model/entity/GroundItem.java index a81c612c1..a338d4149 100644 --- a/game/src/main/org/apollo/game/model/entity/GroundItem.java +++ b/game/src/main/java/org/apollo/game/model/entity/GroundItem.java @@ -80,6 +80,16 @@ public EntityType getEntityType() { return EntityType.GROUND_ITEM; } + @Override + public int getLength() { + return 1; + } + + @Override + public int getWidth() { + return 1; + } + /** * Gets the {@link Item} displayed on the ground. * diff --git a/game/src/main/org/apollo/game/model/entity/Mob.java b/game/src/main/java/org/apollo/game/model/entity/Mob.java similarity index 96% rename from game/src/main/org/apollo/game/model/entity/Mob.java rename to game/src/main/java/org/apollo/game/model/entity/Mob.java index 82cdba964..ec09bb9cb 100644 --- a/game/src/main/org/apollo/game/model/entity/Mob.java +++ b/game/src/main/java/org/apollo/game/model/entity/Mob.java @@ -235,6 +235,15 @@ public final Direction getFirstDirection() { return firstDirection; } + /** + * Gets the current action, if any, of this mob. + * + * @return The action. + */ + public final Action getAction() { + return action; + } + /** * Gets the index of this mob. * @@ -327,6 +336,25 @@ public final WalkingQueue getWalkingQueue() { return walkingQueue; } + @Override + public int getLength() { + return definition.map(NpcDefinition::getSize).orElse(1); + } + + @Override + public int getWidth() { + return definition.map(NpcDefinition::getSize).orElse(1); + } + + /** + * Check whether this mob has a current active {@link Action}. + * + * @return {@code true} if this mob has a non-null {@link Action}. + */ + public final boolean hasAction() { + return action != null; + } + /** * Returns whether or not this mob has an {@link NpcDefinition}. * diff --git a/game/src/main/org/apollo/game/model/entity/MobRepository.java b/game/src/main/java/org/apollo/game/model/entity/MobRepository.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/MobRepository.java rename to game/src/main/java/org/apollo/game/model/entity/MobRepository.java diff --git a/game/src/main/org/apollo/game/model/entity/Npc.java b/game/src/main/java/org/apollo/game/model/entity/Npc.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/Npc.java rename to game/src/main/java/org/apollo/game/model/entity/Npc.java diff --git a/game/src/main/org/apollo/game/model/entity/Player.java b/game/src/main/java/org/apollo/game/model/entity/Player.java similarity index 98% rename from game/src/main/org/apollo/game/model/entity/Player.java rename to game/src/main/java/org/apollo/game/model/entity/Player.java index 2a323b109..76c06db95 100644 --- a/game/src/main/org/apollo/game/model/entity/Player.java +++ b/game/src/main/java/org/apollo/game/model/entity/Player.java @@ -28,6 +28,7 @@ import org.apollo.game.model.entity.attr.AttributeMap; import org.apollo.game.model.entity.attr.AttributePersistence; import org.apollo.game.model.entity.attr.NumericalAttribute; +import org.apollo.game.model.entity.attr.BooleanAttribute; import org.apollo.game.model.entity.obj.DynamicGameObject; import org.apollo.game.model.entity.setting.MembershipStatus; import org.apollo.game.model.entity.setting.PrivacyState; @@ -943,6 +944,22 @@ public void setWithdrawingNotes(boolean withdrawingNotes) { this.withdrawingNotes = withdrawingNotes; } + /** + * Ban the player. + */ + public void ban() { + attributes.set("banned", new BooleanAttribute(true)); + } + + /** + * Sets the mute status of a player. + * + * @param muted Whether the player is muted. + */ + public void setMuted(boolean muted) { + attributes.set("muted", new BooleanAttribute(muted)); + } + @Override public void shout(String message, boolean chatOnly) { blockSet.add(SynchronizationBlock.createForceChatBlock(chatOnly ? message : '~' + message)); diff --git a/game/src/main/org/apollo/game/model/entity/Projectile.java b/game/src/main/java/org/apollo/game/model/entity/Projectile.java similarity index 99% rename from game/src/main/org/apollo/game/model/entity/Projectile.java rename to game/src/main/java/org/apollo/game/model/entity/Projectile.java index db5459d89..a05f218b1 100644 --- a/game/src/main/org/apollo/game/model/entity/Projectile.java +++ b/game/src/main/java/org/apollo/game/model/entity/Projectile.java @@ -414,6 +414,16 @@ public EntityType getEntityType() { return EntityType.PROJECTILE; } + @Override + public int getLength() { + return 1; + } + + @Override + public int getWidth() { + return 1; + } + @Override public int hashCode() { return Objects.hashCode(position, destination, delay, lifetime, target, startHeight, diff --git a/game/src/main/org/apollo/game/model/entity/Skill.java b/game/src/main/java/org/apollo/game/model/entity/Skill.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/Skill.java rename to game/src/main/java/org/apollo/game/model/entity/Skill.java diff --git a/game/src/main/org/apollo/game/model/entity/SkillSet.java b/game/src/main/java/org/apollo/game/model/entity/SkillSet.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/SkillSet.java rename to game/src/main/java/org/apollo/game/model/entity/SkillSet.java diff --git a/game/src/main/org/apollo/game/model/entity/WalkingQueue.java b/game/src/main/java/org/apollo/game/model/entity/WalkingQueue.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/WalkingQueue.java rename to game/src/main/java/org/apollo/game/model/entity/WalkingQueue.java diff --git a/game/src/main/org/apollo/game/model/entity/attr/Attribute.java b/game/src/main/java/org/apollo/game/model/entity/attr/Attribute.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/attr/Attribute.java rename to game/src/main/java/org/apollo/game/model/entity/attr/Attribute.java diff --git a/game/src/main/org/apollo/game/model/entity/attr/AttributeDefinition.java b/game/src/main/java/org/apollo/game/model/entity/attr/AttributeDefinition.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/attr/AttributeDefinition.java rename to game/src/main/java/org/apollo/game/model/entity/attr/AttributeDefinition.java diff --git a/game/src/main/org/apollo/game/model/entity/attr/AttributeMap.java b/game/src/main/java/org/apollo/game/model/entity/attr/AttributeMap.java similarity index 96% rename from game/src/main/org/apollo/game/model/entity/attr/AttributeMap.java rename to game/src/main/java/org/apollo/game/model/entity/attr/AttributeMap.java index 0ee12779d..c521e7b79 100644 --- a/game/src/main/org/apollo/game/model/entity/attr/AttributeMap.java +++ b/game/src/main/java/org/apollo/game/model/entity/attr/AttributeMap.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.Map; -import org.jruby.RubySymbol; import com.google.common.base.Preconditions; @@ -118,8 +117,6 @@ private Attribute createAttribute(T value, AttributeType type) { return new NumericalAttribute((Double) value); case STRING: return new StringAttribute((String) value); - case SYMBOL: - return new StringAttribute(((RubySymbol) value).asJavaString(), true); case BOOLEAN: return new BooleanAttribute((Boolean) value); } diff --git a/game/src/main/org/apollo/game/model/entity/attr/AttributePersistence.java b/game/src/main/java/org/apollo/game/model/entity/attr/AttributePersistence.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/attr/AttributePersistence.java rename to game/src/main/java/org/apollo/game/model/entity/attr/AttributePersistence.java diff --git a/game/src/main/org/apollo/game/model/entity/attr/AttributeType.java b/game/src/main/java/org/apollo/game/model/entity/attr/AttributeType.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/attr/AttributeType.java rename to game/src/main/java/org/apollo/game/model/entity/attr/AttributeType.java diff --git a/game/src/main/org/apollo/game/model/entity/attr/BooleanAttribute.java b/game/src/main/java/org/apollo/game/model/entity/attr/BooleanAttribute.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/attr/BooleanAttribute.java rename to game/src/main/java/org/apollo/game/model/entity/attr/BooleanAttribute.java diff --git a/game/src/main/org/apollo/game/model/entity/attr/NumericalAttribute.java b/game/src/main/java/org/apollo/game/model/entity/attr/NumericalAttribute.java similarity index 84% rename from game/src/main/org/apollo/game/model/entity/attr/NumericalAttribute.java rename to game/src/main/java/org/apollo/game/model/entity/attr/NumericalAttribute.java index 0aa287006..48b53f469 100644 --- a/game/src/main/org/apollo/game/model/entity/attr/NumericalAttribute.java +++ b/game/src/main/java/org/apollo/game/model/entity/attr/NumericalAttribute.java @@ -30,13 +30,13 @@ public NumericalAttribute(Number value) { @Override public byte[] encode() { - long encoded = type == AttributeType.DOUBLE ? Double.doubleToLongBits((double) value) : (long) value; + long encoded = type == AttributeType.DOUBLE ? Double.doubleToLongBits(value.doubleValue()) : value.longValue(); return Longs.toByteArray(encoded); } @Override public String toString() { - return type == AttributeType.DOUBLE ? Double.toString((double) value) : Long.toString((long) value); + return type == AttributeType.DOUBLE ? Double.toString(value.doubleValue()) : Long.toString(value.longValue()); } } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/model/entity/attr/StringAttribute.java b/game/src/main/java/org/apollo/game/model/entity/attr/StringAttribute.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/attr/StringAttribute.java rename to game/src/main/java/org/apollo/game/model/entity/attr/StringAttribute.java diff --git a/game/src/main/org/apollo/game/model/entity/attr/package-info.java b/game/src/main/java/org/apollo/game/model/entity/attr/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/attr/package-info.java rename to game/src/main/java/org/apollo/game/model/entity/attr/package-info.java diff --git a/game/src/main/org/apollo/game/model/entity/obj/DynamicGameObject.java b/game/src/main/java/org/apollo/game/model/entity/obj/DynamicGameObject.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/obj/DynamicGameObject.java rename to game/src/main/java/org/apollo/game/model/entity/obj/DynamicGameObject.java diff --git a/game/src/main/org/apollo/game/model/entity/obj/GameObject.java b/game/src/main/java/org/apollo/game/model/entity/obj/GameObject.java similarity index 72% rename from game/src/main/org/apollo/game/model/entity/obj/GameObject.java rename to game/src/main/java/org/apollo/game/model/entity/obj/GameObject.java index cdb0f749d..1ceb76897 100644 --- a/game/src/main/org/apollo/game/model/entity/obj/GameObject.java +++ b/game/src/main/java/org/apollo/game/model/entity/obj/GameObject.java @@ -2,6 +2,7 @@ import org.apollo.cache.def.ObjectDefinition; import org.apollo.game.model.Position; +import org.apollo.game.model.Direction; import org.apollo.game.model.World; import org.apollo.game.model.area.EntityUpdateType; import org.apollo.game.model.area.Region; @@ -12,6 +13,9 @@ import com.google.common.base.MoreObjects; +import static org.apollo.game.model.entity.obj.ObjectType.RECTANGULAR_CORNER; +import static org.apollo.game.model.entity.obj.ObjectType.TRIANGULAR_CORNER; + /** * Represents an object in the game world. * @@ -85,6 +89,34 @@ public int getType() { return packed >> 2 & 0x3F; } + @Override + public int getLength() { + return isRotated() ? getDefinition().getWidth() : getDefinition().getLength(); + } + + @Override + public int getWidth() { + return isRotated() ? getDefinition().getLength() : getDefinition().getWidth(); + } + + /** + * Returns whether or not this GameObject's orientation is rotated {@link Direction#WEST} or {@link Direction#EAST}. + * + * @return {@code true} iff this GameObject's orientation is rotated. + */ + public boolean isRotated() { + int orientation = getOrientation(); + int type = getType(); + Direction direction = Direction.WNES[orientation]; + + if (type == TRIANGULAR_CORNER.getValue() || type == RECTANGULAR_CORNER.getValue()) { + direction = Direction.WNES_DIAGONAL[orientation]; + } + + return direction == Direction.NORTH || direction == Direction.SOUTH + || direction == Direction.NORTH_WEST || direction == Direction.SOUTH_EAST; + } + @Override public int hashCode() { return packed; @@ -109,5 +141,4 @@ public ObjectUpdateOperation toUpdateOperation(Region region, EntityUpdateType o * @return {@code true} if the Player can see this GameObject, {@code false} if not. */ public abstract boolean viewableBy(Player player, World world); - } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/model/entity/obj/ObjectGroup.java b/game/src/main/java/org/apollo/game/model/entity/obj/ObjectGroup.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/obj/ObjectGroup.java rename to game/src/main/java/org/apollo/game/model/entity/obj/ObjectGroup.java diff --git a/game/src/main/org/apollo/game/model/entity/obj/ObjectType.java b/game/src/main/java/org/apollo/game/model/entity/obj/ObjectType.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/obj/ObjectType.java rename to game/src/main/java/org/apollo/game/model/entity/obj/ObjectType.java diff --git a/game/src/main/org/apollo/game/model/entity/obj/StaticGameObject.java b/game/src/main/java/org/apollo/game/model/entity/obj/StaticGameObject.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/obj/StaticGameObject.java rename to game/src/main/java/org/apollo/game/model/entity/obj/StaticGameObject.java diff --git a/game/src/main/org/apollo/game/model/entity/obj/package-info.java b/game/src/main/java/org/apollo/game/model/entity/obj/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/obj/package-info.java rename to game/src/main/java/org/apollo/game/model/entity/obj/package-info.java diff --git a/game/src/main/org/apollo/game/model/entity/package-info.java b/game/src/main/java/org/apollo/game/model/entity/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/package-info.java rename to game/src/main/java/org/apollo/game/model/entity/package-info.java diff --git a/game/src/main/org/apollo/game/model/entity/path/AStarPathfindingAlgorithm.java b/game/src/main/java/org/apollo/game/model/entity/path/AStarPathfindingAlgorithm.java similarity index 96% rename from game/src/main/org/apollo/game/model/entity/path/AStarPathfindingAlgorithm.java rename to game/src/main/java/org/apollo/game/model/entity/path/AStarPathfindingAlgorithm.java index 0f2f1bc7a..2b8482f52 100644 --- a/game/src/main/org/apollo/game/model/entity/path/AStarPathfindingAlgorithm.java +++ b/game/src/main/java/org/apollo/game/model/entity/path/AStarPathfindingAlgorithm.java @@ -11,8 +11,6 @@ import org.apollo.game.model.Direction; import org.apollo.game.model.Position; -import org.apollo.game.model.World; -import org.apollo.game.model.area.RegionRepository; import org.apollo.game.model.area.collision.CollisionManager; /** @@ -76,7 +74,7 @@ public Deque find(Position origin, Position target) { continue; } - Position adjacent = new Position(nextX, nextY); + Position adjacent = new Position(nextX, nextY, position.getHeight()); Direction direction = Direction.between(adjacent, position); if (traversable(adjacent, direction)) { Node node = nodes.computeIfAbsent(adjacent, Node::new); diff --git a/game/src/main/org/apollo/game/model/entity/path/ChebyshevHeuristic.java b/game/src/main/java/org/apollo/game/model/entity/path/ChebyshevHeuristic.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/path/ChebyshevHeuristic.java rename to game/src/main/java/org/apollo/game/model/entity/path/ChebyshevHeuristic.java diff --git a/game/src/main/org/apollo/game/model/entity/path/EuclideanHeuristic.java b/game/src/main/java/org/apollo/game/model/entity/path/EuclideanHeuristic.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/path/EuclideanHeuristic.java rename to game/src/main/java/org/apollo/game/model/entity/path/EuclideanHeuristic.java diff --git a/game/src/main/org/apollo/game/model/entity/path/Heuristic.java b/game/src/main/java/org/apollo/game/model/entity/path/Heuristic.java similarity index 88% rename from game/src/main/org/apollo/game/model/entity/path/Heuristic.java rename to game/src/main/java/org/apollo/game/model/entity/path/Heuristic.java index 468c65bde..45390aafe 100644 --- a/game/src/main/org/apollo/game/model/entity/path/Heuristic.java +++ b/game/src/main/java/org/apollo/game/model/entity/path/Heuristic.java @@ -7,7 +7,7 @@ * * @author Major */ -abstract class Heuristic { +public abstract class Heuristic { /** * Estimates the value for this heuristic. diff --git a/game/src/main/org/apollo/game/model/entity/path/ManhattanHeuristic.java b/game/src/main/java/org/apollo/game/model/entity/path/ManhattanHeuristic.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/path/ManhattanHeuristic.java rename to game/src/main/java/org/apollo/game/model/entity/path/ManhattanHeuristic.java diff --git a/game/src/main/org/apollo/game/model/entity/path/Node.java b/game/src/main/java/org/apollo/game/model/entity/path/Node.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/path/Node.java rename to game/src/main/java/org/apollo/game/model/entity/path/Node.java diff --git a/game/src/main/org/apollo/game/model/entity/path/PathfindingAlgorithm.java b/game/src/main/java/org/apollo/game/model/entity/path/PathfindingAlgorithm.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/path/PathfindingAlgorithm.java rename to game/src/main/java/org/apollo/game/model/entity/path/PathfindingAlgorithm.java diff --git a/game/src/main/org/apollo/game/model/entity/path/SimplePathfindingAlgorithm.java b/game/src/main/java/org/apollo/game/model/entity/path/SimplePathfindingAlgorithm.java similarity index 97% rename from game/src/main/org/apollo/game/model/entity/path/SimplePathfindingAlgorithm.java rename to game/src/main/java/org/apollo/game/model/entity/path/SimplePathfindingAlgorithm.java index 421315f83..1affd1278 100644 --- a/game/src/main/org/apollo/game/model/entity/path/SimplePathfindingAlgorithm.java +++ b/game/src/main/java/org/apollo/game/model/entity/path/SimplePathfindingAlgorithm.java @@ -91,7 +91,7 @@ private Deque addHorizontal(Position start, Position target, Deque 0 ? Direction.SOUTH : Direction.NORTH)) { + if (!last.equals(target) && dy != 0 && traversable(last, boundaries, dy > 0 ? Direction.SOUTH : Direction.NORTH)) { return addVertical(last, target, positions); } @@ -143,4 +143,4 @@ && traversable(last, boundaries, dx > 0 ? Direction.WEST : Direction.EAST)) { return positions; } -} \ No newline at end of file +} diff --git a/game/src/main/org/apollo/game/model/entity/path/package-info.java b/game/src/main/java/org/apollo/game/model/entity/path/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/path/package-info.java rename to game/src/main/java/org/apollo/game/model/entity/path/package-info.java diff --git a/game/src/main/org/apollo/game/model/entity/setting/Gender.java b/game/src/main/java/org/apollo/game/model/entity/setting/Gender.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/setting/Gender.java rename to game/src/main/java/org/apollo/game/model/entity/setting/Gender.java diff --git a/game/src/main/org/apollo/game/model/entity/setting/MembershipStatus.java b/game/src/main/java/org/apollo/game/model/entity/setting/MembershipStatus.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/setting/MembershipStatus.java rename to game/src/main/java/org/apollo/game/model/entity/setting/MembershipStatus.java diff --git a/game/src/main/org/apollo/game/model/entity/setting/PrivacyState.java b/game/src/main/java/org/apollo/game/model/entity/setting/PrivacyState.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/setting/PrivacyState.java rename to game/src/main/java/org/apollo/game/model/entity/setting/PrivacyState.java diff --git a/game/src/main/org/apollo/game/model/entity/setting/PrivilegeLevel.java b/game/src/main/java/org/apollo/game/model/entity/setting/PrivilegeLevel.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/setting/PrivilegeLevel.java rename to game/src/main/java/org/apollo/game/model/entity/setting/PrivilegeLevel.java diff --git a/game/src/main/org/apollo/game/model/entity/setting/ScreenBrightness.java b/game/src/main/java/org/apollo/game/model/entity/setting/ScreenBrightness.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/setting/ScreenBrightness.java rename to game/src/main/java/org/apollo/game/model/entity/setting/ScreenBrightness.java diff --git a/game/src/main/org/apollo/game/model/entity/setting/ServerStatus.java b/game/src/main/java/org/apollo/game/model/entity/setting/ServerStatus.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/setting/ServerStatus.java rename to game/src/main/java/org/apollo/game/model/entity/setting/ServerStatus.java diff --git a/game/src/main/org/apollo/game/model/entity/setting/package-info.java b/game/src/main/java/org/apollo/game/model/entity/setting/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/entity/setting/package-info.java rename to game/src/main/java/org/apollo/game/model/entity/setting/package-info.java diff --git a/game/src/main/org/apollo/game/model/event/Event.java b/game/src/main/java/org/apollo/game/model/event/Event.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/Event.java rename to game/src/main/java/org/apollo/game/model/event/Event.java diff --git a/game/src/main/org/apollo/game/model/event/EventListener.java b/game/src/main/java/org/apollo/game/model/event/EventListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/EventListener.java rename to game/src/main/java/org/apollo/game/model/event/EventListener.java diff --git a/game/src/main/org/apollo/game/model/event/EventListenerChain.java b/game/src/main/java/org/apollo/game/model/event/EventListenerChain.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/EventListenerChain.java rename to game/src/main/java/org/apollo/game/model/event/EventListenerChain.java diff --git a/game/src/main/org/apollo/game/model/event/EventListenerChainSet.java b/game/src/main/java/org/apollo/game/model/event/EventListenerChainSet.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/EventListenerChainSet.java rename to game/src/main/java/org/apollo/game/model/event/EventListenerChainSet.java diff --git a/game/src/main/org/apollo/game/model/event/PlayerEvent.java b/game/src/main/java/org/apollo/game/model/event/PlayerEvent.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/PlayerEvent.java rename to game/src/main/java/org/apollo/game/model/event/PlayerEvent.java diff --git a/game/src/main/org/apollo/game/model/event/ProxyEvent.java b/game/src/main/java/org/apollo/game/model/event/ProxyEvent.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/ProxyEvent.java rename to game/src/main/java/org/apollo/game/model/event/ProxyEvent.java diff --git a/game/src/main/org/apollo/game/model/event/ProxyEventListener.java b/game/src/main/java/org/apollo/game/model/event/ProxyEventListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/ProxyEventListener.java rename to game/src/main/java/org/apollo/game/model/event/ProxyEventListener.java diff --git a/game/src/main/org/apollo/game/model/event/impl/CloseInterfacesEvent.java b/game/src/main/java/org/apollo/game/model/event/impl/CloseInterfacesEvent.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/impl/CloseInterfacesEvent.java rename to game/src/main/java/org/apollo/game/model/event/impl/CloseInterfacesEvent.java diff --git a/game/src/main/org/apollo/game/model/event/impl/LoginEvent.java b/game/src/main/java/org/apollo/game/model/event/impl/LoginEvent.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/impl/LoginEvent.java rename to game/src/main/java/org/apollo/game/model/event/impl/LoginEvent.java diff --git a/game/src/main/org/apollo/game/model/event/impl/LogoutEvent.java b/game/src/main/java/org/apollo/game/model/event/impl/LogoutEvent.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/impl/LogoutEvent.java rename to game/src/main/java/org/apollo/game/model/event/impl/LogoutEvent.java diff --git a/game/src/main/org/apollo/game/model/event/impl/MobPositionUpdateEvent.java b/game/src/main/java/org/apollo/game/model/event/impl/MobPositionUpdateEvent.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/impl/MobPositionUpdateEvent.java rename to game/src/main/java/org/apollo/game/model/event/impl/MobPositionUpdateEvent.java diff --git a/game/src/main/org/apollo/game/model/event/impl/package-info.java b/game/src/main/java/org/apollo/game/model/event/impl/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/impl/package-info.java rename to game/src/main/java/org/apollo/game/model/event/impl/package-info.java diff --git a/game/src/main/org/apollo/game/model/event/package-info.java b/game/src/main/java/org/apollo/game/model/event/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/event/package-info.java rename to game/src/main/java/org/apollo/game/model/event/package-info.java diff --git a/game/src/main/org/apollo/game/model/inter/EnterAmountListener.java b/game/src/main/java/org/apollo/game/model/inter/EnterAmountListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/EnterAmountListener.java rename to game/src/main/java/org/apollo/game/model/inter/EnterAmountListener.java diff --git a/game/src/main/org/apollo/game/model/inter/InterfaceConstants.java b/game/src/main/java/org/apollo/game/model/inter/InterfaceConstants.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/InterfaceConstants.java rename to game/src/main/java/org/apollo/game/model/inter/InterfaceConstants.java diff --git a/game/src/main/org/apollo/game/model/inter/InterfaceListener.java b/game/src/main/java/org/apollo/game/model/inter/InterfaceListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/InterfaceListener.java rename to game/src/main/java/org/apollo/game/model/inter/InterfaceListener.java diff --git a/game/src/main/org/apollo/game/model/inter/InterfaceSet.java b/game/src/main/java/org/apollo/game/model/inter/InterfaceSet.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/InterfaceSet.java rename to game/src/main/java/org/apollo/game/model/inter/InterfaceSet.java diff --git a/game/src/main/org/apollo/game/model/inter/InterfaceType.java b/game/src/main/java/org/apollo/game/model/inter/InterfaceType.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/InterfaceType.java rename to game/src/main/java/org/apollo/game/model/inter/InterfaceType.java diff --git a/game/src/main/org/apollo/game/model/inter/bank/BankConstants.java b/game/src/main/java/org/apollo/game/model/inter/bank/BankConstants.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/bank/BankConstants.java rename to game/src/main/java/org/apollo/game/model/inter/bank/BankConstants.java diff --git a/game/src/main/org/apollo/game/model/inter/bank/BankDepositEnterAmountListener.java b/game/src/main/java/org/apollo/game/model/inter/bank/BankDepositEnterAmountListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/bank/BankDepositEnterAmountListener.java rename to game/src/main/java/org/apollo/game/model/inter/bank/BankDepositEnterAmountListener.java diff --git a/game/src/main/org/apollo/game/model/inter/bank/BankInterfaceListener.java b/game/src/main/java/org/apollo/game/model/inter/bank/BankInterfaceListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/bank/BankInterfaceListener.java rename to game/src/main/java/org/apollo/game/model/inter/bank/BankInterfaceListener.java diff --git a/game/src/main/org/apollo/game/model/inter/bank/BankUtils.java b/game/src/main/java/org/apollo/game/model/inter/bank/BankUtils.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/bank/BankUtils.java rename to game/src/main/java/org/apollo/game/model/inter/bank/BankUtils.java diff --git a/game/src/main/org/apollo/game/model/inter/bank/BankWithdrawEnterAmountListener.java b/game/src/main/java/org/apollo/game/model/inter/bank/BankWithdrawEnterAmountListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/bank/BankWithdrawEnterAmountListener.java rename to game/src/main/java/org/apollo/game/model/inter/bank/BankWithdrawEnterAmountListener.java diff --git a/game/src/main/org/apollo/game/model/inter/bank/package-info.java b/game/src/main/java/org/apollo/game/model/inter/bank/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/bank/package-info.java rename to game/src/main/java/org/apollo/game/model/inter/bank/package-info.java diff --git a/game/src/main/org/apollo/game/model/inter/dialogue/DialogueAdapter.java b/game/src/main/java/org/apollo/game/model/inter/dialogue/DialogueAdapter.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/dialogue/DialogueAdapter.java rename to game/src/main/java/org/apollo/game/model/inter/dialogue/DialogueAdapter.java diff --git a/game/src/main/org/apollo/game/model/inter/dialogue/DialogueListener.java b/game/src/main/java/org/apollo/game/model/inter/dialogue/DialogueListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/dialogue/DialogueListener.java rename to game/src/main/java/org/apollo/game/model/inter/dialogue/DialogueListener.java diff --git a/game/src/main/org/apollo/game/model/inter/dialogue/package-info.java b/game/src/main/java/org/apollo/game/model/inter/dialogue/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/dialogue/package-info.java rename to game/src/main/java/org/apollo/game/model/inter/dialogue/package-info.java diff --git a/game/src/main/org/apollo/game/model/inter/package-info.java b/game/src/main/java/org/apollo/game/model/inter/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/inter/package-info.java rename to game/src/main/java/org/apollo/game/model/inter/package-info.java diff --git a/game/src/main/org/apollo/game/model/inv/AppearanceInventoryListener.java b/game/src/main/java/org/apollo/game/model/inv/AppearanceInventoryListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/inv/AppearanceInventoryListener.java rename to game/src/main/java/org/apollo/game/model/inv/AppearanceInventoryListener.java diff --git a/game/src/main/org/apollo/game/model/inv/FullInventoryListener.java b/game/src/main/java/org/apollo/game/model/inv/FullInventoryListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/inv/FullInventoryListener.java rename to game/src/main/java/org/apollo/game/model/inv/FullInventoryListener.java diff --git a/game/src/main/org/apollo/game/model/inv/Inventory.java b/game/src/main/java/org/apollo/game/model/inv/Inventory.java similarity index 98% rename from game/src/main/org/apollo/game/model/inv/Inventory.java rename to game/src/main/java/org/apollo/game/model/inv/Inventory.java index c4ff61769..ae8f081e5 100644 --- a/game/src/main/org/apollo/game/model/inv/Inventory.java +++ b/game/src/main/java/org/apollo/game/model/inv/Inventory.java @@ -1,15 +1,14 @@ package org.apollo.game.model.inv; +import com.google.common.base.Preconditions; +import org.apollo.cache.def.ItemDefinition; +import org.apollo.game.model.Item; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; -import org.apollo.cache.def.ItemDefinition; -import org.apollo.game.model.Item; - -import com.google.common.base.Preconditions; - /** * Represents an inventory - a collection of {@link Item}s. * @@ -518,6 +517,15 @@ public int remove(Item item) { return remove(item.getId(), item.getAmount()); } + /** + * Remove all items with the given {@code id} and return the number of + * items removed. + * + * @param id The id of items to remove. + * @return The amount that was removed. + */ + public int removeAll(int id) { return remove(id, getAmount(id)); } + /** * Removes all the listeners. */ diff --git a/game/src/main/org/apollo/game/model/inv/InventoryAdapter.java b/game/src/main/java/org/apollo/game/model/inv/InventoryAdapter.java similarity index 100% rename from game/src/main/org/apollo/game/model/inv/InventoryAdapter.java rename to game/src/main/java/org/apollo/game/model/inv/InventoryAdapter.java diff --git a/game/src/main/org/apollo/game/model/inv/InventoryConstants.java b/game/src/main/java/org/apollo/game/model/inv/InventoryConstants.java similarity index 100% rename from game/src/main/org/apollo/game/model/inv/InventoryConstants.java rename to game/src/main/java/org/apollo/game/model/inv/InventoryConstants.java diff --git a/game/src/main/org/apollo/game/model/inv/InventoryListener.java b/game/src/main/java/org/apollo/game/model/inv/InventoryListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/inv/InventoryListener.java rename to game/src/main/java/org/apollo/game/model/inv/InventoryListener.java diff --git a/game/src/main/org/apollo/game/model/inv/SlottedItem.java b/game/src/main/java/org/apollo/game/model/inv/SlottedItem.java similarity index 100% rename from game/src/main/org/apollo/game/model/inv/SlottedItem.java rename to game/src/main/java/org/apollo/game/model/inv/SlottedItem.java diff --git a/game/src/main/org/apollo/game/model/inv/SynchronizationInventoryListener.java b/game/src/main/java/org/apollo/game/model/inv/SynchronizationInventoryListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/inv/SynchronizationInventoryListener.java rename to game/src/main/java/org/apollo/game/model/inv/SynchronizationInventoryListener.java diff --git a/game/src/main/org/apollo/game/model/inv/package-info.java b/game/src/main/java/org/apollo/game/model/inv/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/inv/package-info.java rename to game/src/main/java/org/apollo/game/model/inv/package-info.java diff --git a/game/src/main/org/apollo/game/model/package-info.java b/game/src/main/java/org/apollo/game/model/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/package-info.java rename to game/src/main/java/org/apollo/game/model/package-info.java diff --git a/game/src/main/org/apollo/game/model/skill/LevelUpSkillListener.java b/game/src/main/java/org/apollo/game/model/skill/LevelUpSkillListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/skill/LevelUpSkillListener.java rename to game/src/main/java/org/apollo/game/model/skill/LevelUpSkillListener.java diff --git a/game/src/main/org/apollo/game/model/skill/SkillAdapter.java b/game/src/main/java/org/apollo/game/model/skill/SkillAdapter.java similarity index 100% rename from game/src/main/org/apollo/game/model/skill/SkillAdapter.java rename to game/src/main/java/org/apollo/game/model/skill/SkillAdapter.java diff --git a/game/src/main/org/apollo/game/model/skill/SkillListener.java b/game/src/main/java/org/apollo/game/model/skill/SkillListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/skill/SkillListener.java rename to game/src/main/java/org/apollo/game/model/skill/SkillListener.java diff --git a/game/src/main/org/apollo/game/model/skill/SynchronizationSkillListener.java b/game/src/main/java/org/apollo/game/model/skill/SynchronizationSkillListener.java similarity index 100% rename from game/src/main/org/apollo/game/model/skill/SynchronizationSkillListener.java rename to game/src/main/java/org/apollo/game/model/skill/SynchronizationSkillListener.java diff --git a/game/src/main/org/apollo/game/model/skill/package-info.java b/game/src/main/java/org/apollo/game/model/skill/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/model/skill/package-info.java rename to game/src/main/java/org/apollo/game/model/skill/package-info.java diff --git a/game/src/main/org/apollo/game/package-info.java b/game/src/main/java/org/apollo/game/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/package-info.java rename to game/src/main/java/org/apollo/game/package-info.java diff --git a/game/src/main/org/apollo/game/plugin/DependencyException.java b/game/src/main/java/org/apollo/game/plugin/DependencyException.java similarity index 100% rename from game/src/main/org/apollo/game/plugin/DependencyException.java rename to game/src/main/java/org/apollo/game/plugin/DependencyException.java diff --git a/game/src/main/java/org/apollo/game/plugin/KotlinPluginEnvironment.java b/game/src/main/java/org/apollo/game/plugin/KotlinPluginEnvironment.java new file mode 100644 index 000000000..7e8d61fa7 --- /dev/null +++ b/game/src/main/java/org/apollo/game/plugin/KotlinPluginEnvironment.java @@ -0,0 +1,66 @@ +package org.apollo.game.plugin; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.logging.Logger; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ClassInfoList; +import io.github.classgraph.ScanResult; +import org.apollo.game.model.World; +import org.apollo.game.plugin.kotlin.KotlinPluginScript; + +public class KotlinPluginEnvironment implements PluginEnvironment { + + private static final Logger logger = Logger.getLogger(KotlinPluginEnvironment.class.getName()); + private static final String PLUGIN_SUFFIX = "_plugin"; + + private final World world; + private PluginContext context; + + public KotlinPluginEnvironment(World world) { + this.world = world; + } + + @Override + public void load(Collection plugins) { + List pluginScripts = new ArrayList<>(); + + ClassGraph classGraph = new ClassGraph().enableAllInfo(); + + try (ScanResult scanResult = classGraph.scan()) { + ClassInfoList pluginClassList = scanResult + .getSubclasses(KotlinPluginScript.class.getName()) + .directOnly(); + + for (ClassInfo pluginClassInfo : pluginClassList) { + Class scriptClass = pluginClassInfo.loadClass(KotlinPluginScript.class); + Constructor scriptConstructor = scriptClass.getConstructor(World.class, + PluginContext.class); + + pluginScripts.add(scriptConstructor.newInstance(world, context)); + logger.info(String.format("Loaded plugin: %s", pluginDescriptor(scriptClass))); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + + pluginScripts.forEach(script -> script.doStart(world)); + } + + @Override + public void setContext(PluginContext context) { + this.context = context; + } + + private static String pluginDescriptor(Class clazz) { + String className = clazz.getSimpleName(); + String name = className.substring(0, className.length() - PLUGIN_SUFFIX.length()); + Package pkg = clazz.getPackage(); + + return pkg == null ? name : name + " from " + pkg.getName(); + } +} diff --git a/game/src/main/org/apollo/game/plugin/PluginContext.java b/game/src/main/java/org/apollo/game/plugin/PluginContext.java similarity index 100% rename from game/src/main/org/apollo/game/plugin/PluginContext.java rename to game/src/main/java/org/apollo/game/plugin/PluginContext.java diff --git a/game/src/main/org/apollo/game/plugin/PluginEnvironment.java b/game/src/main/java/org/apollo/game/plugin/PluginEnvironment.java similarity index 62% rename from game/src/main/org/apollo/game/plugin/PluginEnvironment.java rename to game/src/main/java/org/apollo/game/plugin/PluginEnvironment.java index f049f9748..505d8473e 100644 --- a/game/src/main/org/apollo/game/plugin/PluginEnvironment.java +++ b/game/src/main/java/org/apollo/game/plugin/PluginEnvironment.java @@ -1,6 +1,8 @@ package org.apollo.game.plugin; import java.io.InputStream; +import java.util.Collection; +import java.util.Set; /** * Represents some sort of environment that plugins could be executed in, e.g. {@code javax.script} or Jython. @@ -10,12 +12,11 @@ public interface PluginEnvironment { /** - * Parses the input stream. + * Load all of the plugins defined in the given {@link Set} of {@link PluginMetaData}. * - * @param is The input stream. - * @param name The name of the file. + * @param plugins The plugins to be loaded. */ - public void parse(InputStream is, String name); + void load(Collection plugins); /** * Sets the context for this environment. diff --git a/game/src/main/org/apollo/game/plugin/PluginManager.java b/game/src/main/java/org/apollo/game/plugin/PluginManager.java similarity index 68% rename from game/src/main/org/apollo/game/plugin/PluginManager.java rename to game/src/main/java/org/apollo/game/plugin/PluginManager.java index acf980d0c..5471cb697 100644 --- a/game/src/main/org/apollo/game/plugin/PluginManager.java +++ b/game/src/main/java/org/apollo/game/plugin/PluginManager.java @@ -4,14 +4,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -72,7 +65,7 @@ private static Map createMap(Collection * @throws SAXException If a SAX error occurs. */ private Collection findPlugins() throws IOException, SAXException { - return findPlugins(new File("./data/plugins")); + return findPlugins(new File("./game/data/plugins")); } /** @@ -143,49 +136,13 @@ private void initAuthors() { * @throws DependencyException If a dependency could not be resolved. */ public void start() throws IOException, SAXException, DependencyException { - Map plugins = createMap(findPlugins()); - Set started = new HashSet<>(); + //@todo - load metadata and respective plugins + Map plugins = new HashMap<>(); - PluginEnvironment env = new RubyPluginEnvironment(world); // TODO isolate plugins if possible in the future! + PluginEnvironment env = new KotlinPluginEnvironment(world); // TODO isolate plugins if possible in the future! env.setContext(context); - for (PluginMetaData plugin : plugins.values()) { - start(env, plugin, plugins, started); - } - } - - /** - * Starts a specific plugin. - * - * @param env The environment. - * @param plugin The plugin. - * @param plugins The plugin map. - * @param started A set of started plugins. - * @throws DependencyException If a dependency error occurs. - * @throws IOException If an I/O error occurs. - */ - private void start(PluginEnvironment env, PluginMetaData plugin, Map plugins, Set started) throws DependencyException, IOException { - // TODO check for cyclic dependencies! this way just won't cut it, we need an exception - if (started.contains(plugin)) { - return; - } - started.add(plugin); - - for (String dependencyId : plugin.getDependencies()) { - PluginMetaData dependency = plugins.get(dependencyId); - if (dependency == null) { - throw new DependencyException("Unresolved dependency: " + dependencyId + "."); - } - start(env, dependency, plugins, started); - } - - String[] scripts = plugin.getScripts(); - - for (String script : scripts) { - File scriptFile = new File(plugin.getBase(), script); - InputStream is = new FileInputStream(scriptFile); - env.parse(is, scriptFile.getAbsolutePath()); - } + env.load(plugins.values()); } } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/plugin/PluginMetaData.java b/game/src/main/java/org/apollo/game/plugin/PluginMetaData.java similarity index 100% rename from game/src/main/org/apollo/game/plugin/PluginMetaData.java rename to game/src/main/java/org/apollo/game/plugin/PluginMetaData.java diff --git a/game/src/main/org/apollo/game/plugin/package-info.java b/game/src/main/java/org/apollo/game/plugin/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/plugin/package-info.java rename to game/src/main/java/org/apollo/game/plugin/package-info.java diff --git a/game/src/main/org/apollo/game/release/r317/AddFriendMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/AddFriendMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/AddFriendMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/AddFriendMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/AddGlobalTileItemMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/AddGlobalTileItemMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/AddGlobalTileItemMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/AddGlobalTileItemMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/AddIgnoreMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/AddIgnoreMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/AddIgnoreMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/AddIgnoreMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/AddTileItemMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/AddTileItemMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/AddTileItemMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/AddTileItemMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/ArrowKeyMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/ArrowKeyMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/ArrowKeyMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/ArrowKeyMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/ButtonMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/ButtonMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/ButtonMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/ButtonMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/ClearRegionMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/ClearRegionMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/ClearRegionMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/ClearRegionMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/CloseInterfaceMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/CloseInterfaceMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/CloseInterfaceMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/CloseInterfaceMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/ClosedInterfaceMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/ClosedInterfaceMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/ClosedInterfaceMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/ClosedInterfaceMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/CommandMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/CommandMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/CommandMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/CommandMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/ConfigMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/ConfigMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/ConfigMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/ConfigMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/DialogueContinueMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/DialogueContinueMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/DialogueContinueMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/DialogueContinueMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/DisplayCrossbonesMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/DisplayCrossbonesMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/DisplayCrossbonesMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/DisplayCrossbonesMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/DisplayTabInterfaceMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/DisplayTabInterfaceMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/DisplayTabInterfaceMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/DisplayTabInterfaceMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/EnterAmountMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/EnterAmountMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/EnterAmountMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/EnterAmountMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/EnteredAmountMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/EnteredAmountMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/EnteredAmountMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/EnteredAmountMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FifthItemActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FifthItemActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FifthItemActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FifthItemActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FifthItemOptionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FifthItemOptionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FifthItemOptionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FifthItemOptionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FifthNpcActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FifthNpcActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FifthNpcActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FifthNpcActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FifthPlayerActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FifthPlayerActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FifthPlayerActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FifthPlayerActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FirstItemActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FirstItemActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FirstItemActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FirstItemActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FirstItemOptionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FirstItemOptionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FirstItemOptionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FirstItemOptionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FirstNpcActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FirstNpcActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FirstNpcActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FirstNpcActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FirstObjectActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FirstObjectActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FirstObjectActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FirstObjectActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FirstPlayerActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FirstPlayerActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FirstPlayerActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FirstPlayerActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FlaggedMouseEventMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FlaggedMouseEventMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FlaggedMouseEventMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FlaggedMouseEventMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FlashTabInterfaceMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/FlashTabInterfaceMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FlashTabInterfaceMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/FlashTabInterfaceMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FlashingTabClickedMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FlashingTabClickedMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FlashingTabClickedMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FlashingTabClickedMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FocusUpdateMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FocusUpdateMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FocusUpdateMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FocusUpdateMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/ForwardPrivateChatMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/ForwardPrivateChatMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/ForwardPrivateChatMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/ForwardPrivateChatMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FourthItemActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FourthItemActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FourthItemActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FourthItemActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FourthItemOptionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FourthItemOptionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FourthItemOptionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FourthItemOptionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FourthNpcActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FourthNpcActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FourthNpcActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FourthNpcActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FourthPlayerActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/FourthPlayerActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FourthPlayerActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/FourthPlayerActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/FriendServerStatusMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/FriendServerStatusMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/FriendServerStatusMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/FriendServerStatusMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/GroupedRegionUpdateMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/GroupedRegionUpdateMessageEncoder.java similarity index 97% rename from game/src/main/org/apollo/game/release/r317/GroupedRegionUpdateMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/GroupedRegionUpdateMessageEncoder.java index 80fa508fd..cd6703b77 100644 --- a/game/src/main/org/apollo/game/release/r317/GroupedRegionUpdateMessageEncoder.java +++ b/game/src/main/java/org/apollo/game/release/r317/GroupedRegionUpdateMessageEncoder.java @@ -47,7 +47,7 @@ public GamePacket encode(GroupedRegionUpdateMessage message) { GamePacket packet = encoder.encode(update); builder.put(DataType.BYTE, packet.getOpcode()); - builder.putBytes(packet.getPayload()); + builder.putBytes(packet.content()); } return builder.toGamePacket(); diff --git a/game/src/main/org/apollo/game/release/r317/IdAssignmentMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/IdAssignmentMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/IdAssignmentMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/IdAssignmentMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/IgnoreListMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/IgnoreListMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/IgnoreListMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/IgnoreListMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/ItemOnItemMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/ItemOnItemMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/ItemOnItemMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/ItemOnItemMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/ItemOnNpcMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/ItemOnNpcMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/ItemOnNpcMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/ItemOnNpcMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/ItemOnObjectMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/ItemOnObjectMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/ItemOnObjectMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/ItemOnObjectMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/KeepAliveMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/KeepAliveMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/KeepAliveMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/KeepAliveMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/LogoutMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/LogoutMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/LogoutMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/LogoutMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/MagicOnItemMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/MagicOnItemMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/MagicOnItemMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/MagicOnItemMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/MagicOnNpcMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/MagicOnNpcMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/MagicOnNpcMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/MagicOnNpcMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/MagicOnPlayerMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/MagicOnPlayerMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/MagicOnPlayerMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/MagicOnPlayerMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/MobAnimationResetMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/MobAnimationResetMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/MobAnimationResetMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/MobAnimationResetMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/MobHintIconMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/MobHintIconMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/MobHintIconMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/MobHintIconMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/MouseClickedMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/MouseClickedMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/MouseClickedMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/MouseClickedMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/NpcSynchronizationMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/NpcSynchronizationMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/NpcSynchronizationMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/NpcSynchronizationMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/OpenDialogueInterfaceMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/OpenDialogueInterfaceMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/OpenDialogueInterfaceMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/OpenDialogueInterfaceMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/OpenDialogueOverlayMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/OpenDialogueOverlayMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/OpenDialogueOverlayMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/OpenDialogueOverlayMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/OpenInterfaceMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/OpenInterfaceMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/OpenInterfaceMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/OpenInterfaceMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/OpenInterfaceSidebarMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/OpenInterfaceSidebarMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/OpenInterfaceSidebarMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/OpenInterfaceSidebarMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/OpenOverlayMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/OpenOverlayMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/OpenOverlayMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/OpenOverlayMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/OpenSidebarMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/OpenSidebarMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/OpenSidebarMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/OpenSidebarMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/PlayerDesignMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/PlayerDesignMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/PlayerDesignMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/PlayerDesignMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/PlayerSynchronizationMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/PlayerSynchronizationMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/PlayerSynchronizationMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/PlayerSynchronizationMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/PositionHintIconMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/PositionHintIconMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/PositionHintIconMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/PositionHintIconMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/PrivacyOptionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/PrivacyOptionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/PrivacyOptionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/PrivacyOptionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/PrivacyOptionMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/PrivacyOptionMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/PrivacyOptionMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/PrivacyOptionMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/PrivateChatMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/PrivateChatMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/PrivateChatMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/PrivateChatMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/PublicChatMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/PublicChatMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/PublicChatMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/PublicChatMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/RegionChangeMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/RegionChangeMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/RegionChangeMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/RegionChangeMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/Release317.java b/game/src/main/java/org/apollo/game/release/r317/Release317.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/Release317.java rename to game/src/main/java/org/apollo/game/release/r317/Release317.java diff --git a/game/src/main/org/apollo/game/release/r317/RemoveFriendMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/RemoveFriendMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/RemoveFriendMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/RemoveFriendMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/RemoveIgnoreMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/RemoveIgnoreMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/RemoveIgnoreMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/RemoveIgnoreMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/RemoveObjectMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/RemoveObjectMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/RemoveObjectMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/RemoveObjectMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/RemoveTileItemMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/RemoveTileItemMessageEncoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/RemoveTileItemMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/RemoveTileItemMessageEncoder.java diff --git a/game/src/main/org/apollo/game/release/r317/ReportAbuseMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/ReportAbuseMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/ReportAbuseMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/ReportAbuseMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/SecondItemActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/SecondItemActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/SecondItemActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/SecondItemActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/SecondItemOptionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/SecondItemOptionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/SecondItemOptionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/SecondItemOptionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/SecondNpcActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/SecondNpcActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/SecondNpcActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/SecondNpcActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/SecondObjectActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/SecondObjectActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/SecondObjectActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/SecondObjectActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/SecondPlayerActionMessageDecoder.java b/game/src/main/java/org/apollo/game/release/r317/SecondPlayerActionMessageDecoder.java similarity index 100% rename from game/src/main/org/apollo/game/release/r317/SecondPlayerActionMessageDecoder.java rename to game/src/main/java/org/apollo/game/release/r317/SecondPlayerActionMessageDecoder.java diff --git a/game/src/main/org/apollo/game/release/r317/SendFriendMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/SendFriendMessageEncoder.java similarity index 92% rename from game/src/main/org/apollo/game/release/r317/SendFriendMessageEncoder.java rename to game/src/main/java/org/apollo/game/release/r317/SendFriendMessageEncoder.java index 15660fcbd..932dca8ef 100644 --- a/game/src/main/org/apollo/game/release/r317/SendFriendMessageEncoder.java +++ b/game/src/main/java/org/apollo/game/release/r317/SendFriendMessageEncoder.java @@ -18,7 +18,7 @@ public final class SendFriendMessageEncoder extends MessageEncoder attribute = channel.attr(ApolloHandler.SESSION_KEY); - Session session = attribute.get(); + Channel channel = ctx.channel(); + Attribute attribute = channel.attr(ApolloHandler.SESSION_KEY); + Session session = attribute.get(); - if (message instanceof HttpRequest || message instanceof JagGrabRequest) { - session = new UpdateSession(channel, serverContext); - } + if (message instanceof HttpRequest || message instanceof JagGrabRequest) { + session = new UpdateSession(channel, serverContext); + } - if (session != null) { - session.messageReceived(message); - return; - } + if (session != null) { + session.messageReceived(message); + return; + } - // TODO: Perhaps let HandshakeMessage implement Message to remove this explicit check - if (message instanceof HandshakeMessage) { - HandshakeMessage handshakeMessage = (HandshakeMessage) message; + // TODO: Perhaps let HandshakeMessage implement Message to remove this explicit check + if (message instanceof HandshakeMessage) { + HandshakeMessage handshakeMessage = (HandshakeMessage) message; - switch (handshakeMessage.getServiceId()) { - case HandshakeConstants.SERVICE_GAME: - attribute.set(new LoginSession(channel, serverContext)); - break; + switch (handshakeMessage.getServiceId()) { + case HandshakeConstants.SERVICE_GAME: + attribute.set(new LoginSession(channel, serverContext)); + break; - case HandshakeConstants.SERVICE_UPDATE: - attribute.set(new UpdateSession(channel, serverContext)); - break; - } + case HandshakeConstants.SERVICE_UPDATE: + attribute.set(new UpdateSession(channel, serverContext)); + break; } - - } finally { - ReferenceCountUtil.release(message); } } diff --git a/game/src/main/org/apollo/game/session/GameSession.java b/game/src/main/java/org/apollo/game/session/GameSession.java similarity index 100% rename from game/src/main/org/apollo/game/session/GameSession.java rename to game/src/main/java/org/apollo/game/session/GameSession.java diff --git a/game/src/main/org/apollo/game/session/LoginSession.java b/game/src/main/java/org/apollo/game/session/LoginSession.java similarity index 100% rename from game/src/main/org/apollo/game/session/LoginSession.java rename to game/src/main/java/org/apollo/game/session/LoginSession.java diff --git a/game/src/main/org/apollo/game/session/Session.java b/game/src/main/java/org/apollo/game/session/Session.java similarity index 100% rename from game/src/main/org/apollo/game/session/Session.java rename to game/src/main/java/org/apollo/game/session/Session.java diff --git a/game/src/main/org/apollo/game/session/UpdateSession.java b/game/src/main/java/org/apollo/game/session/UpdateSession.java similarity index 100% rename from game/src/main/org/apollo/game/session/UpdateSession.java rename to game/src/main/java/org/apollo/game/session/UpdateSession.java diff --git a/game/src/main/org/apollo/game/session/package-info.java b/game/src/main/java/org/apollo/game/session/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/session/package-info.java rename to game/src/main/java/org/apollo/game/session/package-info.java diff --git a/game/src/main/org/apollo/game/sync/ClientSynchronizer.java b/game/src/main/java/org/apollo/game/sync/ClientSynchronizer.java similarity index 100% rename from game/src/main/org/apollo/game/sync/ClientSynchronizer.java rename to game/src/main/java/org/apollo/game/sync/ClientSynchronizer.java diff --git a/game/src/main/org/apollo/game/sync/ParallelClientSynchronizer.java b/game/src/main/java/org/apollo/game/sync/ParallelClientSynchronizer.java similarity index 100% rename from game/src/main/org/apollo/game/sync/ParallelClientSynchronizer.java rename to game/src/main/java/org/apollo/game/sync/ParallelClientSynchronizer.java diff --git a/game/src/main/org/apollo/game/sync/SequentialClientSynchronizer.java b/game/src/main/java/org/apollo/game/sync/SequentialClientSynchronizer.java similarity index 100% rename from game/src/main/org/apollo/game/sync/SequentialClientSynchronizer.java rename to game/src/main/java/org/apollo/game/sync/SequentialClientSynchronizer.java diff --git a/game/src/main/org/apollo/game/sync/block/AnimationBlock.java b/game/src/main/java/org/apollo/game/sync/block/AnimationBlock.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/AnimationBlock.java rename to game/src/main/java/org/apollo/game/sync/block/AnimationBlock.java diff --git a/game/src/main/org/apollo/game/sync/block/AppearanceBlock.java b/game/src/main/java/org/apollo/game/sync/block/AppearanceBlock.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/AppearanceBlock.java rename to game/src/main/java/org/apollo/game/sync/block/AppearanceBlock.java diff --git a/game/src/main/org/apollo/game/sync/block/ChatBlock.java b/game/src/main/java/org/apollo/game/sync/block/ChatBlock.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/ChatBlock.java rename to game/src/main/java/org/apollo/game/sync/block/ChatBlock.java diff --git a/game/src/main/org/apollo/game/sync/block/ForceChatBlock.java b/game/src/main/java/org/apollo/game/sync/block/ForceChatBlock.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/ForceChatBlock.java rename to game/src/main/java/org/apollo/game/sync/block/ForceChatBlock.java diff --git a/game/src/main/org/apollo/game/sync/block/ForceMovementBlock.java b/game/src/main/java/org/apollo/game/sync/block/ForceMovementBlock.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/ForceMovementBlock.java rename to game/src/main/java/org/apollo/game/sync/block/ForceMovementBlock.java diff --git a/game/src/main/org/apollo/game/sync/block/GraphicBlock.java b/game/src/main/java/org/apollo/game/sync/block/GraphicBlock.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/GraphicBlock.java rename to game/src/main/java/org/apollo/game/sync/block/GraphicBlock.java diff --git a/game/src/main/org/apollo/game/sync/block/HitUpdateBlock.java b/game/src/main/java/org/apollo/game/sync/block/HitUpdateBlock.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/HitUpdateBlock.java rename to game/src/main/java/org/apollo/game/sync/block/HitUpdateBlock.java diff --git a/game/src/main/org/apollo/game/sync/block/InteractingMobBlock.java b/game/src/main/java/org/apollo/game/sync/block/InteractingMobBlock.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/InteractingMobBlock.java rename to game/src/main/java/org/apollo/game/sync/block/InteractingMobBlock.java diff --git a/game/src/main/org/apollo/game/sync/block/SecondaryHitUpdateBlock.java b/game/src/main/java/org/apollo/game/sync/block/SecondaryHitUpdateBlock.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/SecondaryHitUpdateBlock.java rename to game/src/main/java/org/apollo/game/sync/block/SecondaryHitUpdateBlock.java diff --git a/game/src/main/org/apollo/game/sync/block/SynchronizationBlock.java b/game/src/main/java/org/apollo/game/sync/block/SynchronizationBlock.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/SynchronizationBlock.java rename to game/src/main/java/org/apollo/game/sync/block/SynchronizationBlock.java diff --git a/game/src/main/org/apollo/game/sync/block/SynchronizationBlockSet.java b/game/src/main/java/org/apollo/game/sync/block/SynchronizationBlockSet.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/SynchronizationBlockSet.java rename to game/src/main/java/org/apollo/game/sync/block/SynchronizationBlockSet.java diff --git a/game/src/main/org/apollo/game/sync/block/TransformBlock.java b/game/src/main/java/org/apollo/game/sync/block/TransformBlock.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/TransformBlock.java rename to game/src/main/java/org/apollo/game/sync/block/TransformBlock.java diff --git a/game/src/main/org/apollo/game/sync/block/TurnToPositionBlock.java b/game/src/main/java/org/apollo/game/sync/block/TurnToPositionBlock.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/TurnToPositionBlock.java rename to game/src/main/java/org/apollo/game/sync/block/TurnToPositionBlock.java diff --git a/game/src/main/org/apollo/game/sync/block/package-info.java b/game/src/main/java/org/apollo/game/sync/block/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/sync/block/package-info.java rename to game/src/main/java/org/apollo/game/sync/block/package-info.java diff --git a/game/src/main/org/apollo/game/sync/package-info.java b/game/src/main/java/org/apollo/game/sync/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/sync/package-info.java rename to game/src/main/java/org/apollo/game/sync/package-info.java diff --git a/game/src/main/org/apollo/game/sync/seg/AddNpcSegment.java b/game/src/main/java/org/apollo/game/sync/seg/AddNpcSegment.java similarity index 100% rename from game/src/main/org/apollo/game/sync/seg/AddNpcSegment.java rename to game/src/main/java/org/apollo/game/sync/seg/AddNpcSegment.java diff --git a/game/src/main/org/apollo/game/sync/seg/AddPlayerSegment.java b/game/src/main/java/org/apollo/game/sync/seg/AddPlayerSegment.java similarity index 100% rename from game/src/main/org/apollo/game/sync/seg/AddPlayerSegment.java rename to game/src/main/java/org/apollo/game/sync/seg/AddPlayerSegment.java diff --git a/game/src/main/org/apollo/game/sync/seg/MovementSegment.java b/game/src/main/java/org/apollo/game/sync/seg/MovementSegment.java similarity index 100% rename from game/src/main/org/apollo/game/sync/seg/MovementSegment.java rename to game/src/main/java/org/apollo/game/sync/seg/MovementSegment.java diff --git a/game/src/main/org/apollo/game/sync/seg/RemoveMobSegment.java b/game/src/main/java/org/apollo/game/sync/seg/RemoveMobSegment.java similarity index 100% rename from game/src/main/org/apollo/game/sync/seg/RemoveMobSegment.java rename to game/src/main/java/org/apollo/game/sync/seg/RemoveMobSegment.java diff --git a/game/src/main/org/apollo/game/sync/seg/SegmentType.java b/game/src/main/java/org/apollo/game/sync/seg/SegmentType.java similarity index 100% rename from game/src/main/org/apollo/game/sync/seg/SegmentType.java rename to game/src/main/java/org/apollo/game/sync/seg/SegmentType.java diff --git a/game/src/main/org/apollo/game/sync/seg/SynchronizationSegment.java b/game/src/main/java/org/apollo/game/sync/seg/SynchronizationSegment.java similarity index 100% rename from game/src/main/org/apollo/game/sync/seg/SynchronizationSegment.java rename to game/src/main/java/org/apollo/game/sync/seg/SynchronizationSegment.java diff --git a/game/src/main/org/apollo/game/sync/seg/TeleportSegment.java b/game/src/main/java/org/apollo/game/sync/seg/TeleportSegment.java similarity index 100% rename from game/src/main/org/apollo/game/sync/seg/TeleportSegment.java rename to game/src/main/java/org/apollo/game/sync/seg/TeleportSegment.java diff --git a/game/src/main/org/apollo/game/sync/seg/package-info.java b/game/src/main/java/org/apollo/game/sync/seg/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/sync/seg/package-info.java rename to game/src/main/java/org/apollo/game/sync/seg/package-info.java diff --git a/game/src/main/org/apollo/game/sync/task/NpcSynchronizationTask.java b/game/src/main/java/org/apollo/game/sync/task/NpcSynchronizationTask.java similarity index 100% rename from game/src/main/org/apollo/game/sync/task/NpcSynchronizationTask.java rename to game/src/main/java/org/apollo/game/sync/task/NpcSynchronizationTask.java diff --git a/game/src/main/org/apollo/game/sync/task/PhasedSynchronizationTask.java b/game/src/main/java/org/apollo/game/sync/task/PhasedSynchronizationTask.java similarity index 100% rename from game/src/main/org/apollo/game/sync/task/PhasedSynchronizationTask.java rename to game/src/main/java/org/apollo/game/sync/task/PhasedSynchronizationTask.java diff --git a/game/src/main/org/apollo/game/sync/task/PlayerSynchronizationTask.java b/game/src/main/java/org/apollo/game/sync/task/PlayerSynchronizationTask.java similarity index 100% rename from game/src/main/org/apollo/game/sync/task/PlayerSynchronizationTask.java rename to game/src/main/java/org/apollo/game/sync/task/PlayerSynchronizationTask.java diff --git a/game/src/main/org/apollo/game/sync/task/PostNpcSynchronizationTask.java b/game/src/main/java/org/apollo/game/sync/task/PostNpcSynchronizationTask.java similarity index 100% rename from game/src/main/org/apollo/game/sync/task/PostNpcSynchronizationTask.java rename to game/src/main/java/org/apollo/game/sync/task/PostNpcSynchronizationTask.java diff --git a/game/src/main/org/apollo/game/sync/task/PostPlayerSynchronizationTask.java b/game/src/main/java/org/apollo/game/sync/task/PostPlayerSynchronizationTask.java similarity index 100% rename from game/src/main/org/apollo/game/sync/task/PostPlayerSynchronizationTask.java rename to game/src/main/java/org/apollo/game/sync/task/PostPlayerSynchronizationTask.java diff --git a/game/src/main/org/apollo/game/sync/task/PreNpcSynchronizationTask.java b/game/src/main/java/org/apollo/game/sync/task/PreNpcSynchronizationTask.java similarity index 100% rename from game/src/main/org/apollo/game/sync/task/PreNpcSynchronizationTask.java rename to game/src/main/java/org/apollo/game/sync/task/PreNpcSynchronizationTask.java diff --git a/game/src/main/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java b/game/src/main/java/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java similarity index 100% rename from game/src/main/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java rename to game/src/main/java/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java diff --git a/game/src/main/org/apollo/game/sync/task/SynchronizationTask.java b/game/src/main/java/org/apollo/game/sync/task/SynchronizationTask.java similarity index 100% rename from game/src/main/org/apollo/game/sync/task/SynchronizationTask.java rename to game/src/main/java/org/apollo/game/sync/task/SynchronizationTask.java diff --git a/game/src/main/org/apollo/game/sync/task/package-info.java b/game/src/main/java/org/apollo/game/sync/task/package-info.java similarity index 100% rename from game/src/main/org/apollo/game/sync/task/package-info.java rename to game/src/main/java/org/apollo/game/sync/task/package-info.java diff --git a/game/src/main/org/apollo/package-info.java b/game/src/main/java/org/apollo/package-info.java similarity index 100% rename from game/src/main/org/apollo/package-info.java rename to game/src/main/java/org/apollo/package-info.java diff --git a/game/src/main/kotlin/org/apollo/game/action/ActionCoroutine.kt b/game/src/main/kotlin/org/apollo/game/action/ActionCoroutine.kt new file mode 100644 index 000000000..9f1bd9fd9 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/action/ActionCoroutine.kt @@ -0,0 +1,137 @@ +package org.apollo.game.action + +import java.util.concurrent.CancellationException +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicReference +import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED +import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.* + +typealias ActionPredicate = () -> Boolean +typealias ActionBlock = suspend ActionCoroutine.() -> Unit + +interface ActionCoroutineCondition { + /** + * Called once every tick to check if `Continuation` associated with this condition should be resumed. + */ + fun resume(): Boolean +} + +/** + * A continuation condition that waits on a given number of pulses. + */ +class AwaitPulses(pulses: Int) : ActionCoroutineCondition { + val remainingPulses: AtomicInteger = AtomicInteger(pulses) + + override fun resume(): Boolean { + return remainingPulses.decrementAndGet() <= 0 + } +} + +/** + * A continuation condition that waits until a predicate is fufilled. + */ +class AwaitPredicate(val predicate: ActionPredicate) : ActionCoroutineCondition { + override fun resume(): Boolean { + return predicate.invoke() + } +} + +/** + * A suspend point in an `ActionCoroutine` that has its `continuation` resumed whenever the `condition` evaluates + * to `true`. + */ +data class ActionCoroutineStep(val condition: ActionCoroutineCondition, internal val continuation: Continuation) + +@RestrictsSuspension +class ActionCoroutine : Continuation { + companion object { + /** + * Create a new `ActionCoroutine` and immediately execute the given `block`, returning a continuation that + * can be resumed. + */ + fun start(block: ActionBlock): ActionCoroutine { + val coroutine = ActionCoroutine() + val continuation = block.createCoroutine(coroutine, coroutine) + + coroutine.resumeContinuation(continuation) + + return coroutine + } + } + + override val context: CoroutineContext = EmptyCoroutineContext + override fun resumeWith(result: Result) { + if (result.isFailure) { + throw result.exceptionOrNull()!! + } + } + + private fun resumeContinuation(continuation: Continuation, allowCancellation: Boolean = true) { + try { + continuation.resume(Unit) + } catch (ex: CancellationException) { + if (!allowCancellation) { + throw ex + } + } + } + + /** + * The next `step` in this `ActionCoroutine` saved as a resume point. + */ + private var next = AtomicReference() + + /** + * Check if this continuation has no more steps to execute. + */ + fun stopped(): Boolean { + return next.get() == null + } + + /** + * Update this continuation and check if the condition for the next step to be resumed is satisfied. + */ + fun pulse() { + val nextStep = next.getAndSet(null) ?: return + + val condition = nextStep.condition + val continuation = nextStep.continuation + + if (condition.resume()) { + resumeContinuation(continuation) + } else { + next.compareAndSet(null, nextStep) + } + } + + private suspend fun awaitCondition(condition: ActionCoroutineCondition) { + return suspendCoroutineUninterceptedOrReturn { cont -> + next.compareAndSet(null, ActionCoroutineStep(condition, cont)) + COROUTINE_SUSPENDED + } + } + + /** + * Stop execution of this continuation. + */ + suspend fun stop(): Nothing { + suspendCancellableCoroutine { cont -> + next.set(null) + cont.cancel() + } + + error("Tried to resume execution a coroutine that should have been cancelled.") + } + + /** + * Wait `pulses` game updates before resuming this continuation. + */ + suspend fun wait(pulses: Int = 1) = awaitCondition(AwaitPulses(pulses)) + + /** + * Wait until the `predicate` returns `true` before resuming this continuation. + */ + suspend fun wait(predicate: ActionPredicate) = awaitCondition(AwaitPredicate(predicate)) +} \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/action/AsyncAction.kt b/game/src/main/kotlin/org/apollo/game/action/AsyncAction.kt new file mode 100644 index 000000000..e8df401d2 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/action/AsyncAction.kt @@ -0,0 +1,16 @@ +package org.apollo.game.action + +import org.apollo.game.model.entity.Mob + +abstract class AsyncAction : Action, AsyncActionTrait { + + override var continuation: ActionCoroutine? = null + + constructor(delay: Int, immediate: Boolean, mob: T) : super(delay, immediate, mob) + + override fun execute() { + if (update()) { + stop() + } + } +} \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/action/AsyncActionTrait.kt b/game/src/main/kotlin/org/apollo/game/action/AsyncActionTrait.kt new file mode 100644 index 000000000..2f0e20d0c --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/action/AsyncActionTrait.kt @@ -0,0 +1,29 @@ +package org.apollo.game.action + +interface AsyncActionTrait { + /** + * The continuation that this `Action` is executing. May be `null` if this action hasn't started yet. + */ + abstract var continuation: ActionCoroutine? + + /** + * Update this action, initializing the continuation if not already initialized. + * + * @return `true` if this `Action` has completed execution. + */ + fun update(): Boolean { + val continuation = this.continuation + if (continuation == null) { + this.continuation = ActionCoroutine.start(action()) + return false + } + + continuation.pulse() + return continuation.stopped() + } + + /** + * Create a new `ActionBlock` to execute. + */ + fun action(): ActionBlock +} \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/action/AsyncDistancedAction.kt b/game/src/main/kotlin/org/apollo/game/action/AsyncDistancedAction.kt new file mode 100644 index 000000000..ce550eb34 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/action/AsyncDistancedAction.kt @@ -0,0 +1,21 @@ +package org.apollo.game.action + +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Mob + +/** + * A `DistancedAction` that uses `ActionCoroutine`s to run asynchronously. + */ +abstract class AsyncDistancedAction : DistancedAction, AsyncActionTrait { + + override var continuation: ActionCoroutine? = null + + constructor(delay: Int, immediate: Boolean, mob: T, position: Position, distance: Int) : + super(delay, immediate, mob, position, distance) + + override fun executeAction() { + if (update()) { + stop() + } + } +} diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinCommandHandler.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinCommandHandler.kt new file mode 100644 index 000000000..4509eaa2f --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinCommandHandler.kt @@ -0,0 +1,41 @@ +package org.apollo.game.plugin.kotlin + +import org.apollo.game.command.Command +import org.apollo.game.command.CommandListener +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel + +/** + * A handler for [Command]s. + */ +class KotlinCommandHandler( + val world: World, + val command: String, + privileges: PrivilegeLevel +) : CommandListener(privileges) { + + var callback: Command.(Player) -> Unit = {} + var predicate: Command.() -> Boolean = { true } + + override fun execute(player: Player, command: Command) { + if (command.predicate()) { + command.callback(player) + } + } + + fun register() { + world.commandDispatcher.register(command, this) + } + + fun where(predicate: Command.() -> Boolean): KotlinCommandHandler { + this.predicate = predicate + return this + } + + fun then(callback: Command.(Player) -> Unit) { + this.callback = callback + this.register() + } + +} \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinMessageHandler.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinMessageHandler.kt new file mode 100644 index 000000000..799db9dc9 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinMessageHandler.kt @@ -0,0 +1,22 @@ +package org.apollo.game.plugin.kotlin + +import org.apollo.game.message.handler.MessageHandler +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.PluginContext +import org.apollo.net.message.Message +import kotlin.reflect.KClass + +/** + * A handler for [Message]s. + */ +@Deprecated("To be removed") +class OldKotlinMessageHandler(val world: World, val context: PluginContext, val type: KClass) : + KotlinPlayerHandlerProxyTrait, MessageHandler(world) { + + override var callback: T.(Player) -> Unit = {} + override var predicate: T.() -> Boolean = { true } + + override fun handle(player: Player, message: T) = handleProxy(player, message) + override fun register() = context.addMessageHandler(type.java, this) +} \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPlayerHandlerProxyTrait.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPlayerHandlerProxyTrait.kt new file mode 100644 index 000000000..0de3a0f07 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPlayerHandlerProxyTrait.kt @@ -0,0 +1,31 @@ +package org.apollo.game.plugin.kotlin + +import org.apollo.game.model.entity.Player + +/** + * A proxy interface for any handler that operates on [Player]s. + */ +@Deprecated("To be removed") +interface KotlinPlayerHandlerProxyTrait { + + var callback: S.(Player) -> Unit + var predicate: S.() -> Boolean + + fun register() + + fun where(predicate: S.() -> Boolean): KotlinPlayerHandlerProxyTrait { + this.predicate = predicate + return this + } + + fun then(callback: S.(Player) -> Unit) { + this.callback = callback + this.register() + } + + fun handleProxy(player: Player, subject: S) { + if (subject.predicate()) { + subject.callback(player) + } + } +} diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginScript.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginScript.kt new file mode 100644 index 000000000..37bdfdfca --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginScript.kt @@ -0,0 +1,112 @@ +package org.apollo.game.plugin.kotlin + +import org.apollo.game.command.CommandListener +import org.apollo.game.message.handler.MessageHandler +import org.apollo.game.message.impl.ButtonMessage +import org.apollo.game.model.World +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.model.event.Event +import org.apollo.game.model.event.EventListener +import org.apollo.game.model.event.PlayerEvent +import org.apollo.game.plugin.PluginContext +import org.apollo.net.message.Message +import kotlin.reflect.KClass +import kotlin.script.experimental.annotations.KotlinScript + +@KotlinScript("Apollo Plugin Script", fileExtension = "plugin.kts") +abstract class KotlinPluginScript(var world: World, val context: PluginContext) { + + private var startListener: (World) -> Unit = { _ -> } + + private var stopListener: (World) -> Unit = { _ -> } + + fun on( + listenable: Listenable, + callback: C.() -> Unit + ) { + registerListener(listenable, null, callback) + } + + internal fun registerListener( + listenable: Listenable, + predicateContext: I?, + callback: C.() -> Unit + ) { + // Smart-casting/type-inference is completely broken in this function in intelliJ, so assign to otherwise + // pointless `l` values for now. + + return when (listenable) { + is MessageListenable -> { + @Suppress("UNCHECKED_CAST") + val l = listenable as MessageListenable + + val handler = l.createHandler(world, predicateContext, callback) + context.addMessageHandler(l.type.java, handler) + } + is EventListenable -> { + @Suppress("UNCHECKED_CAST") + val l = listenable as EventListenable + + val handler = l.createHandler(world, predicateContext, callback) + world.listenFor(l.type.java, handler) + } + } + } + + /** + * Create a [CommandListener] for the given [command] name, which only players with a [PrivilegeLevel] + * of [privileges] and above can use. + */ + fun on_command(command: String, privileges: PrivilegeLevel): KotlinCommandHandler { // TODO what to do with this? + return KotlinCommandHandler(world, command, privileges) + } + + /** + * Creates a [MessageHandler]. + */ + @Deprecated("Use new on(Type) listener") + fun on(type: () -> KClass): OldKotlinMessageHandler { + return OldKotlinMessageHandler(world, context, type()) + } + + /** + * Create an [EventListener] for a [PlayerEvent]. + */ + @Deprecated("Use new on(Type) listener") + fun on_player_event(type: () -> KClass): OldKotlinPlayerEventHandler { + return OldKotlinPlayerEventHandler(world, type()) + } + + /** + * Create an [EventListener] for an [Event]. + */ + @Deprecated("Use new on(Type) listener") + fun on_event(type: () -> KClass): OldKotlinEventHandler { + return OldKotlinEventHandler(world, type()) + } + + /** + * Create a [ButtonMessage] [MessageHandler] for the given [id]. + */ + @Deprecated("Use new on(Type) listener") + fun on_button(id: Int): KotlinPlayerHandlerProxyTrait { + return on { ButtonMessage::class }.where { widgetId == id } + } + + fun start(callback: (World) -> Unit) { + startListener = callback + } + + fun stop(callback: (World) -> Unit) { + stopListener = callback + } + + fun doStart(world: World) { + startListener(world) + } + + fun doStop(world: World) { + stopListener(world) + } + +} \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/Listenable.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/Listenable.kt new file mode 100644 index 000000000..9a89fa641 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/Listenable.kt @@ -0,0 +1,27 @@ +package org.apollo.game.plugin.kotlin + +import org.apollo.game.message.handler.MessageHandler +import org.apollo.game.model.World +import org.apollo.game.model.event.Event +import org.apollo.game.model.event.EventListener +import org.apollo.net.message.Message +import kotlin.reflect.KClass + +/** + * A game occurrence that can be listened to. + */ +sealed class Listenable { + abstract val type: KClass +} + +abstract class EventListenable : Listenable() { + + abstract fun createHandler(world: World, predicateContext: P?, callback: C.() -> Unit): EventListener + +} + +abstract class MessageListenable : Listenable() { + + abstract fun createHandler(world: World, predicateContext: P?, callback: C.() -> Unit): MessageHandler + +} diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/ListenableContext.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/ListenableContext.kt new file mode 100644 index 000000000..760644a4f --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/ListenableContext.kt @@ -0,0 +1,17 @@ +package org.apollo.game.plugin.kotlin + +import org.apollo.game.model.entity.Player + +/** + * Contextual information for a [Listenable]. + */ +interface ListenableContext + +/** + * Contextual information for a [Listenable] involving a specific [Player]. + */ +interface PlayerContext : ListenableContext { + val player: Player +} + +interface PredicateContext \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/OldKotlinEventHandler.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/OldKotlinEventHandler.kt new file mode 100644 index 000000000..7269b96d1 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/OldKotlinEventHandler.kt @@ -0,0 +1,34 @@ +package org.apollo.game.plugin.kotlin + +import org.apollo.game.model.World +import org.apollo.game.model.event.Event +import org.apollo.game.model.event.EventListener +import kotlin.reflect.KClass + +/** + * A handler for [Event]s. + */ +@Deprecated("To be removed") +class OldKotlinEventHandler(val world: World, val type: KClass) : EventListener { + + private var callback: S.() -> Unit = {} + private var predicate: S.() -> Boolean = { true } + + fun where(predicate: S.() -> Boolean): OldKotlinEventHandler { + this.predicate = predicate + return this + } + + fun then(callback: S.() -> Unit) { + this.callback = callback + this.register() + } + + override fun handle(event: S) { + if (event.predicate()) { + event.callback() + } + } + + fun register() = world.listenFor(type.java, this) +} \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/OldKotlinPlayerEventHandler.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/OldKotlinPlayerEventHandler.kt new file mode 100644 index 000000000..8a127037b --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/OldKotlinPlayerEventHandler.kt @@ -0,0 +1,21 @@ +package org.apollo.game.plugin.kotlin + +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.event.EventListener +import org.apollo.game.model.event.PlayerEvent +import kotlin.reflect.KClass + +/** + * A handler for [PlayerEvent]s. + */ +@Deprecated("To be removed") +class OldKotlinPlayerEventHandler(val world: World, val type: KClass) : + KotlinPlayerHandlerProxyTrait, EventListener { + + override var callback: T.(Player) -> Unit = {} + override var predicate: T.() -> Boolean = { true } + + override fun handle(event: T) = handleProxy(event.player, event) + override fun register() = world.listenFor(type.java, this) +} \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/ButtonClick.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/ButtonClick.kt new file mode 100644 index 000000000..1b62dbf1f --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/ButtonClick.kt @@ -0,0 +1,56 @@ +package org.apollo.game.plugin.kotlin.message + +import org.apollo.game.message.handler.MessageHandler +import org.apollo.game.message.impl.ButtonMessage +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.kotlin.KotlinPluginScript +import org.apollo.game.plugin.kotlin.MessageListenable +import org.apollo.game.plugin.kotlin.PlayerContext +import org.apollo.game.plugin.kotlin.PredicateContext + +/** + * Registers a listener for [ButtonMessage]s that occur on the given [button] id. + * + * ``` + * on(ButtonClick, button = 416) { + * player.sendMessage("You click the button.") + * } + * ``` + */ +fun KotlinPluginScript.on( + listenable: ButtonClick.Companion, + button: Int, + callback: ButtonClick.() -> Unit +) { + registerListener(listenable, ButtonPredicateContext(button), callback) +} + +class ButtonClick(override val player: Player, val button: Int) : PlayerContext { + + companion object : MessageListenable() { + + override val type = ButtonMessage::class + + override fun createHandler( + world: World, + predicateContext: ButtonPredicateContext?, + callback: ButtonClick.() -> Unit + ): MessageHandler { + return object : MessageHandler(world) { + + override fun handle(player: Player, message: ButtonMessage) { + if (predicateContext == null || predicateContext.button == message.widgetId) { + val context = ButtonClick(player, message.widgetId) + context.callback() + } + } + + } + } + + } + +} + +class ButtonPredicateContext(val button: Int) : PredicateContext \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/ActionContext.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/ActionContext.kt new file mode 100644 index 000000000..e8ddfb4a5 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/ActionContext.kt @@ -0,0 +1,30 @@ +package org.apollo.game.plugin.kotlin.message.action + +import org.apollo.game.plugin.kotlin.KotlinPluginScript +import org.apollo.game.plugin.kotlin.MessageListenable +import org.apollo.game.plugin.kotlin.PlayerContext +import org.apollo.game.plugin.kotlin.PredicateContext +import org.apollo.net.message.Message + +/** + * Registers a listener for an action event that uses the given [option] (case-insensitive). + * + * ``` + * on(PlayerAction, option = "Trade") { + * player.sendMessage("Sending trade request...") + * } + * ``` + */ +fun KotlinPluginScript.on( + listenable: MessageListenable, + option: String, + callback: T.() -> Unit +) { + registerListener(listenable, ActionPredicateContext(option), callback) +} + +interface ActionContext : PlayerContext { + val option: String +} + +open class ActionPredicateContext(open val option: String) : PredicateContext \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/obj/InteractiveObject.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/obj/InteractiveObject.kt new file mode 100644 index 000000000..85e2c6957 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/obj/InteractiveObject.kt @@ -0,0 +1,14 @@ +package org.apollo.game.plugin.kotlin.message.action.obj + +import org.apollo.game.model.entity.obj.GameObject + +/** + * An object that can be interacted with. + */ +interface InteractiveObject { + + val id: Int + + fun instanceOf(other: GameObject): Boolean // TODO alternative name? + +} \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/obj/ObjectAction.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/obj/ObjectAction.kt new file mode 100644 index 000000000..2bb0b7902 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/obj/ObjectAction.kt @@ -0,0 +1,108 @@ +package org.apollo.game.plugin.kotlin.message.action.obj + +import org.apollo.cache.def.ObjectDefinition +import org.apollo.game.message.handler.MessageHandler +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.game.model.World +import org.apollo.game.model.entity.EntityType +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.obj.GameObject +import org.apollo.game.plugin.kotlin.KotlinPluginScript +import org.apollo.game.plugin.kotlin.MessageListenable +import org.apollo.game.plugin.kotlin.message.action.ActionContext + +/** + * Registers a listener for [ObjectActionMessage]s that occur on any of the given [InteractiveObject]s using the + * given [option] (case-insensitive). + * + * ``` + * on(ObjectAction, option = "Open", objects = DOORS.toList()) { + * player.sendMessage("You open the door.") + * } + * ``` + */ +fun KotlinPluginScript.on( + listenable: ObjectAction.Companion, + option: String, + objects: List, + callback: ObjectAction.() -> Unit +) { + @Suppress("UNCHECKED_CAST") (callback as ObjectAction<*>.() -> Unit) + registerListener(listenable, ObjectActionPredicateContext(option, objects), callback) +} + +/** + * Registers a listener for [ObjectActionMessage]s that occur on any of the given [InteractiveObject]s using the + * given [option] (case-insensitive). + * + * ``` + * on(ObjectAction, option = "Open", objects = DOORS.toList()) { + * player.sendMessage("You open the door.") + * } + * ``` + */ +fun KotlinPluginScript.on( + listenable: ObjectAction.Companion, + option: String, + callback: ObjectAction<*>.() -> Unit +) { + registerListener(listenable, ObjectActionPredicateContext(option, emptyList()), callback) +} + +fun KotlinPluginScript.x() { + on(ObjectAction, "walk") { + + } +} + +/** + * An interaction between a [Player] and an [interactive] [GameObject]. + */ +class ObjectAction( // TODO split into two classes, one with T and one without? + override val player: Player, + override val option: String, + val target: GameObject, + val interactive: T +) : ActionContext { + + companion object : MessageListenable, ObjectActionPredicateContext<*>>() { + + override val type = ObjectActionMessage::class + + override fun createHandler( + world: World, + predicateContext: ObjectActionPredicateContext<*>?, + callback: ObjectAction<*>.() -> Unit + ): MessageHandler { + return object : MessageHandler(world) { + + override fun handle(player: Player, message: ObjectActionMessage) { + val def = ObjectDefinition.lookup(message.id) + val option = def.menuActions[message.option] + + val target = world.regionRepository + .fromPosition(message.position) + .getEntities(message.position, EntityType.DYNAMIC_OBJECT, EntityType.STATIC_OBJECT) + .find { it.definition == def } + ?: return // Could happen if object was despawned this tick, before calling this handle function + + val context = when { // Evaluation-order matters here. + predicateContext == null -> ObjectAction(player, option, target, null) + !predicateContext.option.equals(option, ignoreCase = true) -> return + predicateContext.objects.isEmpty() -> ObjectAction(player, option, target, null) + predicateContext.objects.any { it.instanceOf(target) } -> { + val interactive = predicateContext.objects.find { it.instanceOf(target) } ?: return + ObjectAction(player, option, target, interactive) + } + else -> return + } + + context.callback() + } + + } + } + + } + +} diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/obj/ObjectActionPredicateContext.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/obj/ObjectActionPredicateContext.kt new file mode 100644 index 000000000..42a643998 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/obj/ObjectActionPredicateContext.kt @@ -0,0 +1,8 @@ +package org.apollo.game.plugin.kotlin.message.action.obj + +import org.apollo.game.plugin.kotlin.message.action.ActionPredicateContext + +data class ObjectActionPredicateContext( + override val option: String, + val objects: List +) : ActionPredicateContext(option) \ No newline at end of file diff --git a/game/src/main/org/apollo/game/plugin/RubyPluginEnvironment.java b/game/src/main/org/apollo/game/plugin/RubyPluginEnvironment.java deleted file mode 100644 index 9ac48c67c..000000000 --- a/game/src/main/org/apollo/game/plugin/RubyPluginEnvironment.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.apollo.game.plugin; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -import org.apollo.game.model.World; -import org.jruby.embed.ScriptingContainer; - -/** - * A {@link PluginEnvironment} which uses Ruby. - * - * @author Graham - */ -public final class RubyPluginEnvironment implements PluginEnvironment { - - /** - * The scripting container. - */ - private final ScriptingContainer container = new ScriptingContainer(); - - /** - * Creates and bootstraps the Ruby plugin environment. - * - * @param world The {@link World} this RubyPluginEnvironment is for. - * @throws IOException If an I/O error occurs during bootstrapping. - */ - public RubyPluginEnvironment(World world) throws IOException { - container.put("$world", world); - parseBootstrapper(); - } - - @Override - public void parse(InputStream is, String name) { - try { - container.runScriptlet(is, name); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException("Error parsing scriptlet " + name + ".", e); - } - } - - /** - * Parses the bootstrapper. - * - * @throws IOException If an I/O error occurs. - */ - private void parseBootstrapper() throws IOException { - File bootstrap = new File("./data/plugins/bootstrap.rb"); - try (InputStream is = new FileInputStream(bootstrap)) { - parse(is, bootstrap.getAbsolutePath()); - } - } - - @Override - public void setContext(PluginContext context) { - container.put("$ctx", context); - } - -} \ No newline at end of file diff --git a/game/src/main/resources/META-INF/kotlin/script/templates/org.apollo.game.plugin.kotlin.KotlinPluginScript b/game/src/main/resources/META-INF/kotlin/script/templates/org.apollo.game.plugin.kotlin.KotlinPluginScript new file mode 100644 index 000000000..e69de29bb diff --git a/game/src/test/org/apollo/game/message/handler/ItemOnItemVerificationHandlerTests.java b/game/src/test/java/org/apollo/game/message/handler/ItemOnItemVerificationHandlerTests.java similarity index 100% rename from game/src/test/org/apollo/game/message/handler/ItemOnItemVerificationHandlerTests.java rename to game/src/test/java/org/apollo/game/message/handler/ItemOnItemVerificationHandlerTests.java diff --git a/game/src/test/org/apollo/game/message/handler/ItemOnObjectVerificationHandlerTests.java b/game/src/test/java/org/apollo/game/message/handler/ItemOnObjectVerificationHandlerTests.java similarity index 100% rename from game/src/test/org/apollo/game/message/handler/ItemOnObjectVerificationHandlerTests.java rename to game/src/test/java/org/apollo/game/message/handler/ItemOnObjectVerificationHandlerTests.java diff --git a/game/src/test/org/apollo/game/message/handler/ObjectActionVerificationHandlerTests.java b/game/src/test/java/org/apollo/game/message/handler/ObjectActionVerificationHandlerTests.java similarity index 100% rename from game/src/test/org/apollo/game/message/handler/ObjectActionVerificationHandlerTests.java rename to game/src/test/java/org/apollo/game/message/handler/ObjectActionVerificationHandlerTests.java diff --git a/game/src/test/org/apollo/game/message/handler/PublicChatMessageHandlerTests.java b/game/src/test/java/org/apollo/game/message/handler/PublicChatMessageHandlerTests.java similarity index 100% rename from game/src/test/org/apollo/game/message/handler/PublicChatMessageHandlerTests.java rename to game/src/test/java/org/apollo/game/message/handler/PublicChatMessageHandlerTests.java diff --git a/game/src/test/org/apollo/game/model/PositionTests.java b/game/src/test/java/org/apollo/game/model/PositionTests.java similarity index 100% rename from game/src/test/org/apollo/game/model/PositionTests.java rename to game/src/test/java/org/apollo/game/model/PositionTests.java diff --git a/game/src/test/org/apollo/game/model/area/collision/CollisionManagerTests.java b/game/src/test/java/org/apollo/game/model/area/collision/CollisionManagerTests.java similarity index 84% rename from game/src/test/org/apollo/game/model/area/collision/CollisionManagerTests.java rename to game/src/test/java/org/apollo/game/model/area/collision/CollisionManagerTests.java index d0f528975..056793e4a 100644 --- a/game/src/test/org/apollo/game/model/area/collision/CollisionManagerTests.java +++ b/game/src/test/java/org/apollo/game/model/area/collision/CollisionManagerTests.java @@ -85,9 +85,10 @@ public void wall() { assertUntraversable(collisionManager, back, Direction.SOUTH_EAST); } + /** - * Tests that a corner wall is untraversable from the sides that it blocks. Corners are much like walls, with - * the only difference being their orientation. Instead of {@link Direction#WNES}, they have diagonal directions. + * Tests that a corner triangular wall is untraversable from the sides that it blocks. Corners are much like walls, + * with the only difference being their orientation. Instead of {@link Direction#WNES}, they have diagonal directions. * With a corner wall at (0, 1) facing to the north-east we end up with a grid looking like this: *

*

@@ -121,6 +122,42 @@ public void cornerWall() {
 		assertUntraversable(collisionManager, back, Direction.SOUTH_WEST);
 	}
 
+	/**
+	 * Tests that a corner rectangle wall is untraversable from the sides that it blocks.  Corners are much like walls,
+	 * with the only difference being their orientation.  Instead of {@link Direction#WNES}, they have diagonal directions.
+	 * With a corner wall at (0, 1) facing to the north-east we end up with a grid looking like this:
+	 * 

+ *

+	 * (0,2) |---------|---------|
+	 *       |         |         |
+	 *       |         |         |
+	 *       |         |xxx      |
+	 * (0,1) |---------|---------|
+	 *       |      xxx|         |
+	 *       |         |         |
+	 *       |         |         |
+	 * (0,0) |---------|---------|
+	 * 	   (1,0)     (2,0)     (3,0)
+	 * 
+ *

+ * Where you can walk north and east through the tile the corner occupies, as well as south and west through the + * adjacent tile, but not north-east or south-west. + */ + @Test + public void rectangleWall() { + Position front = new Position(0, 1, 0); + Position back = front.step(1, Direction.NORTH_EAST); + + CollisionManager collisionManager = createCollisionManager( + createMapObject(WALL, front, ObjectType.RECTANGULAR_CORNER, Direction.NORTH_EAST) + ); + + assertTraversable(collisionManager, front, Direction.NORTH, Direction.EAST); + assertUntraversable(collisionManager, front, Direction.NORTH_EAST); + assertTraversable(collisionManager, back, Direction.SOUTH, Direction.WEST); + assertUntraversable(collisionManager, back, Direction.SOUTH_WEST); + } + /** * Tests that the tiles occupied by a 2x2 square object are not traversable. When an interactable object is added * to a collision update, all tiles spanning its with and length from its origin position will be marked as completely diff --git a/game/src/test/org/apollo/game/model/entity/MobRepositoryTests.java b/game/src/test/java/org/apollo/game/model/entity/MobRepositoryTests.java similarity index 100% rename from game/src/test/org/apollo/game/model/entity/MobRepositoryTests.java rename to game/src/test/java/org/apollo/game/model/entity/MobRepositoryTests.java diff --git a/game/src/test/org/apollo/game/model/entity/SkillSetTests.java b/game/src/test/java/org/apollo/game/model/entity/SkillSetTests.java similarity index 100% rename from game/src/test/org/apollo/game/model/entity/SkillSetTests.java rename to game/src/test/java/org/apollo/game/model/entity/SkillSetTests.java diff --git a/game/src/test/java/org/apollo/game/model/entity/WalkingQueueTests.java b/game/src/test/java/org/apollo/game/model/entity/WalkingQueueTests.java new file mode 100644 index 000000000..51dd4d60c --- /dev/null +++ b/game/src/test/java/org/apollo/game/model/entity/WalkingQueueTests.java @@ -0,0 +1,82 @@ +package org.apollo.game.model.entity; + +import org.apollo.game.model.Position; +import org.apollo.game.model.World; +import org.apollo.game.model.area.Region; +import org.apollo.game.model.area.RegionRepository; +import org.apollo.game.model.area.collision.CollisionManager; +import org.apollo.util.security.PlayerCredentials; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.spy; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({World.class, CollisionManager.class}) +public class WalkingQueueTests { + private static final Position START_POSITION = new Position(1, 1); + private static final Position END_WALK_POSITION = new Position(2, 1); + private static final Position END_RUN_POSITION = new Position(3, 1); + + private World createFakeWorld(boolean traversable) { + World world = spy(World.class); + CollisionManager collisionManager = mock(CollisionManager.class); + when(collisionManager.traversable(any(), any(), any())).thenReturn(traversable); + when(world.getCollisionManager()).thenReturn(collisionManager); + + return world; + } + + private Player createFakePlayer(World world) { + PlayerCredentials credentials = new PlayerCredentials("test", "test", -1, -1, "0.0.0.0"); + Player player = new Player(world, credentials, START_POSITION); + + Region region = world.getRegionRepository().fromPosition(START_POSITION); + region.addEntity(player); + + return player; + } + + @Test + public void doesntMoveWhenTileIsBlocked() { + World world = createFakeWorld(false); + Player player = createFakePlayer(world); + + WalkingQueue walkingQueue = new WalkingQueue(player); + walkingQueue.addStep(END_WALK_POSITION); + walkingQueue.pulse(); + + assertEquals(START_POSITION, player.getPosition()); + } + + @Test + public void movesOneTileWhenWalking() throws Exception { + World world = createFakeWorld(true); + Player player = createFakePlayer(world); + + WalkingQueue walkingQueue = new WalkingQueue(player); + walkingQueue.addStep(END_WALK_POSITION); + walkingQueue.pulse(); + + assertEquals(END_WALK_POSITION, player.getPosition()); + } + + @Test + public void movesTwoTilesWhenRunning() throws Exception { + World world = createFakeWorld(true); + Player player = createFakePlayer(world); + + WalkingQueue walkingQueue = new WalkingQueue(player); + walkingQueue.setRunning(true); + walkingQueue.addStep(END_RUN_POSITION); + walkingQueue.pulse(); + + assertEquals(END_RUN_POSITION, player.getPosition()); + } +} \ No newline at end of file diff --git a/game/src/test/org/apollo/game/model/entity/attr/AttributeTests.java b/game/src/test/java/org/apollo/game/model/entity/attr/AttributeTests.java similarity index 100% rename from game/src/test/org/apollo/game/model/entity/attr/AttributeTests.java rename to game/src/test/java/org/apollo/game/model/entity/attr/AttributeTests.java diff --git a/game/src/test/kotlin/org/apollo/game/action/ActionCoroutineTest.kt b/game/src/test/kotlin/org/apollo/game/action/ActionCoroutineTest.kt new file mode 100644 index 000000000..a8f165cef --- /dev/null +++ b/game/src/test/kotlin/org/apollo/game/action/ActionCoroutineTest.kt @@ -0,0 +1,38 @@ +package org.apollo.game.action + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class ActionCoroutineTest { + @Test + fun `Coroutine execution resumes after a pulse() call`() { + val coroutine = ActionCoroutine.start { + wait(1) + } + + coroutine.pulse() + assertTrue(coroutine.stopped()) + } + + @Test + fun `Coroutine suspends on wait() calls`() { + val coroutine = ActionCoroutine.start { + wait(1) + } + + // Check that the continuation is still waiting on a pulse. + assertFalse(coroutine.stopped()) + } + + @Test + fun `Coroutine cancels on stop() calls`() { + val coroutine = ActionCoroutine.start { + stop() + } + + assertTrue(coroutine.stopped()) + coroutine.pulse() + assertTrue(coroutine.stopped()) + } +} \ No newline at end of file diff --git a/gradle/code-quality.gradle b/gradle/code-quality.gradle new file mode 100644 index 000000000..98b7506f2 --- /dev/null +++ b/gradle/code-quality.gradle @@ -0,0 +1,19 @@ +def detektAggregateReport = "$buildDir/reports/detekt/detekt.xml" + +repositories { + maven { url "https://repo.spring.io/plugins-release/" } +} + +detekt { + toolVersion = detektVersion + input = files(rootProject.projectDir.absolutePath) + filters = ".*/resources/.*, .*/build/.*" + config = files("$rootDir/gradle/config/detekt.yml") + parallel = true +} + +sonarqube { + properties { + property "sonar.kotlin.detekt.reportPaths", detektAggregateReport + } +} \ No newline at end of file diff --git a/gradle/config/checkstyle.xml b/gradle/config/checkstyle.xml new file mode 100644 index 000000000..e69de29bb diff --git a/gradle/config/detekt.yml b/gradle/config/detekt.yml new file mode 100644 index 000000000..5ff588626 --- /dev/null +++ b/gradle/config/detekt.yml @@ -0,0 +1,479 @@ +autoCorrect: true +failFast: false + +test-pattern: # Configure exclusions for test sources + active: true + patterns: # Test file regexes + - '.*/test/.*' + - '.*Test.kt' + - '.*Spec.kt' + exclude-rule-sets: + - 'comments' + exclude-rules: + - 'NamingRules' + - 'WildcardImport' + - 'MagicNumber' + - 'MaxLineLength' + - 'LateinitUsage' + - 'StringLiteralDuplication' + - 'SpreadOperator' + - 'TooManyFunctions' + - 'ForEachOnRange' + +build: + maxIssues: -1 + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +processors: + active: true + exclude: + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ClassCountProcessor' + # - 'PackageCountProcessor' + # - 'KtFileCountProcessor' + +console-reports: + active: true + exclude: + # - 'ProjectStatisticsReport' + # - 'ComplexityReport' + # - 'NotificationReport' + # - 'FindingsReport' + # - 'BuildFailureReport' + +output-reports: + active: true + exclude: + # - 'HtmlOutputReport' + # - 'PlainOutputReport' + # - 'XmlOutputReport' + +comments: + active: true + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!]$) + UndocumentedPublicClass: + active: false + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + UndocumentedPublicFunction: + active: false + +complexity: + active: true + ComplexCondition: + active: false + threshold: 4 + ComplexInterface: + active: false + threshold: 10 + includeStaticDeclarations: false + ComplexMethod: + active: false + threshold: 10 + ignoreSingleWhenExpression: false + LabeledExpression: + active: false + LargeClass: + active: true + threshold: 150 + LongMethod: + active: false + threshold: 20 + LongParameterList: + active: false + threshold: 6 + ignoreDefaultParameters: false + MethodOverloading: + active: false + threshold: 6 + NestedBlockDepth: + active: true + threshold: 4 + StringLiteralDuplication: + active: false + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + thresholdInFiles: 11 + thresholdInClasses: 11 + thresholdInInterfaces: 11 + thresholdInObjects: 11 + thresholdInEnums: 11 + ignoreDeprecated: false + ignorePrivate: false + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverriddenFunctions: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: false + methodNames: 'toString,hashCode,equals,finalize' + InstanceOfCheckForException: + active: false + NotImplementedDeclaration: + active: false + PrintStackTrace: + active: false + RethrowCaughtException: + active: false + ReturnFromFinally: + active: false + SwallowedException: + active: false + ThrowingExceptionFromFinally: + active: false + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: false + exceptions: 'IllegalArgumentException,IllegalStateException,IOException' + ThrowingNewInstanceOfSameException: + active: false + TooGenericExceptionCaught: + active: true + exceptionNames: + - ArrayIndexOutOfBoundsException + - Error + - Exception + - IllegalMonitorStateException + - NullPointerException + - IndexOutOfBoundsException + - RuntimeException + - Throwable + TooGenericExceptionThrown: + active: true + exceptionNames: + - Error + - Exception + - Throwable + - RuntimeException + +formatting: + active: true + android: false + autoCorrect: true + ChainWrapping: + active: true + autoCorrect: true + CommentSpacing: + active: true + autoCorrect: true + Filename: + active: true + FinalNewline: + active: true + autoCorrect: true + ImportOrdering: + active: true + autoCorrect: true + Indentation: + active: true + autoCorrect: true + indentSize: 4 + continuationIndentSize: 4 + MaximumLineLength: + active: false + ModifierOrdering: + active: true + autoCorrect: true + NoBlankLineBeforeRbrace: + active: true + autoCorrect: true + NoConsecutiveBlankLines: + active: true + autoCorrect: true + NoEmptyClassBody: + active: true + autoCorrect: true + NoItParamInMultilineLambda: + active: false + NoLineBreakAfterElse: + active: true + autoCorrect: true + NoLineBreakBeforeAssignment: + active: true + autoCorrect: true + NoMultipleSpaces: + active: true + autoCorrect: true + NoSemicolons: + active: true + autoCorrect: true + NoTrailingSpaces: + active: true + autoCorrect: true + NoUnitReturn: + active: true + autoCorrect: true + NoUnusedImports: + active: true + autoCorrect: true + NoWildcardImports: + active: false + autoCorrect: true + ParameterListWrapping: + active: true + autoCorrect: true + indentSize: 4 + SpacingAroundColon: + active: true + autoCorrect: true + SpacingAroundComma: + active: true + autoCorrect: true + SpacingAroundCurly: + active: true + autoCorrect: true + SpacingAroundKeyword: + active: true + autoCorrect: true + SpacingAroundOperators: + active: true + autoCorrect: true + SpacingAroundRangeOperator: + active: true + autoCorrect: true + StringTemplate: + active: true + autoCorrect: true + +naming: + active: true + ClassNaming: + active: true + classPattern: '[A-Z$][a-zA-Z0-9$]*' + EnumNaming: + active: true + enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + forbiddenName: '' + FunctionMaxLength: + active: false + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)|(on_[a-zA-Z_$0-9]*)$' + excludeClassPattern: '$^' + MatchingDeclarationName: + active: true + autoCorrect: true + MemberNameEqualsClassName: + active: false + ignoreOverriddenFunction: true + ObjectPropertyNaming: + active: true + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + packagePattern: '^[a-z]+(\.[a-z][a-zA-Z0-9]*)*$' + TopLevelPropertyNaming: + active: false + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][A-Za-z0-9]*' + VariableMaxLength: + active: false + maximumVariableNameLength: 64 + VariableMinLength: + active: false + minimumVariableNameLength: 1 + VariableNaming: + active: false + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + +performance: + active: true + ForEachOnRange: + active: true + SpreadOperator: + active: true + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + DuplicateCaseInWhenExpression: + active: true + EqualsAlwaysReturnsTrueOrFalse: + active: false + EqualsWithHashCodeExist: + active: true + ExplicitGarbageCollectionCall: + active: true + InvalidRange: + active: false + IteratorHasNextCallsNextMethod: + active: false + IteratorNotThrowingNoSuchElementException: + active: false + LateinitUsage: + active: false + excludeAnnotatedProperties: "" + ignoreOnClassesPattern: "" + UnconditionalJumpStatementInLoop: + active: false + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: false + UnsafeCast: + active: false + UselessPostfixExpression: + active: false + WrongEqualsTypeParameter: + active: false + +style: + active: true + CollapsibleIfStatements: + active: false + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: 'to' + EqualsNullCall: + active: false + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenComment: + active: true + values: 'TODO:,FIXME:,STOPSHIP:' + ForbiddenImport: + active: false + imports: '' + FunctionOnlyReturningConstant: + active: false + ignoreOverridableFunction: true + excludedFunctions: 'describeContents' + LoopWithTooManyJumpStatements: + active: false + maxJumpCount: 1 + MagicNumber: + active: false + MandatoryBracesIfStatements: + active: false + MaxLineLength: + active: false + maxLineLength: 120 + excludePackageStatements: false + excludeImportStatements: false + excludeCommentStatements: false + MayBeConst: + active: false + ModifierOrder: + active: true + NestedClassesVisibility: + active: false + NewLineAtEndOfFile: + active: false + NoTabs: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: false + OptionalWhenBraces: + active: false + PreferToOverPairSyntax: + active: false + ProtectedMemberInFinalClass: + active: false + RedundantVisibilityModifierRule: + active: false + ReturnCount: + active: true + max: 2 + excludedFunctions: "equals" + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: false + SpacingBetweenPackageAndImports: + active: false + ThrowsCount: + active: true + max: 2 + TrailingWhitespace: + active: false + UnnecessaryAbstractClass: + active: false + UnnecessaryInheritance: + active: false + UnnecessaryParentheses: + active: false + UntilInsteadOfRangeTo: + active: false + UnusedImports: + active: false + UnusedPrivateMember: + active: false + allowedNames: "(_|ignored|expected)" + UseDataClass: + active: false + excludeAnnotatedClasses: "" + UtilityClassWithPublicConstructor: + active: false + VarCouldBeVal: + active: false + WildcardImport: + active: false + excludeImports: 'java.util.*,kotlinx.android.synthetic.*' + +apollo-plugin: + DeclarationInScript: + active: true \ No newline at end of file diff --git a/gradle/kotlin.gradle b/gradle/kotlin.gradle new file mode 100644 index 000000000..04894ed9f --- /dev/null +++ b/gradle/kotlin.gradle @@ -0,0 +1,3 @@ +kotlin { + +} diff --git a/gradle/properties.gradle b/gradle/properties.gradle new file mode 100644 index 000000000..63d32988d --- /dev/null +++ b/gradle/properties.gradle @@ -0,0 +1,20 @@ +ext { + kotlinVersion = '1.3.40' + kotlinxCoroutinesVersion = '1.3.0-M2' + junitVersion = '4.12' + powermockVersion = '2.0.2' + bouncycastleVersion = '1.54' + c3p0Version = '0.9.5.2' + scryptVersion = '1.4.0' + nettyVersion = '4.0.34.Final' + guavaVersion = '19.0' + commonsCompressVersion = '1.10' + assertjVersion = '3.8.0' + classGraphVersion = '4.0.6' + junitVintageVersion = '5.1.0' + junitPlatformVersion = '1.1.0' + junitJupiterVersion = '5.1.0' + mockkVersion = '1.7.15' + assertkVersion = '0.9' + detektVersion = '1.0.0-RC16' +} \ No newline at end of file diff --git a/gradle/testing.gradle b/gradle/testing.gradle new file mode 100644 index 000000000..5b3a7cd17 --- /dev/null +++ b/gradle/testing.gradle @@ -0,0 +1,81 @@ +apply plugin: "jacoco" + +def jacocoCoverageAggregate = "$buildDir/jacoco/jacocoTestAll.exec" + +def testedProjects() { + subprojects.findAll { subproject -> subproject.plugins.hasPlugin('java') || subproject.plugins.hasPlugin('kotlin') } +} +gradle.projectsEvaluated { + configure(testedProjects()) { + apply plugin: "jacoco" + + jacoco { + toolVersion = '0.8.4' + } + + test { + reports { + junitXml.enabled = true + html.enabled = false + } + + jacoco { + append = false + destinationFile = file("$buildDir/jacoco/jacocoTest.exec") + classDumpDir = file("$buildDir/jacoco/classpathdumps") + } + } + } + + task jacocoMerge(type: JacocoMerge) { + destinationFile = file(jacocoCoverageAggregate) + executionData = project.fileTree(dir: '.', include: '**/build/jacoco/jacocoTest.exec') + + dependsOn(getTasksByName('test', true)) + } + + + task jacocoReport(type: JacocoReport) { + sourceDirectories = files() + classDirectories = files() + executionData = files() + + reports { + html.enabled = true + xml.enabled = true + csv.enabled = false + } + + // Work-around to allow us to build list of executionData files in doFirst + onlyIf = { + true + } + + /* + * Builds list of source dirs, class dirs, and executionData files + * when task is run, not at script evaluation time + */ + doFirst { + subprojects.findAll { subproject -> + subproject.pluginManager.hasPlugin('java') || subproject.pluginManager.hasPlugin('kotlin') + }.each { subproject -> + additionalSourceDirs files((Set) subproject.sourceSets.main.allSource.srcDirs) + additionalClassDirs((FileCollection) subproject.sourceSets.main.output) + } + + executionData = files(jacocoCoverageAggregate) + } + + dependsOn(jacocoMerge) + } + + sonarqube { + properties { + property "sonar.organization", "apollo-rsps" + property "sonar.projectKey", "apollo:org.apollo" + property "sonar.projectName", "Apollo RSPS" + property "sonar.kotlin.file.suffixes", ".kt,.kts" + property "sonar.jacoco.reportPaths", jacocoCoverageAggregate + } + } +} diff --git a/gradle/wrapper.gradle b/gradle/wrapper.gradle new file mode 100644 index 000000000..19b869f71 --- /dev/null +++ b/gradle/wrapper.gradle @@ -0,0 +1,3 @@ +wrapper { + gradleVersion = "5.5" +} diff --git a/gradle/wrapper/code-quality.gradle b/gradle/wrapper/code-quality.gradle new file mode 100644 index 000000000..e69de29bb diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..0d4a95168 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 000000000..ce16fd39a --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Jul 16 03:37:52 BST 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..cccdd3d51 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# 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" + which java >/dev/null 2>&1 || 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 + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..e95643d6a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@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=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/net/build.gradle b/net/build.gradle index b004467da..0b1045c36 100644 --- a/net/build.gradle +++ b/net/build.gradle @@ -1,6 +1,19 @@ +apply plugin: 'java-library' + description = 'Apollo Net' dependencies { - compile project(':cache') - compile project(':util') + api project(':cache') + + implementation project(':util') + implementation group: 'io.netty', name: 'netty-all', version: nettyVersion + implementation group: 'com.google.guava', name: 'guava', version: guavaVersion + implementation group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: bouncycastleVersion + + test.useJUnitPlatform() + testImplementation group: 'junit', name: 'junit', version: junitVersion + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junitJupiterVersion + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junitJupiterVersion + testImplementation group: 'org.junit.vintage', name: 'junit-vintage-engine', version: junitVintageVersion + testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: junitPlatformVersion } diff --git a/net/src/main/org/apollo/net/HttpChannelInitializer.java b/net/src/main/java/org/apollo/net/HttpChannelInitializer.java similarity index 100% rename from net/src/main/org/apollo/net/HttpChannelInitializer.java rename to net/src/main/java/org/apollo/net/HttpChannelInitializer.java diff --git a/net/src/main/org/apollo/net/JagGrabChannelInitializer.java b/net/src/main/java/org/apollo/net/JagGrabChannelInitializer.java similarity index 100% rename from net/src/main/org/apollo/net/JagGrabChannelInitializer.java rename to net/src/main/java/org/apollo/net/JagGrabChannelInitializer.java diff --git a/net/src/main/org/apollo/net/NetworkConstants.java b/net/src/main/java/org/apollo/net/NetworkConstants.java similarity index 100% rename from net/src/main/org/apollo/net/NetworkConstants.java rename to net/src/main/java/org/apollo/net/NetworkConstants.java diff --git a/net/src/main/org/apollo/net/ServiceChannelInitializer.java b/net/src/main/java/org/apollo/net/ServiceChannelInitializer.java similarity index 100% rename from net/src/main/org/apollo/net/ServiceChannelInitializer.java rename to net/src/main/java/org/apollo/net/ServiceChannelInitializer.java diff --git a/net/src/main/org/apollo/net/codec/game/AccessMode.java b/net/src/main/java/org/apollo/net/codec/game/AccessMode.java similarity index 100% rename from net/src/main/org/apollo/net/codec/game/AccessMode.java rename to net/src/main/java/org/apollo/net/codec/game/AccessMode.java diff --git a/net/src/main/org/apollo/net/codec/game/DataConstants.java b/net/src/main/java/org/apollo/net/codec/game/DataConstants.java similarity index 100% rename from net/src/main/org/apollo/net/codec/game/DataConstants.java rename to net/src/main/java/org/apollo/net/codec/game/DataConstants.java diff --git a/net/src/main/org/apollo/net/codec/game/DataOrder.java b/net/src/main/java/org/apollo/net/codec/game/DataOrder.java similarity index 100% rename from net/src/main/org/apollo/net/codec/game/DataOrder.java rename to net/src/main/java/org/apollo/net/codec/game/DataOrder.java diff --git a/net/src/main/org/apollo/net/codec/game/DataTransformation.java b/net/src/main/java/org/apollo/net/codec/game/DataTransformation.java similarity index 100% rename from net/src/main/org/apollo/net/codec/game/DataTransformation.java rename to net/src/main/java/org/apollo/net/codec/game/DataTransformation.java diff --git a/net/src/main/org/apollo/net/codec/game/DataType.java b/net/src/main/java/org/apollo/net/codec/game/DataType.java similarity index 100% rename from net/src/main/org/apollo/net/codec/game/DataType.java rename to net/src/main/java/org/apollo/net/codec/game/DataType.java diff --git a/net/src/main/org/apollo/net/codec/game/GameDecoderState.java b/net/src/main/java/org/apollo/net/codec/game/GameDecoderState.java similarity index 100% rename from net/src/main/org/apollo/net/codec/game/GameDecoderState.java rename to net/src/main/java/org/apollo/net/codec/game/GameDecoderState.java diff --git a/net/src/main/org/apollo/net/codec/game/GameMessageDecoder.java b/net/src/main/java/org/apollo/net/codec/game/GameMessageDecoder.java similarity index 100% rename from net/src/main/org/apollo/net/codec/game/GameMessageDecoder.java rename to net/src/main/java/org/apollo/net/codec/game/GameMessageDecoder.java diff --git a/net/src/main/org/apollo/net/codec/game/GameMessageEncoder.java b/net/src/main/java/org/apollo/net/codec/game/GameMessageEncoder.java similarity index 100% rename from net/src/main/org/apollo/net/codec/game/GameMessageEncoder.java rename to net/src/main/java/org/apollo/net/codec/game/GameMessageEncoder.java diff --git a/net/src/main/org/apollo/net/codec/game/GamePacket.java b/net/src/main/java/org/apollo/net/codec/game/GamePacket.java similarity index 81% rename from net/src/main/org/apollo/net/codec/game/GamePacket.java rename to net/src/main/java/org/apollo/net/codec/game/GamePacket.java index 0f51e9cbf..5c082f82e 100644 --- a/net/src/main/org/apollo/net/codec/game/GamePacket.java +++ b/net/src/main/java/org/apollo/net/codec/game/GamePacket.java @@ -2,6 +2,7 @@ import io.netty.buffer.ByteBuf; +import io.netty.buffer.DefaultByteBufHolder; import org.apollo.net.meta.PacketType; /** @@ -9,7 +10,7 @@ * * @author Graham */ -public final class GamePacket { +public final class GamePacket extends DefaultByteBufHolder { /** * The length. @@ -21,11 +22,6 @@ public final class GamePacket { */ private final int opcode; - /** - * The payload. - */ - private final ByteBuf payload; - /** * The packet type. */ @@ -39,10 +35,10 @@ public final class GamePacket { * @param payload The payload. */ public GamePacket(int opcode, PacketType type, ByteBuf payload) { + super(payload); this.opcode = opcode; this.type = type; length = payload.readableBytes(); - this.payload = payload; } /** @@ -63,15 +59,6 @@ public int getOpcode() { return opcode; } - /** - * Gets the payload. - * - * @return The payload. - */ - public ByteBuf getPayload() { - return payload; - } - /** * Gets the packet type. * diff --git a/net/src/main/org/apollo/net/codec/game/GamePacketBuilder.java b/net/src/main/java/org/apollo/net/codec/game/GamePacketBuilder.java similarity index 100% rename from net/src/main/org/apollo/net/codec/game/GamePacketBuilder.java rename to net/src/main/java/org/apollo/net/codec/game/GamePacketBuilder.java diff --git a/net/src/main/org/apollo/net/codec/game/GamePacketDecoder.java b/net/src/main/java/org/apollo/net/codec/game/GamePacketDecoder.java similarity index 100% rename from net/src/main/org/apollo/net/codec/game/GamePacketDecoder.java rename to net/src/main/java/org/apollo/net/codec/game/GamePacketDecoder.java diff --git a/net/src/main/org/apollo/net/codec/game/GamePacketEncoder.java b/net/src/main/java/org/apollo/net/codec/game/GamePacketEncoder.java similarity index 97% rename from net/src/main/org/apollo/net/codec/game/GamePacketEncoder.java rename to net/src/main/java/org/apollo/net/codec/game/GamePacketEncoder.java index 0a9bfa1e6..a5fda4faf 100644 --- a/net/src/main/org/apollo/net/codec/game/GamePacketEncoder.java +++ b/net/src/main/java/org/apollo/net/codec/game/GamePacketEncoder.java @@ -44,7 +44,7 @@ protected void encode(ChannelHandlerContext ctx, GamePacket packet, ByteBuf out) } else if (type == PacketType.VARIABLE_SHORT) { out.writeShort(payloadLength); } - out.writeBytes(packet.getPayload()); + out.writeBytes(packet.content()); } } \ No newline at end of file diff --git a/net/src/main/org/apollo/net/codec/game/GamePacketReader.java b/net/src/main/java/org/apollo/net/codec/game/GamePacketReader.java similarity index 99% rename from net/src/main/org/apollo/net/codec/game/GamePacketReader.java rename to net/src/main/java/org/apollo/net/codec/game/GamePacketReader.java index 00e4ca9e1..0f8bae99b 100644 --- a/net/src/main/org/apollo/net/codec/game/GamePacketReader.java +++ b/net/src/main/java/org/apollo/net/codec/game/GamePacketReader.java @@ -34,7 +34,7 @@ public final class GamePacketReader { * @param packet The packet. */ public GamePacketReader(GamePacket packet) { - buffer = packet.getPayload(); + buffer = packet.content(); } /** diff --git a/net/src/main/org/apollo/net/codec/game/package-info.java b/net/src/main/java/org/apollo/net/codec/game/package-info.java similarity index 100% rename from net/src/main/org/apollo/net/codec/game/package-info.java rename to net/src/main/java/org/apollo/net/codec/game/package-info.java diff --git a/net/src/main/org/apollo/net/codec/handshake/HandshakeConstants.java b/net/src/main/java/org/apollo/net/codec/handshake/HandshakeConstants.java similarity index 100% rename from net/src/main/org/apollo/net/codec/handshake/HandshakeConstants.java rename to net/src/main/java/org/apollo/net/codec/handshake/HandshakeConstants.java diff --git a/net/src/main/org/apollo/net/codec/handshake/HandshakeDecoder.java b/net/src/main/java/org/apollo/net/codec/handshake/HandshakeDecoder.java similarity index 100% rename from net/src/main/org/apollo/net/codec/handshake/HandshakeDecoder.java rename to net/src/main/java/org/apollo/net/codec/handshake/HandshakeDecoder.java diff --git a/net/src/main/org/apollo/net/codec/handshake/HandshakeMessage.java b/net/src/main/java/org/apollo/net/codec/handshake/HandshakeMessage.java similarity index 100% rename from net/src/main/org/apollo/net/codec/handshake/HandshakeMessage.java rename to net/src/main/java/org/apollo/net/codec/handshake/HandshakeMessage.java diff --git a/net/src/main/org/apollo/net/codec/handshake/package-info.java b/net/src/main/java/org/apollo/net/codec/handshake/package-info.java similarity index 100% rename from net/src/main/org/apollo/net/codec/handshake/package-info.java rename to net/src/main/java/org/apollo/net/codec/handshake/package-info.java diff --git a/net/src/main/org/apollo/net/codec/jaggrab/JagGrabRequest.java b/net/src/main/java/org/apollo/net/codec/jaggrab/JagGrabRequest.java similarity index 100% rename from net/src/main/org/apollo/net/codec/jaggrab/JagGrabRequest.java rename to net/src/main/java/org/apollo/net/codec/jaggrab/JagGrabRequest.java diff --git a/net/src/main/org/apollo/net/codec/jaggrab/JagGrabRequestDecoder.java b/net/src/main/java/org/apollo/net/codec/jaggrab/JagGrabRequestDecoder.java similarity index 100% rename from net/src/main/org/apollo/net/codec/jaggrab/JagGrabRequestDecoder.java rename to net/src/main/java/org/apollo/net/codec/jaggrab/JagGrabRequestDecoder.java diff --git a/net/src/main/org/apollo/net/codec/jaggrab/JagGrabResponse.java b/net/src/main/java/org/apollo/net/codec/jaggrab/JagGrabResponse.java similarity index 100% rename from net/src/main/org/apollo/net/codec/jaggrab/JagGrabResponse.java rename to net/src/main/java/org/apollo/net/codec/jaggrab/JagGrabResponse.java diff --git a/net/src/main/org/apollo/net/codec/jaggrab/JagGrabResponseEncoder.java b/net/src/main/java/org/apollo/net/codec/jaggrab/JagGrabResponseEncoder.java similarity index 100% rename from net/src/main/org/apollo/net/codec/jaggrab/JagGrabResponseEncoder.java rename to net/src/main/java/org/apollo/net/codec/jaggrab/JagGrabResponseEncoder.java diff --git a/net/src/main/org/apollo/net/codec/jaggrab/package-info.java b/net/src/main/java/org/apollo/net/codec/jaggrab/package-info.java similarity index 100% rename from net/src/main/org/apollo/net/codec/jaggrab/package-info.java rename to net/src/main/java/org/apollo/net/codec/jaggrab/package-info.java diff --git a/net/src/main/org/apollo/net/codec/login/LoginConstants.java b/net/src/main/java/org/apollo/net/codec/login/LoginConstants.java similarity index 100% rename from net/src/main/org/apollo/net/codec/login/LoginConstants.java rename to net/src/main/java/org/apollo/net/codec/login/LoginConstants.java diff --git a/net/src/main/org/apollo/net/codec/login/LoginDecoder.java b/net/src/main/java/org/apollo/net/codec/login/LoginDecoder.java similarity index 100% rename from net/src/main/org/apollo/net/codec/login/LoginDecoder.java rename to net/src/main/java/org/apollo/net/codec/login/LoginDecoder.java diff --git a/net/src/main/org/apollo/net/codec/login/LoginDecoderState.java b/net/src/main/java/org/apollo/net/codec/login/LoginDecoderState.java similarity index 100% rename from net/src/main/org/apollo/net/codec/login/LoginDecoderState.java rename to net/src/main/java/org/apollo/net/codec/login/LoginDecoderState.java diff --git a/net/src/main/org/apollo/net/codec/login/LoginEncoder.java b/net/src/main/java/org/apollo/net/codec/login/LoginEncoder.java similarity index 100% rename from net/src/main/org/apollo/net/codec/login/LoginEncoder.java rename to net/src/main/java/org/apollo/net/codec/login/LoginEncoder.java diff --git a/net/src/main/org/apollo/net/codec/login/LoginRequest.java b/net/src/main/java/org/apollo/net/codec/login/LoginRequest.java similarity index 100% rename from net/src/main/org/apollo/net/codec/login/LoginRequest.java rename to net/src/main/java/org/apollo/net/codec/login/LoginRequest.java diff --git a/net/src/main/org/apollo/net/codec/login/LoginResponse.java b/net/src/main/java/org/apollo/net/codec/login/LoginResponse.java similarity index 100% rename from net/src/main/org/apollo/net/codec/login/LoginResponse.java rename to net/src/main/java/org/apollo/net/codec/login/LoginResponse.java diff --git a/net/src/main/org/apollo/net/codec/login/package-info.java b/net/src/main/java/org/apollo/net/codec/login/package-info.java similarity index 100% rename from net/src/main/org/apollo/net/codec/login/package-info.java rename to net/src/main/java/org/apollo/net/codec/login/package-info.java diff --git a/net/src/main/org/apollo/net/codec/update/OnDemandRequest.java b/net/src/main/java/org/apollo/net/codec/update/OnDemandRequest.java similarity index 100% rename from net/src/main/org/apollo/net/codec/update/OnDemandRequest.java rename to net/src/main/java/org/apollo/net/codec/update/OnDemandRequest.java diff --git a/net/src/main/org/apollo/net/codec/update/OnDemandResponse.java b/net/src/main/java/org/apollo/net/codec/update/OnDemandResponse.java similarity index 100% rename from net/src/main/org/apollo/net/codec/update/OnDemandResponse.java rename to net/src/main/java/org/apollo/net/codec/update/OnDemandResponse.java diff --git a/net/src/main/org/apollo/net/codec/update/UpdateDecoder.java b/net/src/main/java/org/apollo/net/codec/update/UpdateDecoder.java similarity index 100% rename from net/src/main/org/apollo/net/codec/update/UpdateDecoder.java rename to net/src/main/java/org/apollo/net/codec/update/UpdateDecoder.java diff --git a/net/src/main/org/apollo/net/codec/update/UpdateEncoder.java b/net/src/main/java/org/apollo/net/codec/update/UpdateEncoder.java similarity index 100% rename from net/src/main/org/apollo/net/codec/update/UpdateEncoder.java rename to net/src/main/java/org/apollo/net/codec/update/UpdateEncoder.java diff --git a/net/src/main/org/apollo/net/codec/update/package-info.java b/net/src/main/java/org/apollo/net/codec/update/package-info.java similarity index 100% rename from net/src/main/org/apollo/net/codec/update/package-info.java rename to net/src/main/java/org/apollo/net/codec/update/package-info.java diff --git a/net/src/main/org/apollo/net/message/Message.java b/net/src/main/java/org/apollo/net/message/Message.java similarity index 100% rename from net/src/main/org/apollo/net/message/Message.java rename to net/src/main/java/org/apollo/net/message/Message.java diff --git a/net/src/main/org/apollo/net/message/package-info.java b/net/src/main/java/org/apollo/net/message/package-info.java similarity index 100% rename from net/src/main/org/apollo/net/message/package-info.java rename to net/src/main/java/org/apollo/net/message/package-info.java diff --git a/net/src/main/org/apollo/net/meta/PacketMetaData.java b/net/src/main/java/org/apollo/net/meta/PacketMetaData.java similarity index 100% rename from net/src/main/org/apollo/net/meta/PacketMetaData.java rename to net/src/main/java/org/apollo/net/meta/PacketMetaData.java diff --git a/net/src/main/org/apollo/net/meta/PacketMetaDataGroup.java b/net/src/main/java/org/apollo/net/meta/PacketMetaDataGroup.java similarity index 100% rename from net/src/main/org/apollo/net/meta/PacketMetaDataGroup.java rename to net/src/main/java/org/apollo/net/meta/PacketMetaDataGroup.java diff --git a/net/src/main/org/apollo/net/meta/PacketType.java b/net/src/main/java/org/apollo/net/meta/PacketType.java similarity index 100% rename from net/src/main/org/apollo/net/meta/PacketType.java rename to net/src/main/java/org/apollo/net/meta/PacketType.java diff --git a/net/src/main/org/apollo/net/meta/package-info.java b/net/src/main/java/org/apollo/net/meta/package-info.java similarity index 100% rename from net/src/main/org/apollo/net/meta/package-info.java rename to net/src/main/java/org/apollo/net/meta/package-info.java diff --git a/net/src/main/org/apollo/net/package-info.java b/net/src/main/java/org/apollo/net/package-info.java similarity index 100% rename from net/src/main/org/apollo/net/package-info.java rename to net/src/main/java/org/apollo/net/package-info.java diff --git a/net/src/main/org/apollo/net/release/MessageDecoder.java b/net/src/main/java/org/apollo/net/release/MessageDecoder.java similarity index 100% rename from net/src/main/org/apollo/net/release/MessageDecoder.java rename to net/src/main/java/org/apollo/net/release/MessageDecoder.java diff --git a/net/src/main/org/apollo/net/release/MessageEncoder.java b/net/src/main/java/org/apollo/net/release/MessageEncoder.java similarity index 100% rename from net/src/main/org/apollo/net/release/MessageEncoder.java rename to net/src/main/java/org/apollo/net/release/MessageEncoder.java diff --git a/net/src/main/org/apollo/net/release/Release.java b/net/src/main/java/org/apollo/net/release/Release.java similarity index 100% rename from net/src/main/org/apollo/net/release/Release.java rename to net/src/main/java/org/apollo/net/release/Release.java diff --git a/net/src/main/org/apollo/net/release/package-info.java b/net/src/main/java/org/apollo/net/release/package-info.java similarity index 100% rename from net/src/main/org/apollo/net/release/package-info.java rename to net/src/main/java/org/apollo/net/release/package-info.java diff --git a/net/src/main/org/apollo/net/update/ChannelRequest.java b/net/src/main/java/org/apollo/net/update/ChannelRequest.java similarity index 100% rename from net/src/main/org/apollo/net/update/ChannelRequest.java rename to net/src/main/java/org/apollo/net/update/ChannelRequest.java diff --git a/net/src/main/org/apollo/net/update/ComparableChannelRequest.java b/net/src/main/java/org/apollo/net/update/ComparableChannelRequest.java similarity index 100% rename from net/src/main/org/apollo/net/update/ComparableChannelRequest.java rename to net/src/main/java/org/apollo/net/update/ComparableChannelRequest.java diff --git a/net/src/main/org/apollo/net/update/HttpRequestWorker.java b/net/src/main/java/org/apollo/net/update/HttpRequestWorker.java similarity index 100% rename from net/src/main/org/apollo/net/update/HttpRequestWorker.java rename to net/src/main/java/org/apollo/net/update/HttpRequestWorker.java diff --git a/net/src/main/org/apollo/net/update/JagGrabRequestWorker.java b/net/src/main/java/org/apollo/net/update/JagGrabRequestWorker.java similarity index 100% rename from net/src/main/org/apollo/net/update/JagGrabRequestWorker.java rename to net/src/main/java/org/apollo/net/update/JagGrabRequestWorker.java diff --git a/net/src/main/org/apollo/net/update/OnDemandRequestWorker.java b/net/src/main/java/org/apollo/net/update/OnDemandRequestWorker.java similarity index 100% rename from net/src/main/org/apollo/net/update/OnDemandRequestWorker.java rename to net/src/main/java/org/apollo/net/update/OnDemandRequestWorker.java diff --git a/net/src/main/org/apollo/net/update/RequestWorker.java b/net/src/main/java/org/apollo/net/update/RequestWorker.java similarity index 100% rename from net/src/main/org/apollo/net/update/RequestWorker.java rename to net/src/main/java/org/apollo/net/update/RequestWorker.java diff --git a/net/src/main/org/apollo/net/update/UpdateConstants.java b/net/src/main/java/org/apollo/net/update/UpdateConstants.java similarity index 100% rename from net/src/main/org/apollo/net/update/UpdateConstants.java rename to net/src/main/java/org/apollo/net/update/UpdateConstants.java diff --git a/net/src/main/org/apollo/net/update/UpdateDispatcher.java b/net/src/main/java/org/apollo/net/update/UpdateDispatcher.java similarity index 100% rename from net/src/main/org/apollo/net/update/UpdateDispatcher.java rename to net/src/main/java/org/apollo/net/update/UpdateDispatcher.java diff --git a/net/src/main/org/apollo/net/update/package-info.java b/net/src/main/java/org/apollo/net/update/package-info.java similarity index 100% rename from net/src/main/org/apollo/net/update/package-info.java rename to net/src/main/java/org/apollo/net/update/package-info.java diff --git a/net/src/main/org/apollo/net/update/resource/CombinedResourceProvider.java b/net/src/main/java/org/apollo/net/update/resource/CombinedResourceProvider.java similarity index 100% rename from net/src/main/org/apollo/net/update/resource/CombinedResourceProvider.java rename to net/src/main/java/org/apollo/net/update/resource/CombinedResourceProvider.java diff --git a/net/src/main/org/apollo/net/update/resource/HypertextResourceProvider.java b/net/src/main/java/org/apollo/net/update/resource/HypertextResourceProvider.java similarity index 100% rename from net/src/main/org/apollo/net/update/resource/HypertextResourceProvider.java rename to net/src/main/java/org/apollo/net/update/resource/HypertextResourceProvider.java diff --git a/net/src/main/org/apollo/net/update/resource/ResourceProvider.java b/net/src/main/java/org/apollo/net/update/resource/ResourceProvider.java similarity index 100% rename from net/src/main/org/apollo/net/update/resource/ResourceProvider.java rename to net/src/main/java/org/apollo/net/update/resource/ResourceProvider.java diff --git a/net/src/main/org/apollo/net/update/resource/VirtualResourceProvider.java b/net/src/main/java/org/apollo/net/update/resource/VirtualResourceProvider.java similarity index 100% rename from net/src/main/org/apollo/net/update/resource/VirtualResourceProvider.java rename to net/src/main/java/org/apollo/net/update/resource/VirtualResourceProvider.java diff --git a/net/src/main/org/apollo/net/update/resource/package-info.java b/net/src/main/java/org/apollo/net/update/resource/package-info.java similarity index 100% rename from net/src/main/org/apollo/net/update/resource/package-info.java rename to net/src/main/java/org/apollo/net/update/resource/package-info.java diff --git a/net/src/test/org/apollo/net/codec/game/GamePacketEncoderTests.java b/net/src/test/java/org/apollo/net/codec/game/GamePacketEncoderTests.java similarity index 100% rename from net/src/test/org/apollo/net/codec/game/GamePacketEncoderTests.java rename to net/src/test/java/org/apollo/net/codec/game/GamePacketEncoderTests.java diff --git a/settings.gradle b/settings.gradle index 2bde76af5..b9ba28b0f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,39 @@ +import java.nio.file.Paths +import java.nio.file.Path + rootProject.name = 'org.apollo' include ':cache' include ':game' +include ':game:plugin' +include ':game:plugin-detekt-rules' +include ':game:plugin-testing' include ':net' include ':util' + +def pluginDirs = [ + rootProject.projectDir.toPath().resolve("game/plugin"), +] + +def processPluginDir(Path pluginDir) { + if (pluginDir.toFile().exists()) { + def pluginFileFinder = new FileNameFinder(); + def pluginFiles = pluginFileFinder.getFileNames(pluginDir.toString(), "**/*.gradle"); + + pluginFiles.each { filename -> + def path = Paths.get(filename) + def parentPath = path.parent + + if (parentPath == pluginDir) { + return + } + + def relativePath = pluginDir.relativize(parentPath) + def pluginName = relativePath.toString().replace(File.separator, ":") + + include ":game:plugin:$pluginName" + } + } +} + +pluginDirs.each { processPluginDir(it) } + diff --git a/util/build.gradle b/util/build.gradle index bbfee7502..aa4fb376e 100644 --- a/util/build.gradle +++ b/util/build.gradle @@ -1,5 +1,22 @@ +apply plugin: 'java-library' + description = 'Apollo Utilities' +dependencies { + api group: 'io.netty', name: 'netty-all', version: nettyVersion + + implementation group: 'org.apache.commons', name: 'commons-compress', version: commonsCompressVersion + implementation group: 'com.google.guava', name: 'guava', version: guavaVersion + implementation group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: bouncycastleVersion + + test.useJUnitPlatform() + testImplementation group: 'junit', name: 'junit', version: junitVersion + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junitJupiterVersion + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junitJupiterVersion + testImplementation group: 'org.junit.vintage', name: 'junit-vintage-engine', version: junitVintageVersion + testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: junitPlatformVersion +} + task(genRsa, dependsOn: classes, type: JavaExec) { def utilSubproject = project(':util') def utilClasspath = utilSubproject.sourceSets.main.runtimeClasspath diff --git a/util/src/main/org/apollo/util/BufferUtil.java b/util/src/main/java/org/apollo/util/BufferUtil.java similarity index 100% rename from util/src/main/org/apollo/util/BufferUtil.java rename to util/src/main/java/org/apollo/util/BufferUtil.java diff --git a/util/src/main/org/apollo/util/CollectionUtil.java b/util/src/main/java/org/apollo/util/CollectionUtil.java similarity index 100% rename from util/src/main/org/apollo/util/CollectionUtil.java rename to util/src/main/java/org/apollo/util/CollectionUtil.java diff --git a/util/src/main/org/apollo/util/CompressionUtil.java b/util/src/main/java/org/apollo/util/CompressionUtil.java similarity index 100% rename from util/src/main/org/apollo/util/CompressionUtil.java rename to util/src/main/java/org/apollo/util/CompressionUtil.java diff --git a/util/src/main/org/apollo/util/LanguageUtil.java b/util/src/main/java/org/apollo/util/LanguageUtil.java similarity index 100% rename from util/src/main/org/apollo/util/LanguageUtil.java rename to util/src/main/java/org/apollo/util/LanguageUtil.java diff --git a/util/src/main/org/apollo/util/NameUtil.java b/util/src/main/java/org/apollo/util/NameUtil.java similarity index 100% rename from util/src/main/org/apollo/util/NameUtil.java rename to util/src/main/java/org/apollo/util/NameUtil.java diff --git a/util/src/main/org/apollo/util/Point.java b/util/src/main/java/org/apollo/util/Point.java similarity index 100% rename from util/src/main/org/apollo/util/Point.java rename to util/src/main/java/org/apollo/util/Point.java diff --git a/util/src/main/org/apollo/util/StatefulFrameDecoder.java b/util/src/main/java/org/apollo/util/StatefulFrameDecoder.java similarity index 100% rename from util/src/main/org/apollo/util/StatefulFrameDecoder.java rename to util/src/main/java/org/apollo/util/StatefulFrameDecoder.java diff --git a/util/src/main/org/apollo/util/StreamUtil.java b/util/src/main/java/org/apollo/util/StreamUtil.java similarity index 100% rename from util/src/main/org/apollo/util/StreamUtil.java rename to util/src/main/java/org/apollo/util/StreamUtil.java diff --git a/util/src/main/org/apollo/util/TextUtil.java b/util/src/main/java/org/apollo/util/TextUtil.java similarity index 100% rename from util/src/main/org/apollo/util/TextUtil.java rename to util/src/main/java/org/apollo/util/TextUtil.java diff --git a/util/src/main/org/apollo/util/ThreadUtil.java b/util/src/main/java/org/apollo/util/ThreadUtil.java similarity index 100% rename from util/src/main/org/apollo/util/ThreadUtil.java rename to util/src/main/java/org/apollo/util/ThreadUtil.java diff --git a/util/src/main/org/apollo/util/package-info.java b/util/src/main/java/org/apollo/util/package-info.java similarity index 100% rename from util/src/main/org/apollo/util/package-info.java rename to util/src/main/java/org/apollo/util/package-info.java diff --git a/util/src/main/org/apollo/util/security/IsaacRandom.java b/util/src/main/java/org/apollo/util/security/IsaacRandom.java similarity index 100% rename from util/src/main/org/apollo/util/security/IsaacRandom.java rename to util/src/main/java/org/apollo/util/security/IsaacRandom.java diff --git a/util/src/main/org/apollo/util/security/IsaacRandomPair.java b/util/src/main/java/org/apollo/util/security/IsaacRandomPair.java similarity index 100% rename from util/src/main/org/apollo/util/security/IsaacRandomPair.java rename to util/src/main/java/org/apollo/util/security/IsaacRandomPair.java diff --git a/util/src/main/org/apollo/util/security/PlayerCredentials.java b/util/src/main/java/org/apollo/util/security/PlayerCredentials.java similarity index 100% rename from util/src/main/org/apollo/util/security/PlayerCredentials.java rename to util/src/main/java/org/apollo/util/security/PlayerCredentials.java diff --git a/util/src/main/org/apollo/util/security/package-info.java b/util/src/main/java/org/apollo/util/security/package-info.java similarity index 100% rename from util/src/main/org/apollo/util/security/package-info.java rename to util/src/main/java/org/apollo/util/security/package-info.java diff --git a/util/src/main/org/apollo/util/tools/EquipmentConstants.java b/util/src/main/java/org/apollo/util/tools/EquipmentConstants.java similarity index 100% rename from util/src/main/org/apollo/util/tools/EquipmentConstants.java rename to util/src/main/java/org/apollo/util/tools/EquipmentConstants.java diff --git a/util/src/main/org/apollo/util/tools/RsaKeyGenerator.java b/util/src/main/java/org/apollo/util/tools/RsaKeyGenerator.java similarity index 100% rename from util/src/main/org/apollo/util/tools/RsaKeyGenerator.java rename to util/src/main/java/org/apollo/util/tools/RsaKeyGenerator.java diff --git a/util/src/main/org/apollo/util/tools/package-info.java b/util/src/main/java/org/apollo/util/tools/package-info.java similarity index 100% rename from util/src/main/org/apollo/util/tools/package-info.java rename to util/src/main/java/org/apollo/util/tools/package-info.java diff --git a/util/src/main/org/apollo/util/xml/XmlNode.java b/util/src/main/java/org/apollo/util/xml/XmlNode.java similarity index 100% rename from util/src/main/org/apollo/util/xml/XmlNode.java rename to util/src/main/java/org/apollo/util/xml/XmlNode.java diff --git a/util/src/main/org/apollo/util/xml/XmlParser.java b/util/src/main/java/org/apollo/util/xml/XmlParser.java similarity index 100% rename from util/src/main/org/apollo/util/xml/XmlParser.java rename to util/src/main/java/org/apollo/util/xml/XmlParser.java diff --git a/util/src/main/org/apollo/util/xml/package-info.java b/util/src/main/java/org/apollo/util/xml/package-info.java similarity index 100% rename from util/src/main/org/apollo/util/xml/package-info.java rename to util/src/main/java/org/apollo/util/xml/package-info.java diff --git a/util/src/test/org/apollo/util/BufferUtilTests.java b/util/src/test/java/org/apollo/util/BufferUtilTests.java similarity index 100% rename from util/src/test/org/apollo/util/BufferUtilTests.java rename to util/src/test/java/org/apollo/util/BufferUtilTests.java diff --git a/util/src/test/org/apollo/util/CollectionUtilTests.java b/util/src/test/java/org/apollo/util/CollectionUtilTests.java similarity index 100% rename from util/src/test/org/apollo/util/CollectionUtilTests.java rename to util/src/test/java/org/apollo/util/CollectionUtilTests.java diff --git a/util/src/test/org/apollo/util/CompressionUtilTests.java b/util/src/test/java/org/apollo/util/CompressionUtilTests.java similarity index 100% rename from util/src/test/org/apollo/util/CompressionUtilTests.java rename to util/src/test/java/org/apollo/util/CompressionUtilTests.java diff --git a/util/src/test/org/apollo/util/LanguageUtilTests.java b/util/src/test/java/org/apollo/util/LanguageUtilTests.java similarity index 100% rename from util/src/test/org/apollo/util/LanguageUtilTests.java rename to util/src/test/java/org/apollo/util/LanguageUtilTests.java diff --git a/util/src/test/org/apollo/util/NameUtilTests.java b/util/src/test/java/org/apollo/util/NameUtilTests.java similarity index 100% rename from util/src/test/org/apollo/util/NameUtilTests.java rename to util/src/test/java/org/apollo/util/NameUtilTests.java diff --git a/util/src/test/org/apollo/util/StreamUtilTests.java b/util/src/test/java/org/apollo/util/StreamUtilTests.java similarity index 100% rename from util/src/test/org/apollo/util/StreamUtilTests.java rename to util/src/test/java/org/apollo/util/StreamUtilTests.java diff --git a/util/src/test/org/apollo/util/TextUtilTests.java b/util/src/test/java/org/apollo/util/TextUtilTests.java similarity index 100% rename from util/src/test/org/apollo/util/TextUtilTests.java rename to util/src/test/java/org/apollo/util/TextUtilTests.java diff --git a/util/src/test/org/apollo/util/ThreadUtilTests.java b/util/src/test/java/org/apollo/util/ThreadUtilTests.java similarity index 100% rename from util/src/test/org/apollo/util/ThreadUtilTests.java rename to util/src/test/java/org/apollo/util/ThreadUtilTests.java diff --git a/util/src/test/org/apollo/util/xml/XmlParserTests.java b/util/src/test/java/org/apollo/util/xml/XmlParserTests.java similarity index 100% rename from util/src/test/org/apollo/util/xml/XmlParserTests.java rename to util/src/test/java/org/apollo/util/xml/XmlParserTests.java