Wilderness improvements

Added an extra option to the deep wilderness gate warning: "I wish to proceed, and don't ask me again"
Reused the same warning, with appropriate changes, for the edge/ardy levers due to the pvp mechanics
Removed the interface warning when crossing the ditch until doomsayer is implemented
Removed the inauthentic threat revenant and restricted brawler/pvp drops to revenants and chaos elemental only
Now require 100k risk to obtain the boosted deep-wildy drop rates
Hid our inauthentic mechanics behind a new server config option enhanced_deep_wilderness
This commit is contained in:
Player Name 2024-07-13 05:46:17 +00:00 committed by Ryan
parent 30f1cbf710
commit 6b152bcc8c
9 changed files with 98 additions and 228 deletions

View file

@ -1,5 +1,6 @@
package content.region.kandarin.ardougne.handlers;
import core.ServerConstants;
import core.cache.def.impl.SceneryDefinition;
import core.plugin.Initializable;
import core.game.dialogue.DialogueInterpreter;
@ -8,7 +9,6 @@ import core.game.interaction.OptionHandler;
import core.game.node.Node;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.TeleportManager;
import core.game.node.entity.player.link.audio.Audio;
import core.game.node.scenery.Scenery;
import core.game.node.scenery.SceneryBuilder;
import core.game.system.task.Pulse;
@ -20,10 +20,11 @@ import core.plugin.ClassScanner;
import org.rs09.consts.Sounds;
import static core.api.ContentAPIKt.playAudio;
import static content.region.wilderness.handlers.WildernessGateHandlerKt.enterDeepWilderness;
/**
* Handles wilderness levers.
* @author 'Vexia
* @author 'Vexia, Player Name
* @version 1.0
*/
@Initializable
@ -279,7 +280,6 @@ public final class WildernessLeverPlugin extends OptionHandler {
public Location[] getLocations() {
return locations;
}
}
/**
@ -325,7 +325,14 @@ public final class WildernessLeverPlugin extends OptionHandler {
public boolean open(Object... args) {
lever = (LeverSets) args[0];
id = (int) args[1];
if (ServerConstants.ENHANCED_DEEP_WILDERNESS) {
enterDeepWilderness(player, (player) -> {
lever.pull(player, lever.getIndex(id), true);
return null;
}, "Pulling the lever will teleport you into the deep wilderness.");
} else {
interpreter.sendDialogue("Warning! Pulling the lever will teleport you deep into the wilderness.");
}
return true;
}

View file

@ -1,152 +0,0 @@
package content.region.wilderness.handlers
import content.region.wilderness.handlers.revenants.RevenantController
import content.region.wilderness.handlers.revenants.RevenantNPC
import content.region.wilderness.handlers.revenants.RevenantType
import core.api.*
import core.game.node.entity.Entity
import core.game.node.entity.combat.CombatStyle
import core.game.node.entity.combat.DeathTask
import core.game.node.entity.npc.NPC
import core.game.node.entity.npc.NPCBehavior
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.game.system.command.Privilege
import core.game.system.timer.PersistTimer
import core.game.system.timer.impl.Disease
import core.game.world.map.path.Pathfinder
import core.game.world.map.zone.impl.WildernessZone
import core.game.world.update.flag.context.Graphics
import core.tools.RandomFunction
import core.tools.colorize
import org.json.simple.JSONObject
object DeepWildyThreat {
@JvmStatic fun getThreat (player: Player) : Int {
return getOrStartTimer<DWThreatTimer>(player).ticksLeft
}
@JvmStatic fun adjustThreat (player: Player, amount: Int) {
val timer = getOrStartTimer<DWThreatTimer>(player)
timer.ticksLeft += amount
if (timer.ticksLeft >= 2500 && timer.lastMessage < 2000) {
sendMessage(player, colorize("%RYou sense a great wrath upon you."))
timer.lastMessage = 2000
} else if (timer.ticksLeft >= 1000 && timer.lastMessage < 1000) {
sendMessage(player, colorize("%RYou sense watchful eyes upon you."))
timer.lastMessage = 1000
} else if (timer.ticksLeft >= 500 && timer.lastMessage < 500) {
sendMessage(player, colorize("%RYou sense a dark presence."))
timer.lastMessage = 500
}
}
}
class DWThreatTimer : PersistTimer(1, "dw-threat"), Commands {
var ticksLeft = 0
var lastMessage = 0
var currentRev: NPC? = null
var forceSpawn = false
override fun run(entity: Entity): Boolean {
if (ticksLeft-- <= 0) return false
if (ticksLeft > 3000) ticksLeft = 3000
if (ticksLeft % 5 != 0) return true
if (entity !is Player) return false
if (!entity.skullManager.isWilderness) return true
val rollchance =
if (ticksLeft >= 2500) 10
else if (ticksLeft >= 2000) 200
else if (ticksLeft >= 1500) 400
else if (ticksLeft >= 1000) 800
else if (ticksLeft >= 500) 1500
else 2_000_000
if ((currentRev == null || DeathTask.isDead(currentRev) || !currentRev!!.isActive) && (forceSpawn || RandomFunction.roll(rollchance))) {
forceSpawn = false
val type = RevenantType.getClosestHigherOrEqual(entity.properties.currentCombatLevel)
val npc = NPC.create(type.ids.random(), entity.location)
npc.isRespawn = false
npc.behavior = RevGuardianBehavior()
npc.init()
npc.setAttribute("dw-threat-target", entity)
RevenantController.unregisterRevenant(npc as RevenantNPC, false)
currentRev = npc
} else if (currentRev != null && !currentRev!!.location.withinDistance(entity.location, 25) && currentRev!!.properties.teleportLocation == null) {
poofClear(currentRev!!)
currentRev = null
}
return true
}
override fun save(root: JSONObject, entity: Entity) {
root["threat-time-remaining"] = ticksLeft.toString()
root["threat-forceSpawn"] = (currentRev != null).toString()
}
override fun parse(root: JSONObject, entity: Entity) {
ticksLeft = root["threat-time-remaining"]?.toString()?.toIntOrNull() ?: 0
forceSpawn = root["threat-forceSpawn"]?.toString()?.toBoolean() ?: false
}
override fun defineCommands() {
define("dwthreat", Privilege.ADMIN, "", "") {player, _ ->
val timer = getOrStartTimer<DWThreatTimer>(player)
notify(player, "Current Threat: ${timer.ticksLeft}")
}
}
}
class RevGuardianBehavior : NPCBehavior() {
val deathMessages = arrayOf("Curses upon thee!", "Rot in blight!", "Suffer my wrath!", "Nevermore!", "May ye be undone!")
var chats = arrayOf("Leave this place!", "Suffer!", "Death to thee!", "Flee, coward!", "Leave my resting place!", "Let me rest in peace!", "Thou belongeth to me!")
override fun tick(self: NPC): Boolean {
val target = getAttribute<Player?>(self, "dw-threat-target", null) ?: return true
if (!target.isActive || DeathTask.isDead(target)) {
self.clear()
return true
}
if (target.properties.teleportLocation != null && self.properties.teleportLocation == null) {
if (WildernessZone.isInZone(target.properties.teleportLocation))
self.properties.teleportLocation = target.properties.teleportLocation
}
self.attack(target)
return true
}
override fun onCreation(self: NPC) {
Graphics.send(Graphics(86), self.location)
sendChat(self, chats.random())
}
override fun canBeAttackedBy(self: NPC, attacker: Entity, style: CombatStyle, shouldSendMessage: Boolean): Boolean {
val target = getAttribute<Player?>(self, "dw-threat-target", null)
if (attacker != target) {
if (shouldSendMessage && attacker is Player)
sendMessage(attacker, "This revenant is focused on someone else.")
return false
}
return super.canBeAttackedBy(self, attacker, style, shouldSendMessage)
}
override fun onDeathStarted(self: NPC, killer: Entity) {
val target = getAttribute<Player?>(self, "dw-threat-target", null) ?: return
sendChat(self, deathMessages.random())
val disease = getOrStartTimer<Disease>(target, 25)
disease.hitsLeft = 25
}
override fun onDropTableRolled(self: NPC, killer: Entity, drops: ArrayList<Item>) {
val target = getAttribute<Player?>(self, "dw-threat-target", null) ?: return
if (killer != target) drops.clear()
val timer = getOrStartTimer<DWThreatTimer>(target)
timer.ticksLeft = 0
}
override fun getPathfinderOverride(self: NPC): Pathfinder? {
return Pathfinder.SMART
}
}

View file

@ -52,6 +52,10 @@ public final class WildernessDitchPlugin extends OptionHandler {
player.faceLocation(node.getLocation());
Scenery ditch = (Scenery) node;
player.setAttribute("wildy_ditch", ditch);
/*
Comment out the annoying ditch warning until the doomsayer has been implemented so that players can disable it properly.
if(!player.isArtificial()) {
if (ditch.getRotation() % 2 == 0) {
if (player.getLocation().getY() <= node.getLocation().getY()) {
@ -65,6 +69,7 @@ public final class WildernessDitchPlugin extends OptionHandler {
}
}
}
*/
WildernessInterfacePlugin.handleDitch(player);
}

