diff --git a/Server/data/cache/main_file_cache.dat2 b/Server/data/cache/main_file_cache.dat2 index 5658efe32..376fd9153 100644 --- a/Server/data/cache/main_file_cache.dat2 +++ b/Server/data/cache/main_file_cache.dat2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2985b1eac72527b3badb3316d61ba0043310f3c38dd6a5626ae2bfb082fee200 +oid sha256:d6b92de1ed8601bc3961de4466e961cffe5777887cb87c7ffed1d466a3d08491 size 91621090 diff --git a/Server/data/cache/main_file_cache.idx19 b/Server/data/cache/main_file_cache.idx19 index e46de80f4..853d55e14 100644 --- a/Server/data/cache/main_file_cache.idx19 +++ b/Server/data/cache/main_file_cache.idx19 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4de110552ac35ab5395e44b79133450ba04eac1fa36c4eceb7271cda858024f1 +oid sha256:3e8d1f3dd00aed753029371a37eb75f2068a6bfb902d481891a2e15afd3a9f34 size 348 diff --git a/Server/data/cache/main_file_cache.idx255 b/Server/data/cache/main_file_cache.idx255 index 4c00d4443..a437d6da5 100644 --- a/Server/data/cache/main_file_cache.idx255 +++ b/Server/data/cache/main_file_cache.idx255 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad2c31c2f1ce536aee407c59c733ece2809e4ee9b930108f13e149744521ed56 +oid sha256:86269328a9c606c683569c2d9ea200a391e08c04c1591071aaed9a36978b744b size 174 diff --git a/Server/data/configs/item_configs.json b/Server/data/configs/item_configs.json index 8c2addbd0..14fe33cfe 100644 --- a/Server/data/configs/item_configs.json +++ b/Server/data/configs/item_configs.json @@ -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" } ] \ No newline at end of file diff --git a/Server/src/main/core/ServerConstants.kt b/Server/src/main/core/ServerConstants.kt index c15622a83..bad28925c 100644 --- a/Server/src/main/core/ServerConstants.kt +++ b/Server/src/main/core/ServerConstants.kt @@ -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() } } diff --git a/Server/src/main/core/game/node/item/GroundItem.java b/Server/src/main/core/game/node/item/GroundItem.java index af9054ee8..dfdfe92f1 100644 --- a/Server/src/main/core/game/node/item/GroundItem.java +++ b/Server/src/main/core/game/node/item/GroundItem.java @@ -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 diff --git a/Server/src/main/core/game/system/config/ServerConfigParser.kt b/Server/src/main/core/game/system/config/ServerConfigParser.kt index 62b866443..5996a508f 100644 --- a/Server/src/main/core/game/system/config/ServerConfigParser.kt +++ b/Server/src/main/core/game/system/config/ServerConfigParser.kt @@ -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() diff --git a/Server/src/main/core/game/worldevents/WorldEvent.kt b/Server/src/main/core/game/worldevents/WorldEvent.kt index 98f66aaf3..705d458cd 100644 --- a/Server/src/main/core/game/worldevents/WorldEvent.kt +++ b/Server/src/main/core/game/worldevents/WorldEvent.kt @@ -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() 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 { diff --git a/Server/src/main/core/game/worldevents/WorldEventInitializer.kt b/Server/src/main/core/game/worldevents/WorldEventInitializer.kt deleted file mode 100644 index 2e0c7b5cb..000000000 --- a/Server/src/main/core/game/worldevents/WorldEventInitializer.kt +++ /dev/null @@ -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{ - override fun newInstance(arg: Any?): Plugin { - /** - * 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 - } - -} \ No newline at end of file diff --git a/Server/src/main/core/game/worldevents/holiday/HolidayRandoms.kt b/Server/src/main/core/game/worldevents/holiday/HolidayRandoms.kt index 6ad5ed6f2..cf63a2e6d 100644 --- a/Server/src/main/core/game/worldevents/holiday/HolidayRandoms.kt +++ b/Server/src/main/core/game/worldevents/holiday/HolidayRandoms.kt @@ -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(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(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(p) == null) { continue diff --git a/Server/src/main/core/game/worldevents/holiday/easter/EasterBunnyDialogueFile.kt b/Server/src/main/core/game/worldevents/holiday/easter/EasterBunnyDialogueFile.kt deleted file mode 100644 index d8b1d98cb..000000000 --- a/Server/src/main/core/game/worldevents/holiday/easter/EasterBunnyDialogueFile.kt +++ /dev/null @@ -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 } - } - } - } - } -} \ No newline at end of file diff --git a/Server/src/main/core/game/worldevents/holiday/easter/EasterEvent.kt b/Server/src/main/core/game/worldevents/holiday/easter/EasterEvent.kt index 63a904a72..43d33ba40 100644 --- a/Server/src/main/core/game/worldevents/holiday/easter/EasterEvent.kt +++ b/Server/src/main/core/game/worldevents/holiday/easter/EasterEvent.kt @@ -1,117 +1,307 @@ 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") { - 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), - Location.create(3212, 3226, 0),Location.create(3212, 3244, 0),Location.create(3202, 3252, 0), - 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)) +class EasterEvent : WorldEvent("easter"), TickListener, InteractionListener, LoginListener { + private val spawnedItems = ArrayList() + private var timeUntilNextEggSpawn = 0 + private var currentLoc = "" - 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)) + override fun tick() { + if (timeUntilNextEggSpawn-- > 0) return + timeUntilNextEggSpawn = EGG_SPAWN_TIME - 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(3060, 3389, 0),Location.create(3052, 3385, 0)) + for (egg in spawnedItems) + { + GroundItemManager.destroy(egg) + } + spawnedItems.clear() - val EDGEVILLE_SPOTS = arrayOf(Location.create(3077, 3487, 0),Location.create(3082, 3487, 0), - Location.create(3089, 3481, 0),Location.create(3084, 3479, 0),Location.create(3108, 3499, 0), - Location.create(3110, 3517, 0),Location.create(3091, 3512, 0),Location.create(3092, 3507, 0), - Location.create(3081, 3513, 0),Location.create(3079, 3513, 1),Location.create(3080, 3508, 1), - Location.create(3108, 3499, 0),Location.create(3093, 3467, 0)) + val (locName, locData) = getRandomLocations() + currentLoc = locName - val TREE_GNOME_STRONGHOLD_SPOTS = arrayOf( - Location.create(2480, 3507, 0),Location.create(2486, 3513, 0),Location.create(2453, 3512, 0), - Location.create(2442, 3484, 0),Location.create(2438, 3486, 0),Location.create(2441, 3506, 0), - 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)) + for (loc in locData) + { + val gi = GroundItemManager.create(GroundItem( + Item(eggs.random()), + loc, + EGG_SPAWN_TIME, + null + )) + gi.forceVisible = true + spawnedItems.add(gi) + } - val locations = arrayOf("Lumbridge","Draynor Village","Falador","Edgeville","Tree Gnome Stronghold") - - val spawnedItems = ArrayList() - 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 + sendNews("Eggs have appeared in $locName!") } - 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 - else -> LUMBRIDGE_SPOTS - }.toMutableList() + override fun login(player: Player) { + if (spawnedItems.isNotEmpty()) + sendMessage(player, colorize("%GEggs were last spotted in $currentLoc!")) + player.hook(Event.XpGained, xpEventHook) + } - Repository.sendNews("A bunch of eggs have been spotted in $locName!") - recentLoc = locName - val itemLocs = ArrayList() - for(i in 0 until locs.size / 2){ - val loc = locs.random() - locs.remove(loc) - itemLocs.add(loc) - } + object xpEventHook : EventHook + { + override fun process(entity: Entity, event: XPGainEvent) { + if (entity !is Player) return - for(i in itemLocs){ - spawnedItems.add(GroundSpawnLoader.GroundSpawn(-1, Item(eggs.random()),i).init()) - } - return false + 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> + { + val toReturn = ArrayList() + 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), + Location.create(3212, 3226, 0),Location.create(3212, 3244, 0),Location.create(3202, 3252, 0), + 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), + 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(3060, 3389, 0),Location.create(3052, 3385, 0)) + + val EDGEVILLE_SPOTS = arrayOf(Location.create(3077, 3487, 0),Location.create(3082, 3487, 0), + Location.create(3089, 3481, 0),Location.create(3084, 3479, 0),Location.create(3108, 3499, 0), + Location.create(3110, 3517, 0),Location.create(3091, 3512, 0),Location.create(3092, 3507, 0), + Location.create(3081, 3513, 0),Location.create(3079, 3513, 1),Location.create(3080, 3508, 1), + Location.create(3108, 3499, 0),Location.create(3093, 3467, 0)) + + val TREE_GNOME_STRONGHOLD_SPOTS = arrayOf( + Location.create(2480, 3507, 0),Location.create(2486, 3513, 0),Location.create(2453, 3512, 0), + Location.create(2442, 3484, 0),Location.create(2438, 3486, 0),Location.create(2441, 3506, 0), + 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)) + + fun locationsForName (name: String) : Array + { + 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 + } + } + + 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 + } + } + + 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) + ) } } \ No newline at end of file diff --git a/Server/src/main/core/game/worldevents/holiday/easter/EasterEventListeners.kt b/Server/src/main/core/game/worldevents/holiday/easter/EasterEventListeners.kt index 4dd777cf3..2fdc30c28 100644 --- a/Server/src/main/core/game/worldevents/holiday/easter/EasterEventListeners.kt +++ b/Server/src/main/core/game/worldevents/holiday/easter/EasterEventListeners.kt @@ -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(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(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 - } } diff --git a/Server/src/main/core/game/worldevents/holiday/halloween/SimpleHalloweenEvent.kt b/Server/src/main/core/game/worldevents/holiday/halloween/SimpleHalloweenEvent.kt deleted file mode 100644 index 50c26daea..000000000 --- a/Server/src/main/core/game/worldevents/holiday/halloween/SimpleHalloweenEvent.kt +++ /dev/null @@ -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.") - } -} \ No newline at end of file diff --git a/Server/src/main/core/plugin/ClassScanner.kt b/Server/src/main/core/plugin/ClassScanner.kt index 30bea7201..2ad6e5f4b 100644 --- a/Server/src/main/core/plugin/ClassScanner.kt +++ b/Server/src/main/core/plugin/ClassScanner.kt @@ -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) diff --git a/Server/src/test/kotlin/core/game/worldevents/holiday/easter/EasterEventTests.kt b/Server/src/test/kotlin/core/game/worldevents/holiday/easter/EasterEventTests.kt new file mode 100644 index 000000000..8ad73cdaf --- /dev/null +++ b/Server/src/test/kotlin/core/game/worldevents/holiday/easter/EasterEventTests.kt @@ -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() + 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)) + } +} \ No newline at end of file