2024 Easter Event

Added Easter event
Refactored event code
Added support for forced-global ground items
This commit is contained in:
Ceikry 2024-03-31 03:43:26 +00:00 committed by Ryan
parent a7516f2d6e
commit b1e6c9fe8c
16 changed files with 441 additions and 349 deletions

BIN
Server/data/cache/main_file_cache.dat2 (Stored with Git LFS) vendored

Binary file not shown.

BIN
Server/data/cache/main_file_cache.idx19 (Stored with Git LFS) vendored

Binary file not shown.

BIN
Server/data/cache/main_file_cache.idx255 (Stored with Git LFS) vendored

Binary file not shown.

View file

@ -131018,5 +131018,17 @@
"examine": "You should not have this.",
"name": "ASDT Slot",
"id": "14430"
},
{
"destroy_message": "This item can not be reclaimed.",
"shop_price": "1",
"examine": "A rabbit-like adornment, back in black!",
"name": "Bunny ears",
"tradeable": "false",
"destroy": "true",
"weight": "0.2",
"archery_ticket_price": "0",
"id": "14658",
"equipment_slot": "0"
}
]

View file

@ -7,6 +7,7 @@ import core.tools.mysql.Database
import core.tools.secondsToTicks
import rs09.game.content.activity.castlewars.CastleWars
import java.math.BigInteger
import java.util.*
/**
* A class holding various variables for the server.
@ -316,12 +317,18 @@ class ServerConstants {
var HOLIDAY_EVENT_RANDOMS = true
@JvmField
var FORCE_HALLOWEEN_RANDOMS = false
var FORCE_HALLOWEEN_EVENTS = false
@JvmField
var FORCE_CHRISTMAS_RANDOMS = false
var FORCE_CHRISTMAS_EVENTS = false
@JvmField
var FORCE_EASTER_EVENTS = false
@JvmField
var RUNECRAFTING_FORMULA_REVISION = 581
@JvmField
var STARTUP_MOMENT = Calendar.getInstance()
}
}

View file

@ -37,6 +37,8 @@ public class GroundItem extends Item {
*/
private boolean removed;
public boolean forceVisible;
/**
* Constructs a new {@code GroundItem} {@code Object}.
* @param item The item
@ -99,7 +101,7 @@ public class GroundItem extends Item {
* @return {@code True} if so.
*/
public boolean isPrivate() {
return remainPrivate || (decayTime - GameWorld.getTicks() > 100);
return !forceVisible && (remainPrivate || (decayTime - GameWorld.getTicks() > 100));
}
@Override

View file

