diff --git a/.gitignore b/.gitignore index 4f11942ef..76d8d1058 100644 --- a/.gitignore +++ b/.gitignore @@ -8,11 +8,14 @@ *~ *.class *.iml - +.project +.classpath +**/bin/ /game/data/rsa.pem /game/data/savedGames /lib/ */target/ */build/ **/build/ -**/out/ \ No newline at end of file +**/out/ +/vendor/ \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2167045f3..6d48a2595 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,47 +1,86 @@ -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" +jobs: +#- job: game_build +# timeoutInMinutes: 15 +# +# pool: +# vmImage: 'ubuntu-latest' +# +# 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.11' +# 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" - - 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 +- job: docs_build + pool: + vmImage: 'windows-2019' + steps: + - task: Gradle@2 + displayName: "Gradle: generate API docs" + inputs: + workingDirectory: '' + gradleWrapperFile: 'gradlew' + gradleOptions: '-Xmx3072m -Dorg.gradle.parallel=true -Dorg.gradle.caching=true -Dsonar.host.url=https://sonarcloud.io' + javaHomeOption: 'JDKVersion' + jdkVersionOption: '1.11' + jdkArchitectureOption: 'x64' + tasks: 'generateApiDocs' + - task: CmdLine@1 + displayName: 'Chocolatey: install DocFX' + inputs: + filename: choco + arguments: 'install docfx -y' + - task: CmdLine@1 + displayName: 'DocFX: generate documentation' + inputs: + filename: docfx + arguments: docs/docfx.json + - task: ArchiveFiles@2 + inputs: + rootFolderOrFile: docs/_site + includeRootFolder: false + archiveFile: '$(Build.ArtifactStagingDirectory)/docs.zip' + replaceExistingArchive: true + - task: PublishBuildArtifacts@1 + inputs: + pathToPublish: '$(Build.ArtifactStagingDirectory)/docs.zip' + artifactName: 'docs.zip' \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..4378419e7 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,9 @@ +############### +# folder # +############### +/**/DROP/ +/**/TEMP/ +/**/packages/ +/**/bin/ +/**/obj/ +_site diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 000000000..b3ca35922 --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,22 @@ +FROM mono:latest + +RUN apt-get update -y && apt-get install -y unzip git + +RUN mkdir -p /docfx +WORKDIR /docfx +RUN curl -L https://github.com/dotnet/docfx/releases/download/v2.44/docfx.zip -O \ + && unzip docfx.zip \ + && chmod +r /docfx + +RUN echo "#!/bin/bash" >> /usr/local/bin/docfx \ + && echo "mono /docfx/docfx.exe \"\$@\"" >> /usr/local/bin/docfx \ + && chmod +x /usr/local/bin/docfx + +RUN mkdir -p /srv/project \ + && useradd -u 1000 docfx \ + && chown docfx:docfx /srv/project + +USER docfx +WORKDIR /srv/project + +ENTRYPOINT ["docfx"] \ No newline at end of file diff --git a/docs/api/.gitignore b/docs/api/.gitignore new file mode 100644 index 000000000..e8079a3be --- /dev/null +++ b/docs/api/.gitignore @@ -0,0 +1,5 @@ +############### +# temp file # +############### +*.yml +.manifest diff --git a/docs/api/core/.gitignore b/docs/api/core/.gitignore new file mode 100644 index 000000000..ad30bfec2 --- /dev/null +++ b/docs/api/core/.gitignore @@ -0,0 +1 @@ +*.yml \ No newline at end of file diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 000000000..6deb9310d --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,2 @@ +# PLACEHOLD +TODO: Add .NET projects to the *src* folder and run `docfx` to generate **REAL** *API Documentation*! diff --git a/docs/api/plugin-script/on_button.md b/docs/api/plugin-script/on_button.md new file mode 100644 index 000000000..bebe66391 --- /dev/null +++ b/docs/api/plugin-script/on_button.md @@ -0,0 +1,5 @@ +--- +uid: on_button +--- + +`on_button` allows registering @"handlers" \ No newline at end of file diff --git a/docs/build.gradle b/docs/build.gradle new file mode 100644 index 000000000..34a4926f6 --- /dev/null +++ b/docs/build.gradle @@ -0,0 +1,25 @@ +configurations { + doclet { + transitive(true) + } +} + +dependencies { + doclet files("vendor/docfx-doclet-1.0-SNAPSHOT-jar-with-dependencies.jar") +} + + +gradle.projectsEvaluated { + task generateApiDocs(type: Javadoc) { + source = project(":game").sourceSets.main.java.sourceDirectories + classpath = project(":game").configurations.runtimeClasspath + project(":game").sourceSets.main.output + dependsOn(project(":game").assemble) + + options.encoding 'UTF-8' + destinationDir = file("build/generated-files") + options.addStringOption("doclet", "com.microsoft.doclet.DocFxDoclet") + options.docletpath = configurations.doclet.toList() + + } + +} \ No newline at end of file diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 000000000..b9a48121c --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,61 @@ +{ + "build": { + "globalMetadata": { + "_appTitle": "Apollo Documentation", + "_gitContribute": { + "repo": "https://github.com/apollo-rsps/apollo", + "branch": "kotlin-experiments" + } + }, + "content": [ + { + "files": [ + "api/**.yml", + "api/**.md" + ] + }, + { + "files": [ + "guide/**.md", + "guide/**/toc.yml", + "toc.yml", + "*.md" + ] + }, + { + "files": [ + "plugin-api/toc.yml", + "plugin-api/spec/**.yml" + ] + } + ], + "resource": [ + { + "files": [ + "images/**" + ] + } + ], + "overwrite": [ + { + "files": [ + "plugin-api/overwrites/**.md" + ], + "exclude": [ + "obj/**", + "_site/**" + ] + } + ], + "dest": "_site", + "globalMetadataFiles": [], + "fileMetadataFiles": [], + "template": [ "default", "templates/material" ], + "postProcessors": [], + "markdownEngineName": "markdig", + "noLangKeyword": false, + "keepFileLink": false, + "cleanupCacheHistory": false, + "disableGitFeatures": false + } +} \ No newline at end of file diff --git a/docs/guide/getting-started/01-setting-up-environment.md b/docs/guide/getting-started/01-setting-up-environment.md new file mode 100644 index 000000000..1af7505a5 --- /dev/null +++ b/docs/guide/getting-started/01-setting-up-environment.md @@ -0,0 +1,7 @@ +# Setting up a development environment + +## Prerequisites + +- **RuneScape game data**: The cache files containing the game data for release 377 of RuneScape. + +- **JDK 8+**: An installation of the JDK is needed to run Gradle build tasks and run the server. diff --git a/docs/guide/getting-started/02-running-apollo.md b/docs/guide/getting-started/02-running-apollo.md new file mode 100644 index 000000000..8254fc198 --- /dev/null +++ b/docs/guide/getting-started/02-running-apollo.md @@ -0,0 +1,84 @@ +This is a short guide to getting a copy of Apollo from our VCS and +running a server able to accept connections from a game client. It +assumes you’re starting fresh and have no local copy of Apollo. + +Requirements +============ + +You should be familiar with running programs on the UNIX shell or +Windows command prompt. There is also a short list of prerequisites +below needed to complete this guide. + +- Git +- Gradle +- Java 8 +- RuneScape r377 game data files [1] + +Getting Apollo +============== + +> **Note** +> +> Apollo is still in a development phase and has no current stable +> release, so to run the server we need to build it from sources first. + +The URL for the Apollo git repository is +. You can clone this using +the `git` command-line client or by using the [GitHub desktop +client](https://help.github.com/desktop/guides/contributing-to-projects/cloning-a-repository-from-github-desktop/). + +``` +> $ git clone +``` + +If using the command line client, the repository will now be under a +folder named *apollo* and is ready to build. When complete open a shell +or Windows command prompt in that directory and move to the next step. + +Building Apollo +=============== + +Apollo uses Gradle build scripts as it’s build system. To build it, +create a command prompt or shell in the Apollo repository folder and +run: + +``` +> $ ./gradlew assemble genRsa +``` + +This will build the core server with the content plugins and run their +respective tests. This process takes around a minute to complete and +when done will generate and output a set of RSA key parameters used by +connecting clients to encrypt their credentials. Save these for later. + +Starting Apollo +=============== + +The last dependency is putting the game data in a location where the +server can find it. By default Apollo looks under `data/fs` in the root +directory for a folder matching the release number. Apollo supports +release 377, so in our case we want the directory structure to look like +this: + +``` +data/fs +└── 377 + ├── jingle1.mid + ├── main_file_cache.dat + ├── main_file_cache.idx0 + ├── main_file_cache.idx1 + ├── main_file_cache.idx2 + ├── main_file_cache.idxN +``` + +Now that everything is in place we can use the Gradle task to boot the +server. + +``` +> $ gradle server:run +``` + +After booting Apollo will have loaded the game data and be ready to accept connections. + +[1] We are unable to provide user-end assets like the game data or +client due to copyright restrictions. diff --git a/docs/guide/introduction/01-what-is-apollo.md b/docs/guide/introduction/01-what-is-apollo.md new file mode 100644 index 000000000..c927c78a5 --- /dev/null +++ b/docs/guide/introduction/01-what-is-apollo.md @@ -0,0 +1,13 @@ +# What is Apollo? + +Apollo is a high-performance, modular RuneScape emulator with a +collection of utilities for managing data files and plugins. Apollo +targets revision 377 of the RuneScape client from late 2006. It aims to +achieve parity with the game server of that time and preserve the +history of the game in doing so. + +## Is Apollo free? + +Apollo is open source and made available under the ISC license. The git +repository for the project is hosted under the [Apollo RSPS +organization](https://github.com/apollo-rsps) on GitHub. diff --git a/docs/guide/plugins/01-what-is-a-plugin.md b/docs/guide/plugins/01-what-is-a-plugin.md new file mode 100644 index 000000000..880674793 --- /dev/null +++ b/docs/guide/plugins/01-what-is-a-plugin.md @@ -0,0 +1,8 @@ +# What is a plugin? + +Plugins in Apollo are a mechanism for extending the server with new functionality without having to change any of the core Java code. +Every plugin is a straight forward Kotlin project with `.plugin.kts` files containing the plugin extension behaviour and optional Kotlin source files containing generic reusable code. + +## Plugin framework architecture + +Under the hood, the Kotlin compiler transofmrs every `.plugin.kts` file into a class file that extends from @"org.apollo.game.plugin.kotlin.KotlinPluginScript", giving it access to the game @"org.apollo.game.model.World" and @"org.apollo.game.message.handler.MessageHandlerChainSet" the plugin framework is initialized with. \ No newline at end of file diff --git a/docs/guide/plugins/02-creating-a-plugin.md b/docs/guide/plugins/02-creating-a-plugin.md new file mode 100644 index 000000000..ee9fb8f29 --- /dev/null +++ b/docs/guide/plugins/02-creating-a-plugin.md @@ -0,0 +1,121 @@ +# Creating a plugin + +Apollo's plugins are written in [Kotlin](http://kotlinlang.org) and are +primarily for content, not core code (if you aren't sure where your code +should go, ask in irc). Note that this tutorial assumes some familiarity +with Kotlin, although good Java knowledge will probably be enough. + +Note: This tutorial is for developing Plugins for the kotlin-experiments +branch prior to the release of the Kotlin plugin system for Apollo. + +## Create a working environment + + + +## Create the plugin metadata + +Apollo's plugins are stored in */game/plugin*, and each plugin has its +own directory. Create one for your plugin - something like 'myplugin'. + +Inside that, create a directory called 'src', then right click it and +'Mark Directory as > Sources Root'. It should turn blue. + +Inside your plugin's directory (*not* in src) you'll want a +*build.gradle* file, containing something like: + +```groovy +plugin { + name = "myplugin" + authors = [ "your name" ] +} +``` + + +Sometimes you need to use code from another plugin, which can be done +like so: `dependencies = [ "util:lookup" ]` + +This imports the `lookup` plugin from `util`. + +## Write the plugin + +Plugins are written in kotlin script (_.kts_), which is then transpiled +into Java (bytecode) at compile time. Kotlin script is designed to be +executed like a scripting language: you do *not* need a main function (a +kotlin script consisting of nothing more than `println("Hello, world!")` +will indeed compile and print "Hello, world!"). + +Apollo uses the _.plugin.kts_ extension to mark files as plugin scripts. + +Add a file named 'myplugin.plugin.kts' inside `src` and add the +following code: + +```kotlin +import org.apollo.game.action.Action +import org.apollo.game.message.impl.InventoryItemMessage +import org.apollo.game.model.Item +import org.apollo.game.model.entity.Entity +import org.apollo.game.model.entity.EntityType +import org.apollo.game.model.entity.GroundItem +import org.apollo.game.model.entity.Player + +class DropItemAction(val player: Player, val slot: Int): Action(delay = 0, immediate = true, player) { + + override fun execute() { + val region = player.world.regionRepository.fromPosition(player.position) + + if (region.getEntities(player.position, EntityType.DYNAMIC_OBJECT, EntityType.STATIC_OBJECT).isEmpty()) { + val amount = player.inventory.reset(slot)?.amount + if (amount == null) { + return + } + + val item = GroundItem.create(player.world, player.position, Item(item, amount), player) + player.world.spawn(item) + } else { + player.sendMessage("You cannot drop this here.") + } + + stop() + } + +} + +val DROP_OPTION_ID = 5 +val INVENTORY_INTERFACE_ID = 3214 + +on { InventoryItemMessage::class } + .where { option == DROP_OPTION_ID && interfaceId == INVENTORY_INTERFACE_ID } + .then { player -> + player.startAction(DropItemAction(player, slot)) + terminate() + } +``` + +Here we have an *action*, and a *listener*, the two core features of +plugins. + +The `on {...}` lambda at the end is the listener, and listens for +specific event types (typically a *Message* subclass) Here we are +listening to `InventoryItemMessage`s, which are called whenever a player +performs an action on an item in an inventory. + +The `where` clause is used to filter out requests that don't match what +we're looking for: here, we only care about messages where the player +selected the fifth option (used for dropping), and when they selected +that option on an item in the inventory (i.e. the interfaceId matches +the inventory interface id). `where` is executed from the context of the +intercepted message, so `option` and `interfaceId` are actually fields +inside `InventoryItemMessage`. + +The 'then' clause is executed if the `where` lambda evaluates to `true`. + +*Actions* are used to schedule player (or NPC)-related code to be +executed in the future (and optionally, periodically). Plugins also have +actions that can be used to suspend/asynchronously execute code. Here +we're creating an `Action` that removes an item from the player's +inventory and spawns a `GroundItem`. Because `Action`s are scheduled, +`execute()` will be called every server pulse (tick), until the `stop()` +function is called. + +Now you can build it by running `gradle build` in the command line, or +in IntelliJ via 'View > Tool Windows > Gradle > Execute Gradle Task' +(type 'build' for the command). + +Voila! diff --git a/docs/guide/plugins/03-testing-a-plugin.md b/docs/guide/plugins/03-testing-a-plugin.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/guide/plugins/04-sharing-plugin-code.md b/docs/guide/plugins/04-sharing-plugin-code.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/guide/toc.yml b/docs/guide/toc.yml new file mode 100644 index 000000000..1fd8906a6 --- /dev/null +++ b/docs/guide/toc.yml @@ -0,0 +1,24 @@ +- name: Introduction + items: + - name: What is Apollo? + href: introduction/01-what-is-apollo.md +- name: Getting Started + items: + - name: Setting up a development environment + href: getting-started/01-setting-up-environment.md + + - name: Running the server + href: getting-started/02-running-apollo.md +- name: Plugins + items: + - name: What is a plugin? + href: plugins/01-what-is-a-plugin.md + + - name: Creating a plugin + href: plugins/02-creating-a-plugin.md + + - name: Testing plugins + href: plugins/03-testing-a-plugin.md + + - name: Sharing code with other plugins + href: plugins/04-sharing-plugin-code.md \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..3ae250636 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,4 @@ +# This is the **HOMEPAGE**. +Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files. +## Quick Start Notes: +1. Add images to the *images* folder if the file is referencing an image. diff --git a/docs/plugin-api/index.md b/docs/plugin-api/index.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/plugin-api/overwrites/ObjectAction.md b/docs/plugin-api/overwrites/ObjectAction.md new file mode 100644 index 000000000..50eebc745 --- /dev/null +++ b/docs/plugin-api/overwrites/ObjectAction.md @@ -0,0 +1,5 @@ +--- +uid: "org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction" +--- + +Some test text \ No newline at end of file diff --git a/docs/plugin-api/spec/org.apollo.game.plugin.action.yml b/docs/plugin-api/spec/org.apollo.game.plugin.action.yml new file mode 100644 index 000000000..dd8251617 --- /dev/null +++ b/docs/plugin-api/spec/org.apollo.game.plugin.action.yml @@ -0,0 +1,21 @@ +### YamlMime:ManagedReference +items: + - uid: "org.apollo.game.plugin.action" + id: "action" + children: + - "org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction" + langs: + - "java" + - "kotlin" + name: "org.apollo.game.plugin.action" + nameWithType: "org.apollo.game.plugin.action" + fullName: "org.apollo.game.plugin.action" + type: "Namespace" + summary: "Contains action types that can be listened on by plugin scripts." + syntax: + content: "package org.apollo.game.plugin.action" +references: + - uid: "org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction" + name: "ObjectAction" + nameWithType: "ObjectAction" + fullName: "org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction" \ No newline at end of file diff --git a/docs/plugin-api/spec/org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction.yml b/docs/plugin-api/spec/org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction.yml new file mode 100644 index 000000000..c758c1262 --- /dev/null +++ b/docs/plugin-api/spec/org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction.yml @@ -0,0 +1,53 @@ +### YamlMime:ManagedReference +items: + - uid: "org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction" + id: "ObjectAction" + parent: "org.apollo.game.plugin.action" + children: + - "org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction.option" + langs: + - "java" + - "kotlin" + name: "ObjectAction" + nameWithType: "ObjectAction" + fullName: "org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction" + type: "Class" + package: "org.apollo.game.plugin.action" + summary: | + An object action is sent when a user interacts with a game object in the workd by using one of + the options presented when the context menu for the object is available. + syntax: + content: "class ObjectAction" + typeParameters: + - id: "T" + - uid: "org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction.option" + id: "mob" + parent: "org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction" + langs: + - "java" + name: "mob" + nameWithType: "ObjectAction.option" + fullName: "org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction.option" + type: "Field" + package: "org.apollo.game.plugin.action" + summary: "The option selected when performing the action." + syntax: + content: "val option: String" + return: + type: "String" +references: + - uid: "int" + spec.java: + - uid: "int" + name: "int" + fullName: "int" + - uid: "boolean" + spec.java: + - uid: "boolean" + name: "boolean" + fullName: "boolean" + - uid: "T" + spec.java: + - uid: "T" + name: "T" + fullName: "T" \ No newline at end of file diff --git a/docs/plugin-api/toc.yml b/docs/plugin-api/toc.yml new file mode 100644 index 000000000..df1000a52 --- /dev/null +++ b/docs/plugin-api/toc.yml @@ -0,0 +1,6 @@ +### YamlMime:TableOfContent +- uid: "org.apollo.game.plugin.action" + name: "Actions" + items: + - uid: "org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction" + name: "ObjectAction" \ No newline at end of file diff --git a/docs/templates/material/partials/head.tmpl.partial b/docs/templates/material/partials/head.tmpl.partial new file mode 100644 index 000000000..eaac2fc2b --- /dev/null +++ b/docs/templates/material/partials/head.tmpl.partial @@ -0,0 +1,21 @@ +{{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + + + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + + {{#_description}}{{/_description}} + + + + + + + + {{#_noindex}}{{/_noindex}} + {{#_enableSearch}}{{/_enableSearch}} + {{#_enableNewTab}}{{/_enableNewTab}} + \ No newline at end of file diff --git a/docs/templates/material/partials/logo.tmpl.partial b/docs/templates/material/partials/logo.tmpl.partial new file mode 100644 index 000000000..59403bc6c --- /dev/null +++ b/docs/templates/material/partials/logo.tmpl.partial @@ -0,0 +1,2 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + diff --git a/docs/templates/material/styles/main.css b/docs/templates/material/styles/main.css new file mode 100644 index 000000000..c64cc7f9a --- /dev/null +++ b/docs/templates/material/styles/main.css @@ -0,0 +1,202 @@ +body { + color: #34393e; + font-family: 'Roboto', sans-serif; + line-height: 1.5; + font-size: 16px; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + word-wrap: break-word +} + +/* HEADINGS */ + +h1 { + font-weight: 600; + font-size: 32px; +} + +h2 { + font-weight: 600; + font-size: 24px; + line-height: 1.8; +} + +h3 { + font-weight: 600; + font-size: 20px; + line-height: 1.8; +} + +h5 { + font-size: 14px; + padding: 10px 0px; +} + +article h1, +article h2, +article h3, +article h4 { + margin-top: 35px; + margin-bottom: 15px; +} + +article h4 { + padding-bottom: 8px; + border-bottom: 2px solid #ddd; +} + +/* NAVBAR */ + +.navbar-brand>img { + color: #fff; +} + +.navbar { + border: none; + /* Both navbars use box-shadow */ + -webkit-box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5); + -moz-box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5); + box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5); +} + +.subnav { + border-top: 1px solid #ddd; + background-color: #fff; +} + +.navbar-inverse { + background-color: #0d47a1; + z-index: 100; +} + +.navbar-inverse .navbar-nav>li>a, +.navbar-inverse .navbar-text { + color: #fff; + background-color: #0d47a1; + border-bottom: 3px solid transparent; + padding-bottom: 12px; +} + +.navbar-inverse .navbar-nav>li>a:focus, +.navbar-inverse .navbar-nav>li>a:hover { + color: #fff; + background-color: #0d47a1; + border-bottom: 3px solid white; +} + +.navbar-inverse .navbar-nav>.active>a, +.navbar-inverse .navbar-nav>.active>a:focus, +.navbar-inverse .navbar-nav>.active>a:hover { + color: #fff; + background-color: #0d47a1; + border-bottom: 3px solid white; +} + +.navbar-form .form-control { + border: none; + border-radius: 20px; +} + +/* SIDEBAR */ + +.toc .level1>li { + font-weight: 400; +} + +.toc .nav>li>a { + color: #34393e; +} + +.sidefilter { + background-color: #fff; + border-left: none; + border-right: none; +} + +.sidefilter { + background-color: #fff; + border-left: none; + border-right: none; +} + +.toc-filter { + padding: 10px; + margin: 0; +} + +.toc-filter>input { + border: 2px solid #ddd; + border-radius: 20px; +} + +.toc-filter>.filter-icon { + display: none; +} + +.sidetoc>.toc { + background-color: #fff; + overflow-x: hidden; +} + +.sidetoc { + background-color: #fff; + border: none; +} + +/* ALERTS */ + +.alert { + padding: 0px 0px 5px 0px; + color: inherit; + background-color: inherit; + border: none; + box-shadow: 0px 2px 2px 0px rgba(100, 100, 100, 0.4); +} + +.alert>p { + margin-bottom: 0; + padding: 5px 10px; +} + +.alert>ul { + margin-bottom: 0; + padding: 5px 40px; +} + +.alert>h5 { + padding: 10px 15px; + margin-top: 0; + text-transform: uppercase; + font-weight: bold; + border-radius: 4px 4px 0 0; +} + +.alert-info>h5 { + color: #1976d2; + border-bottom: 4px solid #1976d2; + background-color: #e3f2fd; +} + +.alert-warning>h5 { + color: #f57f17; + border-bottom: 4px solid #f57f17; + background-color: #fff3e0; +} + +.alert-danger>h5 { + color: #d32f2f; + border-bottom: 4px solid #d32f2f; + background-color: #ffebee; +} + +/* CODE HIGHLIGHT */ +pre { + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + word-break: break-all; + word-wrap: break-word; + background-color: #fffaef; + border-radius: 4px; + box-shadow: 0px 1px 4px 1px rgba(100, 100, 100, 0.4); +} \ No newline at end of file diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 000000000..f796f9f96 --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,10 @@ +- name: Guide + href: guide/ + +- name: Core API Documentation + href: api/ + homepage: api/index.md + +- name: Plugin API Documentation + href: plugin-api/ + homepage: plugin-api/index.md \ No newline at end of file diff --git a/docs/vendor/docfx-doclet-1.0-SNAPSHOT-jar-with-dependencies.jar b/docs/vendor/docfx-doclet-1.0-SNAPSHOT-jar-with-dependencies.jar new file mode 100644 index 000000000..ed39cb2e5 Binary files /dev/null and b/docs/vendor/docfx-doclet-1.0-SNAPSHOT-jar-with-dependencies.jar differ diff --git a/game/build.gradle b/game/build.gradle index 1a451eb99..d72505e28 100644 --- a/game/build.gradle +++ b/game/build.gradle @@ -31,11 +31,6 @@ dependencies { 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") { diff --git a/game/plugin-detekt-rules/bin/main/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider b/game/plugin-detekt-rules/bin/main/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider new file mode 100644 index 000000000..3eb44c259 --- /dev/null +++ b/game/plugin-detekt-rules/bin/main/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/bin/main/org/apollo/game/plugin/detekt/ApolloPluginRuleSetProvider.kt b/game/plugin-detekt-rules/bin/main/org/apollo/game/plugin/detekt/ApolloPluginRuleSetProvider.kt new file mode 100644 index 000000000..c084d54b9 --- /dev/null +++ b/game/plugin-detekt-rules/bin/main/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/bin/main/org/apollo/game/plugin/detekt/rules/DeclarationInScriptRule.kt b/game/plugin-detekt-rules/bin/main/org/apollo/game/plugin/detekt/rules/DeclarationInScriptRule.kt new file mode 100644 index 000000000..055b5fd1d --- /dev/null +++ b/game/plugin-detekt-rules/bin/main/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/bin/test/org/apollo/game/plugin/detekt/rules/DeclarationInScriptRuleTest.kt b/game/plugin-detekt-rules/bin/test/org/apollo/game/plugin/detekt/rules/DeclarationInScriptRuleTest.kt new file mode 100644 index 000000000..f6ca6daeb --- /dev/null +++ b/game/plugin-detekt-rules/bin/test/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/bin/test/testData/example.kts b/game/plugin-detekt-rules/bin/test/testData/example.kts new file mode 100644 index 000000000..0fb1729b7 --- /dev/null +++ b/game/plugin-detekt-rules/bin/test/testData/example.kts @@ -0,0 +1,3 @@ +class ExampleDeclaration { + +} \ No newline at end of file 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 index f0873b998..627730ccb 100644 --- a/game/plugin/api/src/org/apollo/game/plugin/api/Definitions.kt +++ b/game/plugin/api/src/org/apollo/game/plugin/api/Definitions.kt @@ -4,6 +4,8 @@ import java.lang.IllegalArgumentException import org.apollo.cache.def.ItemDefinition import org.apollo.cache.def.NpcDefinition import org.apollo.cache.def.ObjectDefinition +import org.intellij.lang.annotations.Language +import java.util.regex.Pattern /** * Provides plugins with access to item, npc, and object definitions @@ -76,14 +78,18 @@ object Definitions { return findEntity(NpcDefinition::getDefinitions, NpcDefinition::getName, name) } + fun npcs(@Language("RegExp") pattern: String): Sequence { + return findEntities(NpcDefinition::getDefinitions, NpcDefinition::getName, pattern) + } + /** * The [Regex] used to match 'names' that have an id attached to the end. */ private val ID_REGEX = Regex(".+_[0-9]+$") - private fun findEntity( + private inline fun findEntity( definitionsProvider: () -> Array, - nameSupplier: T.() -> String, + crossinline nameSupplier: T.() -> String, name: String ): T? { val definitions = definitionsProvider() @@ -98,7 +104,22 @@ object Definitions { return definitions[id] } - val normalizedName = name.replace('_', ' ') - return definitions.firstOrNull { it.nameSupplier().equals(normalizedName, ignoreCase = true) } + return findEntities(definitionsProvider, nameSupplier, name).firstOrNull() + } + + private inline fun findEntities( + definitionsProvider: () -> Array, + crossinline nameSupplier: T.() -> String, + regexp: String + ) : Sequence { + val definitions = definitionsProvider().asSequence() + val pattern = Pattern.compile(regexp, Pattern.CASE_INSENSITIVE) + + return definitions.filter { + val name = it.nameSupplier() + val matcher = pattern.matcher(name) + + matcher.matches() + } } } \ No newline at end of file diff --git a/game/plugin/bank/build.gradle b/game/plugin/bank/build.gradle index 3a9351392..81df48840 100644 --- a/game/plugin/bank/build.gradle +++ b/game/plugin/bank/build.gradle @@ -5,6 +5,7 @@ dependencies { implementation project(':cache') implementation project(':net') implementation project(':util') + implementation project(':game:plugin:api') testImplementation project(':game:plugin-testing') } diff --git a/game/plugin/bank/src/bank.plugin.kts b/game/plugin/bank/src/bank.plugin.kts index 0aef5ac18..1bcca95b3 100644 --- a/game/plugin/bank/src/bank.plugin.kts +++ b/game/plugin/bank/src/bank.plugin.kts @@ -1,38 +1,31 @@ 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.game.plugin.api.Definitions.npcs +import org.apollo.game.plugin.kotlin.message.action.npc.NpcAction +import org.apollo.game.plugin.kotlin.message.action.obj.InteractiveObject +import org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction 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) } +enum class QuickBankObject(override val id: Int) : InteractiveObject { + BankBooth(2213) +} -/** - * 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] +on(ObjectAction, "Open Bank", objects = QuickBankObject.values()) { + BankAction.start(player, target.position) +} - if (npc.id in BANKER_NPCS) { - BankAction.start(this, it, npc.position) - } - } +val bankerNpcs = npcs("(Gnome )?Banker") /** - * The ids of all banker [Npcs][Npc]. + * Hook into the [NpcActionMessage] and listen for when a banker's second action ("Open Bank") is selected. */ -val BANKER_NPCS = setOf(166, 494, 495, 496, 497, 498, 499, 1036, 1360, 1702, 2163, 2164, 2354, 2355, 2568, 2569, 2570) +on(NpcAction, "Open Bank", bankerNpcs) { + BankAction.start(player, target.position) +} /** * A [DistancedAction] that opens a [Player]'s bank when they get close enough to a booth or banker. @@ -51,9 +44,8 @@ class BankAction(player: Player, position: Position) : DistancedAction(0 /** * Starts a [BankAction] for the specified [Player], terminating the [Message] that triggered. */ - fun start(message: Message, player: Player, position: Position) { + fun start(player: Player, position: Position) { player.startAction(BankAction(player, position)) - message.terminate() } } diff --git a/game/plugin/build.gradle b/game/plugin/build.gradle index 24ba2cc84..e444bb236 100644 --- a/game/plugin/build.gradle +++ b/game/plugin/build.gradle @@ -1,33 +1,32 @@ -gradle.projectsEvaluated { - configure(subprojects.findAll { it.buildFile.exists() }) { subproj -> - apply from: "$rootDir/gradle/kotlin.gradle" +configure(subprojects.findAll { it.buildFile.exists() }) { subproj -> + apply plugin: "kotlin" - sourceSets { - main { - kotlin { - srcDirs += "src" - } - } - - test { - kotlin { - srcDirs += "test" - } + sourceSets { + main { + kotlin { + srcDirs += "src" } } test { - useJUnitPlatform() - } - - tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - kotlinOptions { - jvmTarget = "1.8" + kotlin { + srcDirs += "test" } } + } - dependencies { - implementation group: 'com.google.guava', name: 'guava', version: guavaVersion + tasks.withType(Test) { + useJUnitPlatform() + } + + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions { + jvmTarget = "1.8" } } -} \ No newline at end of file + + dependencies { + kotlinScriptDef(":game") + implementation group: 'com.google.guava', name: 'guava', version: guavaVersion + } +} diff --git a/game/plugin/run/src/run.plugin.kts b/game/plugin/run/src/run.plugin.kts index fa65f0495..f52aaba04 100644 --- a/game/plugin/run/src/run.plugin.kts +++ b/game/plugin/run/src/run.plugin.kts @@ -1,10 +1,9 @@ import org.apollo.game.message.impl.ButtonMessage +import org.apollo.game.plugin.kotlin.message.ButtonClick +import org.apollo.game.plugin.kotlin.message.on 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 +on(ButtonClick, WALK_BUTTON_ID) { player.toggleRunning() } +on(ButtonClick, RUN_BUTTON_ID) { player.toggleRunning() } 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 index 37bdfdfca..93cba3fe8 100644 --- a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginScript.kt +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginScript.kt @@ -20,16 +20,9 @@ abstract class KotlinPluginScript(var world: World, val context: PluginContext) private var stopListener: (World) -> Unit = { _ -> } - fun on( - listenable: Listenable, - callback: C.() -> Unit - ) { - registerListener(listenable, null, callback) - } - internal fun registerListener( listenable: Listenable, - predicateContext: I?, + predicateContext: I, callback: C.() -> Unit ) { // Smart-casting/type-inference is completely broken in this function in intelliJ, so assign to otherwise 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 index 9a89fa641..be4838aec 100644 --- a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/Listenable.kt +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/Listenable.kt @@ -2,6 +2,7 @@ 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.model.event.Event import org.apollo.game.model.event.EventListener import org.apollo.net.message.Message @@ -16,12 +17,20 @@ sealed class Listenable { abstract class EventListenable : Listenable() { - abstract fun createHandler(world: World, predicateContext: P?, callback: C.() -> Unit): EventListener + 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 + protected fun handler(world: World, callback: (Player, T) -> Unit): MessageHandler { + return object : MessageHandler(world) { + override fun handle(player: Player, message: T) { + callback(player, message) + } + } + } + + abstract fun createHandler(world: World, predicateContext: P, callback: C.() -> Unit): MessageHandler } 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 index 1b62dbf1f..3a22b30c0 100644 --- 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 @@ -31,16 +31,16 @@ 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?, + predicateContext: ButtonPredicateContext, callback: ButtonClick.() -> Unit ): MessageHandler { return object : MessageHandler(world) { override fun handle(player: Player, message: ButtonMessage) { - if (predicateContext == null || predicateContext.button == message.widgetId) { + if (predicateContext.button == message.widgetId) { val context = ButtonClick(player, message.widgetId) context.callback() } diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/actions.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/actions.kt new file mode 100644 index 000000000..4550dec96 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/actions.kt @@ -0,0 +1,66 @@ +import org.apollo.cache.def.NpcDefinition +import org.apollo.game.message.impl.NpcActionMessage +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.game.plugin.kotlin.KotlinPluginScript +import org.apollo.game.plugin.kotlin.message.action.npc.NpcAction +import org.apollo.game.plugin.kotlin.message.action.npc.NpcActionPredicateContext +import org.apollo.game.plugin.kotlin.message.action.obj.InteractiveObject +import org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction +import org.apollo.game.plugin.kotlin.message.action.obj.ObjectActionPredicateContext + +/** + * 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.on( + listenable: ObjectAction.Companion, + option: String, + interactives: List, + callback: ObjectAction.() -> Unit +) { + val cb = callback as ObjectAction<*>.() -> Unit + registerListener(listenable, ObjectActionPredicateContext(option, interactives.toList()), cb) +} + + +fun KotlinPluginScript.on( + listenable: ObjectAction.Companion, + option: String, + objects: Array, + callback: ObjectAction.() -> Unit +) { + on(listenable, option, objects.toList(), callback) +} + +/** + * Registers a listener for [NpcActionMessage]s that occur on any of the given [NpcDefinition]s using the + * given [option] (case-insensitive). + * + * ``` + * on(NpcAction, option = "Talk-to", npcs = npcs("(Gnome )?Banker") { + * ... + * } + * ``` + */ +fun KotlinPluginScript.on( + listenable: NpcAction.Companion, + option: String, + npcs: Sequence, + callback: NpcAction.() -> Unit +) { + registerListener(listenable, NpcActionPredicateContext(option, npcs.toList()), callback) +} \ No newline at end of file diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/npc/NpcAction.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/npc/NpcAction.kt new file mode 100644 index 000000000..aac20745d --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/npc/NpcAction.kt @@ -0,0 +1,37 @@ +package org.apollo.game.plugin.kotlin.message.action.npc + +import org.apollo.cache.def.NpcDefinition +import org.apollo.game.message.handler.MessageHandler +import org.apollo.game.message.impl.NpcActionMessage +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.game.model.World +import org.apollo.game.model.entity.Npc +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.message.action.ActionContext +import org.apollo.game.plugin.kotlin.message.action.obj.InteractiveObject +import org.apollo.game.plugin.kotlin.message.action.obj.ObjectAction +import org.apollo.game.plugin.kotlin.message.action.obj.ObjectActionPredicateContext + +class NpcAction(override val option: String, override val player: Player, val target: Npc) : ActionContext { + + companion object : MessageListenable() { + override val type = NpcActionMessage::class + + override fun createHandler(world: World, predicateContext: NpcActionPredicateContext, callback: NpcAction.() -> Unit): MessageHandler { + val ids = predicateContext.npcDefinitions.map(NpcDefinition::getId).toSet() + + return handler(world) { player, message -> + val npc = world.npcRepository[message.index] + val option = npc.definition.interactions[message.option] + val npcMatched = ids.isEmpty() || npc.id in ids + + if (npcMatched && predicateContext.option.equals(option, ignoreCase = true)) { + val context = NpcAction(option, player, npc) + context.callback() + } + } + } + } +} diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/npc/NpcActionPredicateContext.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/npc/NpcActionPredicateContext.kt new file mode 100644 index 000000000..68a8cad46 --- /dev/null +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/message/action/npc/NpcActionPredicateContext.kt @@ -0,0 +1,10 @@ +package org.apollo.game.plugin.kotlin.message.action.npc + +import org.apollo.cache.def.NpcDefinition +import org.apollo.game.plugin.kotlin.message.action.ActionPredicateContext +import org.apollo.game.plugin.kotlin.message.action.obj.InteractiveObject + +data class NpcActionPredicateContext( + override val option: String, + val npcDefinitions: List +) : ActionPredicateContext(option) \ 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 index 85e2c6957..fdb9ae5e1 100644 --- 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 @@ -9,6 +9,4 @@ 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 index 2bb0b7902..1612b7ff4 100644 --- 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 @@ -31,30 +31,6 @@ fun KotlinPluginScript.on( 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]. */ @@ -71,35 +47,31 @@ class ObjectAction( // TODO split into two classes, one override fun createHandler( world: World, - predicateContext: ObjectActionPredicateContext<*>?, + 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 + val objectMap = predicateContext.objects.associateBy(InteractiveObject::id) + + return handler(world) { player, message -> + 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@handler // Could happen if object was despawned this tick, before calling this handle function + + val context = when { // Evaluation-order matters here. + !predicateContext.option.equals(option, ignoreCase = true) -> return@handler + objectMap.isEmpty() -> ObjectAction(player, option, target, null) + objectMap.containsKey(message.id) -> { + ObjectAction(player, option, target, objectMap[message.id]) } - - context.callback() + else -> return@handler } + context.callback() } } diff --git a/gradle/config/github_deploy_key.enc b/gradle/config/github_deploy_key.enc new file mode 100644 index 000000000..f129ee8cd Binary files /dev/null and b/gradle/config/github_deploy_key.enc differ diff --git a/gradle/kotlin.gradle b/gradle/kotlin.gradle index 04894ed9f..7acbdf85b 100644 --- a/gradle/kotlin.gradle +++ b/gradle/kotlin.gradle @@ -1,3 +1,4 @@ + kotlin { - + } diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 000000000..c05181715 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,15 @@ +[build] + # Directory to change to before starting a build. + # This is where we will look for package.json/.nvmrc/etc. + + # Directory (relative to root of your repo) that contains the deploy-ready + # HTML files and assets generated by the build. If a base directory has + # been specified, include it in the publish directory path. + publish = "doc/_site" + + # Default build command. + command = ''' + ./gradlew generateApiDocs && \ + docker build -t docfx -f docs/Dockerfile && + docker run -v `pwd`:/srv/project docfx:latest -- docs/ + ''' diff --git a/settings.gradle b/settings.gradle index b9ba28b0f..c2efa6a8b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,7 @@ import java.nio.file.Path rootProject.name = 'org.apollo' include ':cache' +include ':docs' include ':game' include ':game:plugin' include ':game:plugin-detekt-rules' diff --git a/tools/dokka2yaml/build.gradle b/tools/dokka2yaml/build.gradle new file mode 100644 index 000000000..a445102d4 --- /dev/null +++ b/tools/dokka2yaml/build.gradle @@ -0,0 +1,13 @@ +apply plugin: "java" +apply plugin: 'kotlin' +apply from: "$rootDir/gradle/kotlin.gradle" + +repositories { + jcenter() +} + +dependencies { + compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8' + + compile 'org.jetbrains.dokka:dokka-fatjar:0.9.18' +} \ No newline at end of file diff --git a/tools/dokka2yaml/src/main/kotlin/org/apollo/tools/dokka2yaml/YamlFileFormatDescriptor.kt b/tools/dokka2yaml/src/main/kotlin/org/apollo/tools/dokka2yaml/YamlFileFormatDescriptor.kt new file mode 100644 index 000000000..966582cd0 --- /dev/null +++ b/tools/dokka2yaml/src/main/kotlin/org/apollo/tools/dokka2yaml/YamlFileFormatDescriptor.kt @@ -0,0 +1,19 @@ +package org.apollo.tools.dokka2yaml + +import com.google.inject.Binder +import org.jetbrains.dokka.FormatService +import org.jetbrains.dokka.Formats.FormatDescriptor +import org.jetbrains.dokka.Formats.KotlinFormatDescriptorBase +import kotlin.reflect.KClass + +class YamlFileFormatDescriptor : KotlinFormatDescriptorBase() { + override val formatServiceClass = YamlFileGenerator::class + override fun configureAnalysis(binder: Binder) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun configureOutput(binder: Binder) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + +} \ No newline at end of file diff --git a/tools/dokka2yaml/src/main/kotlin/org/apollo/tools/dokka2yaml/YamlFileGenerator.kt b/tools/dokka2yaml/src/main/kotlin/org/apollo/tools/dokka2yaml/YamlFileGenerator.kt new file mode 100644 index 000000000..1c24ad212 --- /dev/null +++ b/tools/dokka2yaml/src/main/kotlin/org/apollo/tools/dokka2yaml/YamlFileGenerator.kt @@ -0,0 +1,24 @@ +package org.apollo.tools.dokka2yaml + +import org.jetbrains.dokka.DocumentationNode +import org.jetbrains.dokka.FormatService +import org.jetbrains.dokka.FormattedOutputBuilder +import org.jetbrains.dokka.Location + +class YamlFileGenerator(override val extension: String = ".yaml") : FormatService { + override fun createOutputBuilder(to: StringBuilder, location: Location): FormattedOutputBuilder { + return YamlFileOutputBuilder() + } +} + +class YamlFileOutputBuilder : FormattedOutputBuilder { + override fun appendNodes(nodes: Iterable) { + for (node in nodes) { + appendNode(node) + } + } + + fun appendNode(node: DocumentationNode) { + + } +} \ No newline at end of file diff --git a/tools/dokka2yaml/src/main/resources/dokka2yaml.properties b/tools/dokka2yaml/src/main/resources/dokka2yaml.properties new file mode 100644 index 000000000..798edc9f0 --- /dev/null +++ b/tools/dokka2yaml/src/main/resources/dokka2yaml.properties @@ -0,0 +1,2 @@ +class=org.apollo.tools.dokka2yaml.YamlFileFormatDescriptor +description=Turns KDoc and Javadoc into the code2yaml format for DocFX. \ No newline at end of file