Added MapArea interface for writing content, simplified the ClassScanner, added better javadoc

This commit is contained in:
ceikry 2022-04-04 00:21:08 -05:00
parent ae5fc443c7
commit 55126ce8a6
13 changed files with 151 additions and 132 deletions

View file

@ -714,6 +714,15 @@ fun findLocalNPCs(entity: Entity, ids: IntArray, distance: Int): List<NPC>{
return RegionManager.getLocalNpcs(entity, distance).filter { it.id in ids }.toList()
}
/**
* @param regionId the ID of the region
* @return a [ZoneBorders] encapsulating the entire region indicated by the provided regionId
*/
fun getRegionBorders(regionId: Int) : ZoneBorders
{
return ZoneBorders.forRegion(regionId)
}
/**
* Gets the value of an attribute key from the Entity's attributes store
* @param entity the entity to get the attribute from

View file

@ -0,0 +1,7 @@
package api
interface ContentInterface {
/**
* A dummy/shell interface that allows us to more easily load content via class scanning
*/
}

View file

@ -2,7 +2,12 @@ package api
import core.game.node.entity.player.Player
interface LoginListener {
/**
* An interface for writing content that allows the class to execute some code when a player logs in.
*
* Login listeners are called *before* [PersistPlayer] data is parsed.
*/
interface LoginListener : ContentInterface {
/**
* NOTE: This should NOT reference any non-static class-local variables.
* If you need to access a player's specific instance, use an attribute.

View file

@ -2,7 +2,12 @@ package api
import core.game.node.entity.player.Player
interface LogoutListener {
/**
* An interface for writing content that allows code to be executed by the class when a player logs out.
*
* Logout listeners are called *before* [PersistPlayer] data is saved.
*/
interface LogoutListener : ContentInterface {
/**
* NOTE: This should NOT reference any non-static class-local variables.
* If you need to access a player's specific instance, use an attribute.

View file

@ -0,0 +1,18 @@
package api
import core.game.node.entity.Entity
import core.game.world.map.Location
import core.game.world.map.zone.ZoneBorders
import core.game.world.map.zone.ZoneRestriction
/**
* Interface that allows a class to define a map area.
* Optionally-overridable methods include [getRestrictions], [areaEnter], [areaLeave] and [entityStep]
*/
interface MapArea {
fun defineAreaBorders() : Array<ZoneBorders>
fun getRestrictions() : Array<ZoneRestriction> {return arrayOf()}
fun areaEnter(entity: Entity) {}
fun areaLeave(entity: Entity) {}
fun entityStep(entity: Entity, location: Location, lastLocation: Location) {}
}

View file

@ -3,7 +3,14 @@ package api
import core.game.node.entity.player.Player
import org.json.simple.JSONObject
interface PersistPlayer {
/**
* An interface for writing content that allows data to be saved and loaded from player saves.
*
* Parsing is called *after* any [LoginListener] is executed.
*
* Saving is called *after* any [LogoutListener] is executed.
*/
interface PersistPlayer : ContentInterface {
/**
* NOTE: This should NOT reference nonstatic class-local variables.
* You need to fetch a player's specific instance of the data and save from that.

View file

@ -1,6 +0,0 @@
package api
interface PersistWorld {
fun saveWorld()
fun parseWorld()
}

View file

@ -2,7 +2,10 @@ package api
import rs09.game.system.SystemLogger
interface ShutdownListener {
/**
* An interface for writing content that allows the class to execute code as the server is shutting down
*/
interface ShutdownListener : ContentInterface {
/**
* NOTE: This should NOT reference nonstatic class-local variables.
*/

View file

@ -2,7 +2,10 @@ package api
import rs09.game.system.SystemLogger
interface StartupListener {
/**
* An interface for writing content that allows the class to execute code when the server is started.
*/
interface StartupListener : ContentInterface {
/**
* NOTE: This should NOT reference nonstatic class-local variables.
*/

View file

@ -1,6 +1,9 @@
package api
interface TickListener {
/**
* An interface for writing content that allows the class to be updated each tick.
*/
interface TickListener : ContentInterface {
/**
* NOTE: This should NOT reference nonstatic class-local variables.
* TickListeners are generally for NON-player, WORLD tick events.

View file

@ -6,75 +6,52 @@ import core.game.node.entity.Entity
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills
import core.game.system.task.Pulse
import core.game.world.map.zone.MapZone
import core.game.world.map.zone.ZoneBorders
import core.game.world.map.zone.ZoneBuilder
import core.plugin.Initializable
import core.plugin.Plugin
import org.rs09.consts.NPCs
import rs09.game.content.dialogue.DialogueFile
import rs09.tools.secondsToTicks
import java.util.concurrent.TimeUnit
@Initializable
class FarmingPatchZone : MapZone("farming patch", true), Plugin<Any> {
class FarmingPatchZone : MapArea, TickListener {
private val playersInZone = hashMapOf<Player,Int>()
val playersInZone = hashMapOf<Player,Int>()
override fun configure() {
registerRegion(12083)
registerRegion(10548)
register(ZoneBorders(3594,3521,3608,3532))
submitWorldPulse(zonePulse)
override fun defineAreaBorders(): Array<ZoneBorders> {
return arrayOf(
getRegionBorders(12083),
getRegionBorders(10548),
ZoneBorders(3594,3521,3608,3532)
)
}
override fun newInstance(arg: Any?): Plugin<Any> {
ZoneBuilder.configure(this)
return this
}
override fun fireEvent(identifier: String?, vararg args: Any?): Any {
return Unit
}
override fun enter(e: Entity?): Boolean {
if(e is Player && playersInZone[e] == null && getStatLevel(e, Skills.FARMING) <= 15) {
playersInZone[e] = 0
override fun areaEnter(entity: Entity) {
if(entity is Player && playersInZone[entity] == null && getStatLevel(entity, Skills.FARMING) <= 15) {
playersInZone[entity] = 0
}
return super.enter(e)
}
override fun leave(e: Entity?, logout: Boolean): Boolean {
if(e is Player){
playersInZone.remove(e)
}
return super.enter(e)
override fun areaLeave(entity: Entity) {
if(entity is Player)
playersInZone.remove(entity)
}
val zonePulse = object : Pulse(){
override fun pulse(): Boolean {
playersInZone.toList().forEach { (player, ticks) ->
if(ticks == secondsToTicks(TimeUnit.MINUTES.toSeconds(5).toInt())){
val npc = NPC(NPCs.GITHAN_7122)
npc.location = player.location
npc.init()
npc.moveStep()
npc.face(player)
openDialogue(player, SpiritDialogue(true), npc)
} else if (ticks == secondsToTicks(TimeUnit.MINUTES.toSeconds(10).toInt())){
val npc = NPC(NPCs.GITHAN_7122)
npc.location = player.location
npc.init()
npc.moveStep()
npc.face(player)
openDialogue(player, SpiritDialogue(false), npc)
playersInZone.remove(player)
}
playersInZone[player] = ticks + 1
override fun tick() {
playersInZone.toList().forEach { (player, ticks) ->
if(ticks == 500){
val npc = NPC(NPCs.GITHAN_7122)
npc.location = player.location
npc.init()
npc.moveStep()
npc.face(player)
openDialogue(player, SpiritDialogue(true), npc)
} else if (ticks == 1000){
val npc = NPC(NPCs.GITHAN_7122)
npc.location = player.location
npc.init()
npc.moveStep()
npc.face(player)
openDialogue(player, SpiritDialogue(false), npc)
playersInZone.remove(player)
}
return false
playersInZone[player] = ticks + 1
}
}
@ -94,5 +71,4 @@ class FarmingPatchZone : MapZone("farming patch", true), Plugin<Any> {
poofClear(npc ?: return)
}
}
}

View file

@ -1,5 +1,6 @@
package rs09.game.node.entity.player.diary.seers
import api.MapArea
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.diary.DiaryType
@ -10,32 +11,16 @@ import core.game.world.map.zone.ZoneBuilder
import core.plugin.Initializable
import core.plugin.Plugin
@Initializable
class SeersCourthouseZone : MapZone("seers-courthouse", true), Plugin<Any?>{
override fun newInstance(arg: Any?): SeersCourthouseZone {
ZoneBuilder.configure(this)
return this
class SeersCourthouseZone : MapArea {
override fun defineAreaBorders(): Array<ZoneBorders> {
return arrayOf(ZoneBorders(2735,3471,2736,3471))
}
override fun configure() {
super.register(ZoneBorders(2735,3471,2736,3471))
}
override fun enter(e: Entity?): Boolean {
if(e is Player && !e.isArtificial){
if(!e.achievementDiaryManager.hasCompletedTask(DiaryType.SEERS_VILLAGE,2,3) && e.prayer.active.contains(PrayerType.PIETY)){
e.achievementDiaryManager.finishTask(e,DiaryType.SEERS_VILLAGE,2,3)
override fun areaEnter(entity: Entity) {
if(entity is Player && !entity.isArtificial){
if(!entity.achievementDiaryManager.hasCompletedTask(DiaryType.SEERS_VILLAGE,2,3) && entity.prayer.active.contains(PrayerType.PIETY)){
entity.achievementDiaryManager.finishTask(entity,DiaryType.SEERS_VILLAGE,2,3)
}
}
return super.enter(e)
}
override fun fireEvent(identifier: String?, vararg args: Any?): Any {
return Unit
}
override fun leave(e: Entity?, logout: Boolean): Boolean {
return super.leave(e, logout)
}
}

View file

@ -4,9 +4,14 @@ import api.*
import core.game.content.activity.ActivityManager
import core.game.content.activity.ActivityPlugin
import core.game.content.dialogue.DialoguePlugin
import core.game.node.entity.Entity
import core.game.node.entity.player.info.login.LoginConfiguration
import core.game.node.entity.player.link.quest.Quest
import core.game.node.entity.player.link.quest.QuestRepository
import core.game.world.map.Location
import core.game.world.map.zone.MapZone
import core.game.world.map.zone.ZoneBuilder
import core.game.world.map.zone.ZoneMonitor
import core.plugin.Plugin
import core.plugin.PluginManifest
import core.plugin.PluginType
@ -15,6 +20,7 @@ import io.github.classgraph.ClassInfo
import rs09.game.ai.general.scriptrepository.PlayerScripts
import rs09.game.interaction.InteractionListener
import rs09.game.interaction.InterfaceListener
import rs09.game.interaction.Listener
import rs09.game.node.entity.player.info.login.PlayerSaveParser
import rs09.game.node.entity.player.info.login.PlayerSaver
import rs09.game.node.entity.skill.magic.SpellListener
@ -68,14 +74,47 @@ object ClassScanner {
fun load() {
val result = ClassGraph().enableClassInfo().enableAnnotationInfo().scan()
result.getClassesWithAnnotation("core.plugin.Initializable").forEach(Consumer { p: ClassInfo ->
val clazz = p.loadClass().newInstance()
if(clazz is Plugin<*>) definePlugin(clazz)
})
result.getClassesImplementing("api.ContentInterface").filter { !it.isAbstract }.forEach {
try {
definePlugin(p.loadClass().newInstance() as Plugin<Object>)
} catch (e: InstantiationException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
val clazz = it.loadClass().newInstance()
if(clazz is LoginListener) GameWorld.loginListeners.add(clazz)
if(clazz is LogoutListener) GameWorld.logoutListeners.add(clazz)
if(clazz is TickListener) GameWorld.tickListeners.add(clazz)
if(clazz is StartupListener) GameWorld.startupListeners.add(clazz)
if(clazz is ShutdownListener) GameWorld.shutdownListeners.add(clazz)
if(clazz is PersistPlayer) {
PlayerSaver.contentHooks.add(clazz)
PlayerSaveParser.contentHooks.add(clazz)
}
if(clazz is MapArea)
{
val zone = object : MapZone(clazz.javaClass.simpleName + "MapArea", true, *clazz.getRestrictions()){
override fun enter(e: Entity?): Boolean {
clazz.areaEnter(e ?: return super.enter(null))
return super.enter(e)
}
override fun leave(e: Entity?, logout: Boolean): Boolean {
clazz.areaLeave(e ?: return super.leave(null, logout))
return super.leave(e, logout)
}
override fun move(e: Entity?, from: Location?, to: Location?): Boolean {
if(e != null && from != null && to != null) clazz.entityStep(e, to, from)
return super.move(e, from, to)
}
}
for(border in clazz.defineAreaBorders()) zone.register(border)
ZoneBuilder.configure(zone)
}
} catch (e: Exception) {
SystemLogger.logErr("Error loading content: ${it.simpleName}, ${e.localizedMessage}")
e.printStackTrace()
}
})
}
result.getClassesWithAnnotation("rs09.game.ai.general.scriptrepository.PlayerCompatible").forEach { res ->
val description = res.getAnnotationInfo("rs09.game.ai.general.scriptrepository.ScriptDescription").parameterValues[0].value as Array<String>
val identifier = res.getAnnotationInfo("rs09.game.ai.general.scriptrepository.ScriptIdentifier").parameterValues[0].value.toString()
@ -83,41 +122,6 @@ object ClassScanner {
PlayerScripts.identifierMap[identifier] =
PlayerScripts.PlayerScript(identifier, description, name, res.loadClass())
}
result.getClassesImplementing("api.StartupListener").filter { !it.isAbstract }.forEach {
try {
val clazz = it.loadClass().newInstance() as StartupListener
GameWorld.startupListeners.add(clazz)
} catch (e: Exception)
{
SystemLogger.logErr("Error loading startup listener: ${it.simpleName}, ${e.localizedMessage}")
e.printStackTrace()
}
}
result.getClassesImplementing("api.ShutdownListener").filter { !it.isAbstract }.forEach {
val clazz = it.loadClass().newInstance() as ShutdownListener
GameWorld.shutdownListeners.add(clazz)
}
result.getClassesImplementing("api.LoginListener").filter { !it.isAbstract }.forEach {
val clazz = it.loadClass().newInstance() as LoginListener
GameWorld.loginListeners.add(clazz)
}
result.getClassesImplementing("api.LogoutListener").filter { !it.isAbstract }.forEach {
val clazz = it.loadClass().newInstance() as LogoutListener
GameWorld.logoutListeners.add(clazz)
}
result.getClassesImplementing("api.TickListener").filter { !it.isAbstract }.forEach {
val clazz = it.loadClass().newInstance() as TickListener
GameWorld.tickListeners.add(clazz)
}
result.getClassesImplementing("api.PersistPlayer").filter { !it.isAbstract }.forEach {
val clazz = it.loadClass().newInstance() as PersistPlayer
PlayerSaver.contentHooks.add(clazz)
PlayerSaveParser.contentHooks.add(clazz)
}
result.getClassesImplementing("api.PersistWorld").filter { !it.isAbstract }.forEach {
val clazz = it.loadClass().newInstance() as PersistPlayer
PlayerSaver.contentHooks.add(clazz)
}
}
/**