@ -1,16 +1,15 @@
package core.game.system.config
import com.moandjiezana.toml.Toml
import core.game.world.map.Location
import core.tools.mysql.Database
import core.ServerConstants
import core.api.log
import core.api.parseEnumEntry
import core.tools.SystemLogger
import core.game.world.GameSettings
import core.game.world.GameWorld
import core.game.world.map.Location
import core.tools.Log
import core.tools.LogLevel
import core.tools.mysql.Database
import java.io.File
import java.net.URL
import kotlin.system.exitProcess
@ -162,8 +161,9 @@ object ServerConfigParser {
ServerConstants.BETTER_DFS = data.getBoolean("world.better_dfs", true)
ServerConstants.NEW_PLAYER_ANNOUNCEMENT = data.getBoolean("world.new_player_announcement", true)
ServerConstants.HOLIDAY_EVENT_RANDOMS = data.getBoolean("world.holiday_event_randoms", true)
ServerConstants.FORCE_HALLOWEEN_RANDOMS = data.getBoolean("world.force_halloween_randoms", false)
ServerConstants.FORCE_CHRISTMAS_RANDOMS = data.getBoolean("world.force_christmas_randoms", false)
ServerConstants.FORCE_HALLOWEEN_EVENTS = data.getBoolean("world.force_halloween_randoms", false)
ServerConstants.FORCE_CHRISTMAS_EVENTS = data.getBoolean("world.force_christmas_randoms", false)
ServerConstants.FORCE_EASTER_EVENTS = data.getBoolean("world.force_easter_randoms", false)
ServerConstants.RUNECRAFTING_FORMULA_REVISION = data.getLong("world.runecrafting_formula_revision", 581).toInt()
val logLevel = data.getString("server.log_level", "VERBOSE").uppercase()

View file

@ -1,42 +1,33 @@
package core.game.worldevents
import core.tools.SystemLogger
import core.plugin.Plugin
import org.json.simple.JSONObject
import core.ServerStore
import core.api.ContentInterface
import core.plugin.ClassScanner
import core.plugin.Plugin
import core.tools.Log
import org.json.simple.JSONObject
import java.util.*
/**
* The class other world events should extend off of.
* @author Ceikry
*/
open class WorldEvent(var name: String) {
abstract class WorldEvent(var name: String) : ContentInterface {
var plugins = PluginSet()
/**
* if the event is active or not. Can be used to check dates or just always return true
* whatever you need for this specific event
*/
open fun checkActive(): Boolean {
open fun checkActive(cal: Calendar): Boolean {
return false
}
/**
* Check to see if the event should trigger. An event trigger could spawn shooting stars, or a world boss
* or whatever kind of whacky shit you need it to do.
*/
open fun checkTrigger(): Boolean{
return true
}
/**
* Used to initialize the event, which loads all associated plugins.
* Used to initialize the event
* The WorldEventInitializer runs this if checkActive() returns true.
*/
open fun initialize(){
plugins.initialize()
open fun initEvent(){
}
/**
@ -45,11 +36,6 @@ open class WorldEvent(var name: String) {
fun log(message: String){
core.api.log(this::class.java, Log.FINE, "[World Events($name)] $message")
}
/**
* Used to start, or "fire", world events.
*/
open fun fireEvent() {}
}
@ -74,11 +60,11 @@ object WorldEvents {
private var events = hashMapOf<String, WorldEvent>()
fun add(event: WorldEvent){
events.put(event.name.toLowerCase(),event)
events[event.name.lowercase(Locale.getDefault())] = event
}
fun get(name: String): WorldEvent?{
return events.get(name.toLowerCase())
return events[name.lowercase(Locale.getDefault())]
}
fun getArchive() : JSONObject {

View file

@ -1,34 +0,0 @@
package core.game.worldevents
import core.plugin.Initializable
import core.plugin.Plugin
import io.github.classgraph.ClassGraph
import io.github.classgraph.ClassInfo
import java.util.function.Consumer
/**
* Responsible for initializing world events
* @author Ceikry
*/
@Initializable
class WorldEventInitializer : Plugin<Any>{
override fun newInstance(arg: Any?): Plugin<Any> {
/**
* Here I used ClassGraph to scan the core.game.content.global.worldevents package for subclasses of the WorldEvent class and
* then initialize them if their active setting evaluates to true. This makes it so we don't have to manually add them to
* a list. It also prevents unnecessary plugins from being loaded if an event isn't currently active.
*/
val result = ClassGraph().enableClassInfo().whitelistPackages("rs09.game.content.global.worldevents").scan()
result.getSubclasses("rs09.game.content.global.worldevents.WorldEvent").forEach(Consumer { p: ClassInfo ->
val c = p.loadClass().newInstance() as WorldEvent
if(c.checkActive()) c.initialize().also { WorldEvents.add(c) }
})
return this
}
override fun fireEvent(identifier: String?, vararg args: Any?): Any {
return Unit
}
}

View file

@ -75,10 +75,10 @@ class HolidayRandoms() : PersistTimer(0, "holiday", isAuto = true), Commands {
fun checkIfHoliday(): String {
val currentDate = LocalDate.now()
if ((!currentDate.isBefore(halloweenStartDate) && !currentDate.isAfter(halloweenEndDate)) || ServerConstants.FORCE_HALLOWEEN_RANDOMS)
if ((!currentDate.isBefore(halloweenStartDate) && !currentDate.isAfter(halloweenEndDate)) || ServerConstants.FORCE_HALLOWEEN_EVENTS)
return "halloween"
if ((!currentDate.isBefore(christmasStartDate) && !currentDate.isAfter(christmasEndDate)) || ServerConstants.FORCE_CHRISTMAS_RANDOMS)
if ((!currentDate.isBefore(christmasStartDate) && !currentDate.isAfter(christmasEndDate)) || ServerConstants.FORCE_CHRISTMAS_EVENTS)
return "christmas"
return "none"
@ -164,7 +164,7 @@ class HolidayRandoms() : PersistTimer(0, "holiday", isAuto = true), Commands {
ServerConstants.HOLIDAY_EVENT_RANDOMS = true
when (event) {
"halloween" -> {
ServerConstants.FORCE_HALLOWEEN_RANDOMS = true
ServerConstants.FORCE_HALLOWEEN_EVENTS = true
for (p in Repository.players) {
if (getTimer<HolidayRandoms>(p) != null || p.isArtificial) {
continue
@ -174,7 +174,7 @@ class HolidayRandoms() : PersistTimer(0, "holiday", isAuto = true), Commands {
}
}
"christmas" -> {
ServerConstants.FORCE_CHRISTMAS_RANDOMS = true
ServerConstants.FORCE_CHRISTMAS_EVENTS = true
for (p in Repository.players) {
if (getTimer<HolidayRandoms>(p) != null || p.isArtificial) {
continue
@ -191,8 +191,8 @@ class HolidayRandoms() : PersistTimer(0, "holiday", isAuto = true), Commands {
if (checkIfHoliday() == "none" || !ServerConstants.HOLIDAY_EVENT_RANDOMS)
reject(player, "No holiday random events are currently active.")
ServerConstants.HOLIDAY_EVENT_RANDOMS = false
ServerConstants.FORCE_HALLOWEEN_RANDOMS = false
ServerConstants.FORCE_CHRISTMAS_RANDOMS = false
ServerConstants.FORCE_HALLOWEEN_EVENTS = false
ServerConstants.FORCE_CHRISTMAS_EVENTS = false
for (p in Repository.players) {
if (getTimer<HolidayRandoms>(p) == null) {
continue

View file

@ -1,106 +0,0 @@
package core.game.worldevents.holiday.easter
import core.game.dialogue.FacialExpression
import core.game.node.entity.player.link.emote.Emotes
import core.game.node.item.GroundItemManager
import core.game.node.item.Item
import org.rs09.consts.Items
import core.game.dialogue.DialogueFile
import core.game.worldevents.WorldEvents
import core.tools.END_DIALOGUE
class EasterBunnyDialogueFile(val NEED_BASKET : Boolean) : DialogueFile() {
val EGG_ATTRIBUTE = "/save:easter:eggs"
override fun handle(componentID: Int, buttonID: Int) {
if(NEED_BASKET){
when(stage++){
0 -> npc("Hello, adventurer! Thank goodness you're here!")
1 -> player(FacialExpression.THINKING,"Thanks goodness I'M here?")
2 -> npc("Yes, yes, I need your help, you see!")
3 -> npc("I have lost ALL of my eggs. What a terrible thing.")
4 -> npc("Us easter bunnies rely on EGGS to live.")
5 -> npc("Take this basket, please, and do me a kindness.")
6 -> player(FacialExpression.THINKING,"What kindness might that be?")
7 -> npc("I need you to try and gather up as many of my","lost eggs as you can.")
8 -> npc("Please, for me? I will reward you for your time.")
9 -> player("Fine, I suppose I will.")
10 -> {
player!!.inventory.add(Item(Items.BASKET_OF_EGGS_4565))
player!!.dialogueInterpreter.sendItemMessage(Items.BASKET_OF_EGGS_4565,"The Easter Bunny gives you a basket.")
if(!player!!.musicPlayer.hasUnlocked(273)) {
player!!.musicPlayer.unlock(273, true)
}
stage = END_DIALOGUE
}
}
} else {
when (stage) {
0 -> options("Ask about rewards", "Ask about egg location").also { stage++ }
1 -> when (buttonID) {
1 -> player(FacialExpression.THINKING, "What kind of rewards can I claim?").also { stage = 10 }
2 -> player(FacialExpression.THINKING, "Where were some eggs last seen?").also { stage = 20 }
}
10 -> {
if (!player!!.emoteManager.isUnlocked(Emotes.BUNNY_HOP)) {
options("A credit (5 eggs)", "A Book of Knowledge (20 eggs)", "Bunny Hop Emote (30 eggs)")
} else {
options("A credit (5 eggs)", "A Book of Knowledge (20 eggs)")
}
stage++
}
11 -> {
end()
when (buttonID) {
1 -> {
if (player!!.getAttribute(EGG_ATTRIBUTE, 0) < 5) {
player!!.dialogueInterpreter.sendDialogue("You need 5 eggs to afford that.")
} else {
player!!.incrementAttribute(EGG_ATTRIBUTE, -5)
player!!.details.accountInfo.credits += 1
player!!.dialogueInterpreter.sendDialogue(
"You turn in 5 eggs in exchange for a credit.",
"You now have ${player!!.getAttribute(EGG_ATTRIBUTE, 0)} eggs."
)
}
}
2 -> {
if (player!!.getAttribute(EGG_ATTRIBUTE, 0) < 20) {
player!!.dialogueInterpreter.sendDialogue("You need 20 eggs to afford that.")
} else {
player!!.incrementAttribute(EGG_ATTRIBUTE, -20)
val item = Item(Items.BOOK_OF_KNOWLEDGE_11640)
if (!player!!.inventory.add(item)) {
GroundItemManager.create(item, player)
}
player!!.dialogueInterpreter.sendDialogue("You spend 20 eggs on a Book of Knowledge.")
}
}
3 -> {
if (player!!.getAttribute(EGG_ATTRIBUTE, 0) < 30) {
player!!.dialogueInterpreter.sendDialogue("You need 30 eggs to afford that.")
} else {
player!!.incrementAttribute(EGG_ATTRIBUTE, -30)
player!!.emoteManager.unlock(Emotes.BUNNY_HOP)
player!!.dialogueInterpreter.sendDialogue(
"The Easter Bunny teaches you how to bunny hop in exchange",
"for 30 eggs."
)
}
}
}
}
20 ->{
val event = WorldEvents.get("easter") as EasterEvent
npc("The last known location of some eggs is ${event.recentLoc}.").also { stage = END_DIALOGUE }
}
}
}
}
}

View file

@ -1,22 +1,229 @@
package core.game.worldevents.holiday.easter
import core.game.node.entity.npc.NPC
import core.ServerConstants
import core.api.*
import core.api.utils.WeightBasedTable
import core.api.utils.WeightedItem
import core.game.event.EventHook
import core.game.event.XPGainEvent
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.interaction.QueueStrength
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.emote.Emotes
import core.game.node.item.GroundItem
import core.game.node.item.GroundItemManager
import core.game.node.item.Item
import core.game.system.task.Pulse
import core.game.world.GameWorld
import core.game.world.map.Direction
import core.game.world.map.Location
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import core.game.world.map.path.Pathfinder
import core.game.worldevents.WorldEvent
import core.game.system.config.GroundSpawnLoader
import core.game.world.GameWorld
import core.game.world.repository.Repository
import core.tools.secondsToTicks
import core.tools.RandomFunction
import core.tools.StringUtils
import core.tools.colorize
import org.rs09.consts.Items
import java.util.*
class EasterEvent : WorldEvent("easter") {
class EasterEvent : WorldEvent("easter"), TickListener, InteractionListener, LoginListener {
private val spawnedItems = ArrayList<GroundItem>()
private var timeUntilNextEggSpawn = 0
private var currentLoc = ""
override fun tick() {
if (timeUntilNextEggSpawn-- > 0) return
timeUntilNextEggSpawn = EGG_SPAWN_TIME
for (egg in spawnedItems)
{
GroundItemManager.destroy(egg)
}
spawnedItems.clear()
val (locName, locData) = getRandomLocations()
currentLoc = locName
for (loc in locData)
{
val gi = GroundItemManager.create(GroundItem(
Item(eggs.random()),
loc,
EGG_SPAWN_TIME,
null
))
gi.forceVisible = true
spawnedItems.add(gi)
}
sendNews("Eggs have appeared in $locName!")
}
override fun login(player: Player) {
if (spawnedItems.isNotEmpty())
sendMessage(player, colorize("%GEggs were last spotted in $currentLoc!"))
player.hook(Event.XpGained, xpEventHook)
}
object xpEventHook : EventHook<XPGainEvent>
{
override fun process(entity: Entity, event: XPGainEvent) {
if (entity !is Player) return
val lastRoll = getAttribute(entity, LAST_EGG_ROLL, 0)
if (getWorldTicks() - lastRoll < 10) return
setAttribute(entity, LAST_EGG_ROLL, getWorldTicks())
val activeEggRate = if (GameWorld.settings!!.isDevMode) EGG_RATE_DEV else EGG_RATE
if (RandomFunction.roll(activeEggRate)) {
val dirs = Direction.values()
val dir = dirs[RandomFunction.random(dirs.size)]
val loc = entity.location.transform(dir, 3)
if (!Pathfinder.find(entity, loc).isSuccessful) return
GroundItemManager.create(Item(eggs.random()), loc, entity)
sendMessage(entity, colorize("%RAn egg has appeared nearby."))
}
}
}
fun getRandomLocations() : Pair<String, List<Location>>
{
val toReturn = ArrayList<Location>()
val name = locNames.random()
val locs = locationsForName(name)
for (loc in locs)
if (RandomFunction.nextBool()) toReturn.add(loc)
return Pair(name, toReturn)
}
override fun checkActive(cal: Calendar): Boolean {
return cal.get(Calendar.MONTH) == Calendar.APRIL || ServerConstants.FORCE_EASTER_EVENTS
}
private fun onEggBroken (player: Player)
{
val eggsBroken = getAttribute(player, EGGS_BROKEN, 0) + 1
if (eggsBroken == 10)
{
player.emoteManager.unlock(Emotes.BUNNY_HOP)
Emotes.BUNNY_HOP.play(player)
sendMessage(player, colorize("%RYou have unlocked the 'Bunny Hop' emote!"))
}
else if (eggsBroken == 15)
{
addItemOrDrop(player, BUN_EARS)
sendMessage(player, colorize("%RYou have been given bunny ears!"))
}
else if (eggsBroken % 5 == 0)
{
var trackUnlocked = false;
for (track in tracks)
{
if (player.musicPlayer.hasUnlocked(track))
continue
player.musicPlayer.unlock(track)
trackUnlocked = true
break
}
if (!trackUnlocked)
giveRandomLoot(player)
}
else giveRandomLoot(player)
setAttribute(player, EGGS_BROKEN, eggsBroken)
}
private fun giveRandomLoot (player: Player)
{
//Give some loot
val loot = lootTable.roll(player)[0]
addItemOrDrop(player, loot.id, loot.amount)
val term = if (loot.amount == 1)
if (StringUtils.isPlusN(loot.name)) "an"
else "a"
else "some"
val name = if (loot.amount == 1) loot.name else StringUtils.plusS(loot.name)
sendMessage(player, "Inside the egg you find $term ${name.lowercase()}.")
}
override fun defineListeners() {
on (eggs, IntType.GROUNDITEM, "take") {player, node ->
if (node !is GroundItem) return@on true
queueScript(player, strength = QueueStrength.NORMAL) { stage ->
when (stage)
{
0 -> {
animate(player, STOMP_ANIM)
return@queueScript delayScript(player, 1)
}
1 -> {
val item = GroundItemManager.get(node.id, node.location, null)
if (item == null || !item.isActive) return@queueScript stopExecuting(player)
sendGraphics(gfxForEgg(node.id), node.location)
GroundItemManager.destroy(node)
spawnedItems.remove(node)
onEggBroken(player)
return@queueScript stopExecuting(player)
}
}
return@queueScript keepRunning(player)
}
return@on true
}
on (eggs, IntType.ITEM, "release") {player, node ->
if (node !is Item) return@on true
queueScript(player, strength = QueueStrength.NORMAL) { stage ->
when (stage)
{
0 -> {
animate(player, STOMP_ANIM)
return@queueScript delayScript(player, 1)
}
1 -> {
if (!removeItem(player, node)) return@queueScript stopExecuting(player)
sendGraphics(gfxForEgg(node.id), player.location)
onEggBroken(player)
return@queueScript stopExecuting(player)
}
}
return@queueScript keepRunning(player)
}
return@on true
}
}
companion object
{
const val LOC_LUM = "Lumbridge"
const val LOC_DRAYNOR = "Draynor Village"
const val LOC_FALADOR = "Falador"
const val LOC_EDGE = "Edgeville"
const val LOC_TGS = "Tree Gnome Stronghold"
const val EGG_SPAWN_TIME = 5_000
const val EGG_A = 11027
const val EGG_B = 11028
const val EGG_C = 11029
const val EGG_D = 11030
const val GFX_A = 1040
const val GFX_B = 1045
const val GFX_C = 1042
const val GFX_D = 1043
const val STOMP_ANIM = 10017
const val BUN_EARS = 14658
const val TRACK_BSB = 502
const val TRACK_EJ = 273
const val TRACK_FB = 603
const val EGG_RATE = 64
const val EGG_RATE_DEV = 3
val EGGS_BROKEN = "/save:easter${ServerConstants.STARTUP_MOMENT.get(Calendar.YEAR)}:eggs"
val LAST_EGG_ROLL = "easter:lasteggroll"
val tracks = arrayOf(TRACK_BSB, TRACK_EJ, TRACK_FB)
val eggs = intArrayOf(EGG_A, EGG_B, EGG_C, EGG_D)
val locNames = arrayOf(LOC_LUM, LOC_DRAYNOR, LOC_FALADOR, LOC_EDGE, LOC_TGS)
val LUMBRIDGE_SPOTS = arrayOf(Location.create(3190, 3240, 0),
Location.create(3219, 3204, 0),Location.create(3212, 3201, 0),Location.create(3205, 3209, 0),
Location.create(3205, 3217, 0),Location.create(3211, 3213, 0),Location.create(3206, 3229, 0),
@ -24,16 +231,20 @@ class EasterEvent : WorldEvent("easter") {
Location.create(3197, 3220, 0),Location.create(3189, 3207, 0),Location.create(3229, 3200, 0),
Location.create(3228, 3205, 0),Location.create(3251, 3212, 0),Location.create(3232, 3237, 0))
val DRAYNOR_SPOTS = arrayOf(Location.create(3085, 3242, 0),Location.create(3083, 3236, 0),Location.create(3093, 3224, 0),
Location.create(3099, 3241, 0),Location.create(3095, 3255, 0),Location.create(3089, 3264, 0),Location.create(3089, 3265, 0),
Location.create(3088, 3268, 0),Location.create(3090, 3274, 0),Location.create(3100, 3281, 0),Location.create(3116, 3265, 0),
Location.create(3123, 3272, 0),Location.create(3079, 3261, 0))
val DRAYNOR_SPOTS = arrayOf(Location.create(3085, 3242, 0),
Location.create(3083, 3236, 0),Location.create(3093, 3224, 0),Location.create(3099, 3241, 0),
Location.create(3095, 3255, 0),Location.create(3089, 3264, 0),Location.create(3089, 3265, 0),
Location.create(3088, 3268, 0),Location.create(3090, 3274, 0),Location.create(3100, 3281, 0),
Location.create(3116, 3265, 0),Location.create(3123, 3272, 0),Location.create(3079, 3261, 0),
Location.create(3127, 3280, 0),Location.create(3118, 3287, 0),Location.create(3111, 3270, 0),
Location.create(3119, 3277, 0),Location.create(3113, 3283, 0))
val FALADOR_SPOTS = arrayOf(
Location.create(2961, 3332, 0),Location.create(2967, 3336, 0),Location.create(2974, 3329, 0),
Location.create(2979, 3346, 0),Location.create(2970, 3348, 0),Location.create(2955, 3375, 0),Location.create(2942, 3386, 0),
Location.create(2937, 3385, 0),Location.create(3005, 3370, 0),Location.create(3006, 3383, 0),Location.create(3007, 3387, 0),
Location.create(2985, 3391, 0),Location.create(2984, 3381, 0),Location.create(2980, 3384, 0),Location.create(3025, 3345, 0),
Location.create(2979, 3346, 0),Location.create(2970, 3348, 0),Location.create(2955, 3375, 0),
Location.create(2942, 3386, 0),Location.create(2937, 3385, 0),Location.create(3005, 3370, 0),
Location.create(3006, 3383, 0),Location.create(3007, 3387, 0),Location.create(2985, 3391, 0),
Location.create(2984, 3381, 0),Location.create(2980, 3384, 0),Location.create(3025, 3345, 0),
Location.create(3060, 3389, 0),Location.create(3052, 3385, 0))
val EDGEVILLE_SPOTS = arrayOf(Location.create(3077, 3487, 0),Location.create(3082, 3487, 0),
@ -48,70 +259,49 @@ class EasterEvent : WorldEvent("easter") {
Location.create(2471, 3482, 0),Location.create(2482, 3478, 0),Location.create(2480, 3469, 0),
Location.create(2489, 3440, 0),Location.create(2470, 3417, 0),Location.create(2478, 3402, 0),
Location.create(2486, 3407, 0),Location.create(2492, 3404, 0),Location.create(2493, 3413, 0),
Location.create(2446, 3395, 0),Location.create(2422, 3398, 0),Location.create(2421, 3402, 0),Location.create(2418, 3398, 0),
Location.create(2401, 3415, 0),Location.create(2397, 3436, 0),Location.create(2409, 3448, 0),Location.create(2482, 3391, 0))
Location.create(2446, 3395, 0),Location.create(2422, 3398, 0),Location.create(2421, 3402, 0),
Location.create(2418, 3398, 0),Location.create(2401, 3415, 0),Location.create(2397, 3436, 0),
Location.create(2409, 3448, 0),Location.create(2482, 3391, 0))
val locations = arrayOf("Lumbridge","Draynor Village","Falador","Edgeville","Tree Gnome Stronghold")
val spawnedItems = ArrayList<GroundItem>()
val eggs = arrayOf(Items.EASTER_EGG_11027,Items.EASTER_EGG_11028,Items.EASTER_EGG_11029,Items.EASTER_EGG_11030)
var recentLoc = ""
override fun checkActive(): Boolean {
val isApril = Calendar.getInstance().get(Calendar.MONTH) == Calendar.APRIL
val isBefore9th = Calendar.getInstance().get(Calendar.DAY_OF_MONTH) < 9
return false
}
override fun initialize() {
super.initialize()
val easterBunny = NPC(NPCs.EASTER_BUNNY_7197)
easterBunny.isNeverWalks = true
easterBunny.direction = Direction.WEST
easterBunny.location = Location.create(3225, 3212, 0)
for(i in 0 until 5){
val bunny = NPC(NPCs.EASTER_BUNNY_3688)
bunny.location = Location.create(3223, 3213, 0)
bunny.isWalks = true
bunny.walkRadius = 4
bunny.init()
}
easterBunny.init()
GameWorld.settings?.message_model = 715
GameWorld.settings?.message_string = "Happy Easter!"
GameWorld.Pulser.submit(object : Pulse(){
override fun pulse(): Boolean {
if(delay == 1){
delay = secondsToTicks(if(GameWorld.settings?.isDevMode == true) 60 else 3600)
}
for(item in spawnedItems){
GroundItemManager.destroy(item)
}
spawnedItems.clear()
val locName = locations.random()
val locs = when(locName){
"Lumbridge" -> LUMBRIDGE_SPOTS
"Draynor Village" -> DRAYNOR_SPOTS
"Falador" -> FALADOR_SPOTS
"Edgeville" -> EDGEVILLE_SPOTS
"Tree Gnome Stronghold" -> TREE_GNOME_STRONGHOLD_SPOTS
fun locationsForName (name: String) : Array<Location>
{
return when (name)
{
LOC_LUM -> LUMBRIDGE_SPOTS
LOC_DRAYNOR -> DRAYNOR_SPOTS
LOC_FALADOR -> FALADOR_SPOTS
LOC_TGS -> TREE_GNOME_STRONGHOLD_SPOTS
LOC_EDGE -> EDGEVILLE_SPOTS
else -> LUMBRIDGE_SPOTS
}.toMutableList()
Repository.sendNews("A bunch of eggs have been spotted in $locName!")
recentLoc = locName
val itemLocs = ArrayList<Location>()
for(i in 0 until locs.size / 2){
val loc = locs.random()
locs.remove(loc)
itemLocs.add(loc)
}
}
for(i in itemLocs){
spawnedItems.add(GroundSpawnLoader.GroundSpawn(-1, Item(eggs.random()),i).init())
fun gfxForEgg (egg: Int) : Int
{
return when (egg)
{
EGG_A -> GFX_A
EGG_B -> GFX_B
EGG_C -> GFX_C
EGG_D -> GFX_D
else -> GFX_A
}
return false
}
})
val lootTable = WeightBasedTable.create(
WeightedItem(Items.EASTER_EGG_1962, 1, 15, 0.10),
WeightedItem(Items.PURPLE_SWEETS_10476, 5, 15, 0.10),
WeightedItem(Items.COINS_995, 500, 2500, 0.15),
WeightedItem(Items.ESS_IMPLING_JAR_11246, 1, 1, 0.05),
WeightedItem(Items.BABY_IMPLING_JAR_11238, 1, 1, 0.05),
WeightedItem(Items.EARTH_IMPLING_JAR_11244, 1, 1, 0.05),
WeightedItem(Items.GOURM_IMPLING_JAR_11242, 1, 1, 0.05),
WeightedItem(Items.MAGPIE_IMPLING_JAR_11252, 1, 1, 0.025),
WeightedItem(Items.ECLECTIC_IMPLING_JAR_11248, 1, 1, 0.025),
WeightedItem(Items.ESS_IMPLING_JAR_11246, 1, 1, 0.025),
WeightedItem(Items.NATURE_IMPLING_JAR_11250, 1, 1, 0.015),
WeightedItem(Items.NINJA_IMPLING_JAR_11254, 1, 1, 0.015),
WeightedItem(Items.DRAGON_IMPLING_JAR_11256, 1, 1, 0.005)
)
}
}

View file

@ -1,50 +1,10 @@
package core.game.worldevents.holiday.easter
import core.game.interaction.DestinationFlag
import core.game.interaction.MovementPulse
import core.game.node.item.GroundItemManager
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import core.game.interaction.InteractionListener
import core.game.interaction.IntType
class EasterEventListeners : InteractionListener {
val EGG_ATTRIBUTE = "/save:easter:eggs"
val eggs = intArrayOf(Items.EASTER_EGG_11027, Items.EASTER_EGG_11028, Items.EASTER_EGG_11029, Items.EASTER_EGG_11030)
override fun defineListeners() {
on(eggs, IntType.ITEM, "take"){ player, node ->
player.pulseManager.run(object : MovementPulse(player,node, DestinationFlag.ITEM){
override fun pulse(): Boolean {
if(player.inventory.contains(Items.BASKET_OF_EGGS_4565,1) || player.equipment.contains(Items.BASKET_OF_EGGS_4565,1)){
val item = GroundItemManager.get(node.id,node.location,null)
GroundItemManager.destroy(item)
player.incrementAttribute(EGG_ATTRIBUTE)
player.sendMessage("You place the egg in the basket. You now have ${player.getAttribute<Int>(EGG_ATTRIBUTE,0)} eggs!")
} else {
player.sendMessage("You need to have your egg basket with you to collect these.")
}
return true
}
})
return@on true
}
on(Items.BASKET_OF_EGGS_4565, IntType.ITEM, "operate"){ player, node ->
val numEggs = player.getAttribute<Int>(EGG_ATTRIBUTE) ?: 0
player.dialogueInterpreter.sendDialogue("You have $numEggs eggs in the basket.")
return@on true
}
on(NPCs.EASTER_BUNNY_7197, IntType.NPC, "talk-to"){ player, node ->
val npc = node.asNpc()
npc.faceLocation(player.location)
val NEED_BASKET = !(player!!.inventory.contains(Items.BASKET_OF_EGGS_4565,1) || player!!.equipment.contains(Items.BASKET_OF_EGGS_4565,1) || player!!.bank.contains(Items.BASKET_OF_EGGS_4565,1))
player.dialogueInterpreter.open(EasterBunnyDialogueFile(NEED_BASKET),npc)
return@on true
}
}

View file

@ -1,21 +0,0 @@
package core.game.worldevents.holiday.halloween
import core.game.worldevents.PluginSet
import core.game.worldevents.WorldEvent
import core.game.world.GameWorld
class SimpleHalloweenEvent : WorldEvent("hween"){
override fun checkActive(): Boolean {
return false
}
override fun initialize() {
plugins = PluginSet(
GrimDialogue()
)
super.initialize()
GameWorld.settings?.message_model = 800
GameWorld.settings?.message_string = "A mysterious figure has appeared in Draynor! You should go investigate!"
log("Initialized.")
}
}

View file

@ -1,29 +1,32 @@
package core.plugin
import core.ServerConstants
import core.api.*
import core.game.activity.ActivityManager
import core.game.activity.ActivityPlugin
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 io.github.classgraph.ClassGraph
import io.github.classgraph.ClassInfo
import io.github.classgraph.ScanResult
import core.game.system.timer.*
import core.game.bots.PlayerScripts
import core.game.interaction.InteractionListener
import core.game.interaction.InterfaceListener
import core.game.node.entity.Entity
import core.game.node.entity.npc.NPCBehavior
import core.game.node.entity.player.info.login.LoginConfiguration
import core.game.node.entity.player.info.login.PlayerSaveParser
import core.game.node.entity.player.info.login.PlayerSaver
import core.tools.SystemLogger
import core.tools.SystemLogger.logStartup
import core.game.node.entity.player.link.quest.Quest
import core.game.node.entity.player.link.quest.QuestRepository
import core.game.system.timer.RSTimer
import core.game.system.timer.TimerRegistry
import core.game.world.GameWorld
import core.game.world.map.Location
import core.game.world.map.zone.MapZone
import core.game.world.map.zone.ZoneBuilder
import core.game.worldevents.WorldEvent
import core.game.worldevents.WorldEvents
import core.tools.Log
import core.tools.SystemLogger.logStartup
import io.github.classgraph.ClassGraph
import io.github.classgraph.ClassInfo
import io.github.classgraph.ScanResult
import java.util.function.Consumer
/**
@ -90,6 +93,10 @@ object ClassScanner {
scanResults.getClassesImplementing("core.api.ContentInterface").filter { !it.isAbstract }.forEach {
try {
val clazz = it.loadClass().newInstance()
if(clazz is WorldEvent) { //Check world event first so if it's not active we don't register tick listeners, etc.
if (!clazz.checkActive(ServerConstants.STARTUP_MOMENT)) return@forEach
WorldEvents.add(clazz)
}
if(clazz is LoginListener) GameWorld.loginListeners.add(clazz)
if(clazz is LogoutListener) GameWorld.logoutListeners.add(clazz)
if(clazz is TickListener) GameWorld.tickListeners.add(clazz)

View file

@ -0,0 +1,89 @@
package core.game.worldevents.holiday.easter
import core.game.world.map.Location
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import java.util.*
class EasterEventTests {
@Test fun eventShouldOnlyTriggerInApril()
{
val inst = EasterEvent()
val calInst = Calendar.getInstance()
calInst.set(Calendar.YEAR, 2024)
calInst.set(Calendar.MONTH, Calendar.APRIL)
calInst.set(Calendar.DAY_OF_MONTH, 1)
Assertions.assertEquals(true, inst.checkActive(calInst))
calInst.set(Calendar.MONTH, Calendar.MARCH)
Assertions.assertEquals(false, inst.checkActive(calInst))
calInst.set(Calendar.DAY_OF_MONTH, 31)
Assertions.assertEquals(false, inst.checkActive(calInst))
calInst.set(Calendar.MONTH, Calendar.MAY)
calInst.set(Calendar.DAY_OF_MONTH, 1)
Assertions.assertEquals(false, inst.checkActive(calInst))
}
@Test fun getLocationsForNameShouldReturnExpectedList()
{
val expectations = arrayOf(
Pair(EasterEvent.LOC_LUM, EasterEvent.LUMBRIDGE_SPOTS),
Pair(EasterEvent.LOC_DRAYNOR, EasterEvent.DRAYNOR_SPOTS),
Pair(EasterEvent.LOC_TGS, EasterEvent.TREE_GNOME_STRONGHOLD_SPOTS),
Pair(EasterEvent.LOC_EDGE, EasterEvent.EDGEVILLE_SPOTS),
Pair(EasterEvent.LOC_FALADOR, EasterEvent.FALADOR_SPOTS),
Pair("Random Invalid Dummy Data", EasterEvent.LUMBRIDGE_SPOTS)
)
for ((locName, locList) in expectations)
{
Assertions.assertEquals(locList, EasterEvent.locationsForName(locName))
}
}
@Test fun getRandomLocationDataShouldReturnARandomLocationNameAndListOfUniqueRandomLocations()
{
val inst = EasterEvent()
val sampleA = inst.getRandomLocations()
var sampleB = inst.getRandomLocations()
//Assert that they are equal when they contain the same information (just in case JVM is goofy)
Assertions.assertEquals(sampleA, Pair(sampleA.first, sampleA.second.toTypedArray().toList()))
//build in some safety against extreme unlikelihood
var similarityTolerance = 3
//Check 100 times just to be very sure that we're not getting the same data
for (i in 0 until 100) {
if (sampleA == sampleB) similarityTolerance--
sampleB = inst.getRandomLocations()
}
Assertions.assertEquals(true, similarityTolerance > 0)
val usedLocations = ArrayList<Location>()
for (loc in sampleB.second)
{
if (!usedLocations.contains(loc))
usedLocations.add(loc)
else
Assertions.fail("Loc $loc appeared more than once!")
}
}
@Test fun gfxForEggShouldReturnCorrectGfx()
{
val expectations = arrayOf(
Pair(EasterEvent.EGG_A, EasterEvent.GFX_A),
Pair(EasterEvent.EGG_B, EasterEvent.GFX_B),
Pair(EasterEvent.EGG_C, EasterEvent.GFX_C),
Pair(EasterEvent.EGG_D, EasterEvent.GFX_D),
Pair(-1, EasterEvent.GFX_A)
)
for ((egg, gfx) in expectations)
Assertions.assertEquals(gfx, EasterEvent.gfxForEgg(egg))
}
}