View file

@ -1,9 +1,10 @@
package content.region.wilderness.handlers
import content.region.wilderness.handlers.WildernessGateHandler.GateDialogue
import core.ServerConstants
import core.api.*
import core.game.interaction.*
import core.game.node.entity.player.Player
import core.game.node.scenery.Scenery
import core.game.node.Node
import core.game.global.action.DoorActionHandler
import core.game.dialogue.*
@ -17,15 +18,18 @@ class WildernessGateHandler : InteractionListener {
}
private fun handleGate(player: Player, node: Node) : Boolean {
if (player.location.y > 3890) {
val isEntering = !player.skullManager.isDeepWilderness()
if (isEntering)
openDialogue(player, GateDialogue(node.asScenery()))
else {
if (player.properties.combatPulse.isInCombat)
if (player.location.y > 3890 && ServerConstants.ENHANCED_DEEP_WILDERNESS) {
val isEntering = !player.skullManager.isDeepWilderness
if (isEntering) {
fun enter(player: Player) {
DoorActionHandler.handleAutowalkDoor(player, node.asScenery())
player.skullManager.isDeepWilderness = true
}
enterDeepWilderness(player, ::enter, "Beyond this gate you enter the deep wilderness!")
} else {
if (player.properties.combatPulse.isInCombat) {
sendMessage(player, "You cannot leave while you are under attack.")
else {
} else {
DoorActionHandler.handleAutowalkDoor(player, node.asScenery())
player.skullManager.isDeepWilderness = false
}
@ -35,20 +39,35 @@ class WildernessGateHandler : InteractionListener {
return true
}
class GateDialogue (val gate: Scenery) : DialogueFile() {
class GateDialogue(val callback: (Player) -> Unit, val firstLine: String) : DialogueFile() {
override fun handle (interfaceId: Int, buttonId: Int) {
when (stage) {
0 -> sendDialogueLines(player!!, "WARNING!", "Beyond this gate you enter the deep wilderness!", "Anyone will be able to attack you without consequence!", "You WILL NOT be able to leave during combat!").also { stage++ }
1 -> showTopics (
0 -> sendDialogueLines(player!!, "WARNING!", firstLine, "Anyone will be able to attack you without consequence!", "You WILL NOT be able to leave during combat!").also { stage++ }
1 -> sendDialogueLines(player!!, "While you are there, you will be skulled:","you will lose all your items if you die!","The skull will go away when you leave the deep wilderness.").also { stage++ }
2 -> sendDialogueLines(player!!, "If you are risking sufficient value, the skull will become red.","This increases your chances of receiving special loot from killing","revenants and the Chaos Elemental!").also { stage++ }
3 -> showTopics(
Topic(FacialExpression.NEUTRAL, "I wish to proceed.", 10, true),
Topic(FacialExpression.NEUTRAL, "I wish to proceed, and don't show this warning again.", 11, true),
Topic(FacialExpression.NEUTRAL, "Never mind.", END_DIALOGUE, true)
)
10 -> {
end()
DoorActionHandler.handleAutowalkDoor (player!!, gate)
player!!.skullManager.isDeepWilderness = true
callback(player!!)
}
11 -> {
player!!.setAttribute("/save:skip-deep-wilderness-warning", true)
end()
callback(player!!)
}
}
}
}
}
fun enterDeepWilderness(player: Player, callback: (Player) -> Unit, firstLine: String) {
if (player.getAttribute("/save:skip-deep-wilderness-warning",false)) {
callback(player)
} else {
openDialogue(player, GateDialogue(callback, firstLine))
}
}

View file

@ -333,6 +333,9 @@ class ServerConstants {
@JvmField
var RUNECRAFTING_FORMULA_REVISION = 581
@JvmField
var ENHANCED_DEEP_WILDERNESS = false
@JvmField
var STARTUP_MOMENT = Calendar.getInstance()
}

View file

@ -236,7 +236,7 @@ public final class SkullManager {
}
public void setDeepWilderness (boolean deepWildy) {
if(deepWildy) {
if(ServerConstants.ENHANCED_DEEP_WILDERNESS && deepWildy) {
updateDWSkullIcon();
} else {
removeDWSkullIcon();

View file

@ -165,6 +165,7 @@ object ServerConfigParser {
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()
ServerConstants.ENHANCED_DEEP_WILDERNESS = data.getBoolean("world.enhanced_deep_wilderness", false)
val logLevel = data.getString("server.log_level", "VERBOSE").uppercase()
ServerConstants.LOG_LEVEL = parseEnumEntry<LogLevel>(logLevel) ?: LogLevel.VERBOSE

View file

@ -2,7 +2,6 @@ package core.game.world.map.zone.impl;
import content.global.handlers.item.equipment.brawling_gloves.BrawlingGloves;
import content.global.skill.summoning.familiar.Familiar;
import content.region.wilderness.handlers.DeepWildyThreat;
import core.game.component.Component;
import core.game.interaction.Option;
import core.game.node.Node;
@ -23,6 +22,7 @@ import core.game.world.map.zone.ZoneBorders;
import core.game.world.map.zone.ZoneRestriction;
import core.game.world.repository.Repository;
import core.tools.RandomFunction;
import org.rs09.consts.Items;
import org.rs09.consts.NPCs;
@ -93,47 +93,34 @@ public final class WildernessZone extends MapZone {
private void rollWildernessExclusiveLoot(Entity e, Entity killer) {
if (!(killer instanceof Player)) return;
boolean isDeepWildy = ((Player) killer).getSkullManager().isDeepWilderness();
boolean isRevOrCele = e.asNpc().getName().contains("Revenant") || e.getId() == NPCs.CHAOS_ELEMENTAL_3200;
boolean isSufficientRisk = ((Player) killer).getAttribute("deepwild-value-risk", 0L) > SkullManager.DEEP_WILD_DROP_RISK_THRESHOLD;
boolean isValidTarget = e instanceof NPC && ((isDeepWildy && isSufficientRisk) || isRevOrCele);
if (isDeepWildy) {
DeepWildyThreat.adjustThreat((Player) killer, 50);
}
if (!isValidTarget) return;
if (!(e instanceof NPC)) return;
if (!(e.getId() == NPCs.CHAOS_ELEMENTAL_3200 || e.asNpc().getName().contains("Revenant"))) return;
int pvpGearRate = getNewDropRate(e.asNpc().getDefinition().getCombatLevel());
if (isDeepWildy && isRevOrCele)
boolean higherRate = ((Player) killer).getSkullManager().isDeepWilderness() && ((Player) killer).getAttribute("deepwild-value-risk", 0L) > SkullManager.DEEP_WILD_DROP_RISK_THRESHOLD;
if (higherRate) {
pvpGearRate /= 2;
int cEleGloveRate = isDeepWildy ? 50 : 150;
int normalGloveRate = isDeepWildy && isRevOrCele ? 100 : (int)((1.0/(1.0-Math.pow(1.0 - (1.0/(double)pvpGearRate), 16.0))) * 5.0 / 6.0);
}
int cEleGloveRate = 50;
int normalGloveRate = higherRate ? 100 : (int) ((1.0 / (1.0 - Math.pow(1.0 - (1.0 / (double) pvpGearRate), 16.0))) * 5.0 / 6.0);
if (RandomFunction.roll(e.getId() == NPCs.CHAOS_ELEMENTAL_3200 ? cEleGloveRate : normalGloveRate)) {
byte glove = (byte) RandomFunction.random(1, 14);
Item reward = new Item(BrawlingGloves.forIndicator(glove).getId());
GroundItemManager.create(reward, e.asNpc().getDropLocation(), killer.asPlayer());
Repository.sendNews(killer.getUsername() + " has received " + reward.getName().toLowerCase() + " from a " + e.asNpc().getName() + "!");
if (isDeepWildy)
DeepWildyThreat.adjustThreat((Player) killer, 750);
}
for (int j : PVP_GEAR) {
boolean chance = RandomFunction.roll(pvpGearRate);
if (chance) {
Item reward;
if (j == 13879 || j == 13883) { // checks if it's a javelin or throwing axe
if (j == Items.MORRIGANS_JAVELIN_13879 || j == Items.MORRIGANS_THROWING_AXE_13883) {
reward = new Item(j, RandomFunction.random(15, 50));
} else {
reward = new Item(j);
}
Repository.sendNews(killer.asPlayer().getUsername() + " has received a " + reward.getName() + " from a " + e.asNpc().getName() + "!");
GroundItemManager.create(reward, ((NPC) e).getDropLocation(), killer.asPlayer());
if (isDeepWildy)
DeepWildyThreat.adjustThreat((Player) killer, 3000);
Repository.sendNews(killer.asPlayer().getUsername() + " has received a " + reward.getName() + " from a " + e.asNpc().getName() + "!");
}
}
}
@ -156,8 +143,6 @@ public final class WildernessZone extends MapZone {
return super.interact(e, target, option);
}
@Override
public boolean enter(Entity e) {
if (e instanceof Player) {

View file

@ -104,6 +104,8 @@ force_halloween_randoms = false
force_christmas_randoms = false
#runecrafting formula revision (573 introduced probabilistic multiple runes, 581 extrapolated probabilistic runes past 99)
runecrafting_formula_revision = 581
#enable the enhanced deep wilderness, where the area past the members' fence applies a red skull that boosts brawler/pvp drop rates
enhanced_deep_wilderness = true
[paths]
#path to the data folder, which contains the cache subfolder and such