diff --git a/Server/src/main/kotlin/api/ContentAPI.kt b/Server/src/main/kotlin/api/ContentAPI.kt index fbb2d9e66..8b668f8b6 100644 --- a/Server/src/main/kotlin/api/ContentAPI.kt +++ b/Server/src/main/kotlin/api/ContentAPI.kt @@ -714,6 +714,15 @@ fun findLocalNPCs(entity: Entity, ids: IntArray, distance: Int): List{ 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 diff --git a/Server/src/main/kotlin/api/ContentInterface.kt b/Server/src/main/kotlin/api/ContentInterface.kt new file mode 100644 index 000000000..c8bdc0ca9 --- /dev/null +++ b/Server/src/main/kotlin/api/ContentInterface.kt @@ -0,0 +1,7 @@ +package api + +interface ContentInterface { + /** + * A dummy/shell interface that allows us to more easily load content via class scanning + */ +} \ No newline at end of file diff --git a/Server/src/main/kotlin/api/LoginListener.kt b/Server/src/main/kotlin/api/LoginListener.kt index 96a900d18..f967353be 100644 --- a/Server/src/main/kotlin/api/LoginListener.kt +++ b/Server/src/main/kotlin/api/LoginListener.kt @@ -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. diff --git a/Server/src/main/kotlin/api/LogoutListener.kt b/Server/src/main/kotlin/api/LogoutListener.kt index 443d4c5e6..8b7456511 100644 --- a/Server/src/main/kotlin/api/LogoutListener.kt +++ b/Server/src/main/kotlin/api/LogoutListener.kt @@ -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. diff --git a/Server/src/main/kotlin/api/MapArea.kt b/Server/src/main/kotlin/api/MapArea.kt new file mode 100644 index 000000000..f26f8a61a --- /dev/null +++ b/Server/src/main/kotlin/api/MapArea.kt @@ -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 + fun getRestrictions() : Array {return arrayOf()} + fun areaEnter(entity: Entity) {} + fun areaLeave(entity: Entity) {} + fun entityStep(entity: Entity, location: Location, lastLocation: Location) {} +} \ No newline at end of file diff --git a/Server/src/main/kotlin/api/PersistPlayer.kt b/Server/src/main/kotlin/api/PersistPlayer.kt index d09416706..dc9588416 100644 --- a/Server/src/main/kotlin/api/PersistPlayer.kt +++ b/Server/src/main/kotlin/api/PersistPlayer.kt @@ -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. diff --git a/Server/src/main/kotlin/api/PersistWorld.kt b/Server/src/main/kotlin/api/PersistWorld.kt deleted file mode 100644 index cb499402a..000000000 --- a/Server/src/main/kotlin/api/PersistWorld.kt +++ /dev/null @@ -1,6 +0,0 @@ -package api - -interface PersistWorld { - fun saveWorld() - fun parseWorld() -} \ No newline at end of file diff --git a/Server/src/main/kotlin/api/ShutdownListener.kt b/Server/src/main/kotlin/api/ShutdownListener.kt index cc97a06fa..cbabc2c23 100644 --- a/Server/src/main/kotlin/api/ShutdownListener.kt +++ b/Server/src/main/kotlin/api/ShutdownListener.kt @@ -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. */ diff --git a/Server/src/main/kotlin/api/StartupListener.kt b/Server/src/main/kotlin/api/StartupListener.kt index 2240a8895..4f2fe9f9f 100644 --- a/Server/src/main/kotlin/api/StartupListener.kt +++ b/Server/src/main/kotlin/api/StartupListener.kt @@ -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. */ diff --git a/Server/src/main/kotlin/api/TickListener.kt b/Server/src/main/kotlin/api/TickListener.kt index c0d9cf4b1..debf63b37 100644 --- a/Server/src/main/kotlin/api/TickListener.kt +++ b/Server/src/main/kotlin/api/TickListener.kt @@ -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. diff --git a/Server/src/main/kotlin/rs09/game/content/zone/FarmingPatchZone.kt b/Server/src/main/kotlin/rs09/game/content/zone/FarmingPatchZone.kt index 9d97fc2fb..59aff1862 100644 --- a/Server/src/main/kotlin/rs09/game/content/zone/FarmingPatchZone.kt +++ b/Server/src/main/kotlin/rs09/game/content/zone/FarmingPatchZone.kt @@ -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 { +class FarmingPatchZone : MapArea, TickListener { + private val playersInZone = hashMapOf() - val playersInZone = hashMapOf() - - override fun configure() { - registerRegion(12083) - registerRegion(10548) - register(ZoneBorders(3594,3521,3608,3532)) - submitWorldPulse(zonePulse) + override fun defineAreaBorders(): Array { + return arrayOf( + getRegionBorders(12083), + getRegionBorders(10548), + ZoneBorders(3594,3521,3608,3532) + ) } - override fun newInstance(arg: Any?): Plugin { - 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 { poofClear(npc ?: return) } } - } \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/node/entity/player/diary/seers/SeersCourthouseZone.kt b/Server/src/main/kotlin/rs09/game/node/entity/player/diary/seers/SeersCourthouseZone.kt index 3fc4a872a..6b7815479 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/player/diary/seers/SeersCourthouseZone.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/player/diary/seers/SeersCourthouseZone.kt @@ -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{ - - override fun newInstance(arg: Any?): SeersCourthouseZone { - ZoneBuilder.configure(this) - return this +class SeersCourthouseZone : MapArea { + override fun defineAreaBorders(): Array { + 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) } } \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/plugin/ClassScanner.kt b/Server/src/main/kotlin/rs09/plugin/ClassScanner.kt index 69db21b83..8ca7a043e 100644 --- a/Server/src/main/kotlin/rs09/plugin/ClassScanner.kt +++ b/Server/src/main/kotlin/rs09/plugin/ClassScanner.kt @@ -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) - } 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 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) - } } /**