Deep wilderness balancing

Added a threat system to the deep wilderness that increases as a player kills NPCs to add balance to lucrative drop rates of PvP items in the deep wilderness
Threat decreases with time
Threat only increases in the deep wilderness
As threat increases, the risk of encountering hostile wilderness events increases
Hostile wilderness events may spawn anywhere in the wilderness
Improved the safety checks on items dropped in PvP
Untradeable items dropped in PvP will now yield an amount of coins proportional to the item's high alchemy value, and delete the item. This amount is halved if the killer doesn't have the level for high alchemy. An additional 250 gp is deducted (after halving, if applicable) from all items to account for the hypothetical cost of a nature rune
Added admin command to view threat level ::dwthreat
This commit is contained in:
Ceikry 2023-08-09 04:02:18 +00:00 committed by Ryan
parent 8b6678d4ee
commit 0f908a1544
4 changed files with 131 additions and 3 deletions

View file

@ -0,0 +1,90 @@
package content.region.wilderness.handlers
import content.region.wilderness.handlers.revenants.RevenantType
import core.api.*
import core.game.node.entity.Entity
import core.game.node.entity.combat.DeathTask
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.system.command.Privilege
import core.game.system.timer.PersistTimer
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 chats = arrayOf("Leave this place!", "Suffer!", "Death to you!", "Flee, coward!", "Leave my resting place!", "Let me rest in peace!", "You belong to me!")
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) && RandomFunction.roll(rollchance)) {
val type = RevenantType.getClosestHigherOrEqual(entity.properties.currentCombatLevel)
val npc = NPC.create(type.ids.random(), entity.location)
npc.isRespawn = false
npc.init()
npc.attack(entity)
Graphics.send(Graphics(86), npc.location)
ticksLeft -= 500
sendChat(npc, chats.random())
currentRev = npc
} else if (currentRev != null && !currentRev!!.location.withinDistance(entity.location, 25)) {
poofClear(currentRev!!)
currentRev = null
}
return true
}
override fun save(root: JSONObject, entity: Entity) {
root["threat-time-remaining"] = ticksLeft
}
override fun parse(root: JSONObject, entity: Entity) {
ticksLeft = root.getOrDefault("threat-time-remaining", 3000) as? Int ?: 3000
}
override fun defineCommands() {
define("dwthreat", Privilege.ADMIN, "", "") {player, _ ->
val timer = getOrStartTimer<DWThreatTimer>(player)
notify(player, "Current Threat: ${timer.ticksLeft}")
}
}
}

View file

@ -1,5 +1,7 @@
package content.region.wilderness.handlers.revenants; package content.region.wilderness.handlers.revenants;
import core.cache.def.impl.NPCDefinition;
/** /**
* A revenant type. * A revenant type.
* @author Vexia * @author Vexia
@ -58,4 +60,12 @@ public enum RevenantType {
public int getMaxHit() { public int getMaxHit() {
return maxHit; return maxHit;
} }
public static RevenantType getClosestHigherOrEqual (int combatLevel) {
for (RevenantType t : values()) {
NPCDefinition def = NPCDefinition.forId(t.ids[0]);
if (def.getCombatLevel() >= combatLevel) return t;
}
return null;
}
} }

View file

@ -2,6 +2,7 @@ package core.game.node.entity.player;
import content.global.handlers.item.equipment.special.SalamanderSwingHandler; import content.global.handlers.item.equipment.special.SalamanderSwingHandler;
import content.global.skill.runecrafting.PouchManager; import content.global.skill.runecrafting.PouchManager;
import core.api.EquipmentSlot;
import core.game.component.Component; import core.game.component.Component;
import core.game.container.Container; import core.game.container.Container;
import core.game.container.ContainerType; import core.game.container.ContainerType;
@ -34,6 +35,7 @@ import core.game.node.entity.player.link.skillertasks.SkillerTasks;
import core.game.node.entity.skill.Skills; import core.game.node.entity.skill.Skills;
import content.global.skill.construction.HouseManager; import content.global.skill.construction.HouseManager;
import content.global.skill.summoning.familiar.FamiliarManager; import content.global.skill.summoning.familiar.FamiliarManager;
import core.game.node.item.GroundItem;
import core.game.node.item.GroundItemManager; import core.game.node.item.GroundItemManager;
import core.game.node.item.Item; import core.game.node.item.Item;
import core.game.system.communication.CommunicationInfo; import core.game.system.communication.CommunicationInfo;
@ -613,6 +615,11 @@ public class Player extends Entity {
if (this.isArtificial() && killer instanceof NPC) { if (this.isArtificial() && killer instanceof NPC) {
return; return;
} }
if (killer instanceof Player && getWorldTicks() - killer.getAttribute("/save:last-murder-news", 0) >= 500) {
Item wep = getItemFromEquipment((Player) killer, EquipmentSlot.WEAPON);
sendNews(killer.getName() + " has murdered " + getName() + " with " + (wep == null ? "their fists." : (StringUtils.isPlusN(wep.getName()) ? "an " : "a ") + wep.getName()));
killer.setAttribute("/save:last-murder-news", getWorldTicks());
}
getPacketDispatch().sendMessage("Oh dear, you are dead!"); getPacketDispatch().sendMessage("Oh dear, you are dead!");
incrementAttribute("/save:"+STATS_BASE+":"+STATS_DEATHS); incrementAttribute("/save:"+STATS_BASE+":"+STATS_DEATHS);
@ -653,7 +660,9 @@ public class Player extends Entity {
g.initialize(this, location, Arrays.stream(c[1].toArray()).filter(Objects::nonNull).toArray(Item[]::new)); //note: the amount of code required to filter nulls from an array in Java is atrocious. g.initialize(this, location, Arrays.stream(c[1].toArray()).filter(Objects::nonNull).toArray(Item[]::new)); //note: the amount of code required to filter nulls from an array in Java is atrocious.
} else { } else {
StringBuilder itemsLost = new StringBuilder(); StringBuilder itemsLost = new StringBuilder();
int coins = 0;
for (Item item : c[1].toArray()) { for (Item item : c[1].toArray()) {
boolean stayPrivate = false;
if (item == null) continue; if (item == null) continue;
if (killer instanceof Player) if (killer instanceof Player)
itemsLost.append(getItemName(item.getId())).append("(").append(item.getAmount()).append("), "); itemsLost.append(getItemName(item.getId())).append("(").append(item.getAmount()).append("), ");
@ -661,8 +670,20 @@ public class Player extends Entity {
continue; continue;
if (GraveController.shouldRelease(item.getId())) if (GraveController.shouldRelease(item.getId()))
continue; continue;
if (!item.getDefinition().isTradeable()) {
if (killer instanceof Player) {
int value = item.getDefinition().getAlchemyValue(true);
if (getStatLevel(killer, Skills.MAGIC) < 55) value /= 2;
coins += Math.max(0, value - 250);
continue;
} else stayPrivate = true;
}
item = GraveController.checkTransform(item); item = GraveController.checkTransform(item);
GroundItemManager.create(item, location, killer instanceof Player ? (Player) killer : this); GroundItem gi = GroundItemManager.create(item, location, killer instanceof Player ? (Player) killer : this);
gi.setRemainPrivate(stayPrivate);
}
if (coins > 0) {
GroundItemManager.create(new Item(Items.COINS_995, coins), location, (Player) killer);
} }
if (killer instanceof Player) if (killer instanceof Player)
PlayerMonitor.log((Player) killer, LogType.PK, "Killed " + name + ", who dropped: " + itemsLost); PlayerMonitor.log((Player) killer, LogType.PK, "Killed " + name + ", who dropped: " + itemsLost);

View file

@ -1,5 +1,6 @@
package core.game.world.map.zone.impl; package core.game.world.map.zone.impl;
import content.region.wilderness.handlers.DeepWildyThreat;
import core.game.component.Component; import core.game.component.Component;
import core.game.interaction.Option; import core.game.interaction.Option;
import content.global.handlers.item.equipment.brawling_gloves.BrawlingGloves; import content.global.handlers.item.equipment.brawling_gloves.BrawlingGloves;
@ -95,6 +96,10 @@ public final class WildernessZone extends MapZone {
boolean isDeepWildy = ((Player) killer).getSkullManager().isDeepWilderness(); boolean isDeepWildy = ((Player) killer).getSkullManager().isDeepWilderness();
boolean isValidTarget = e instanceof NPC && (isDeepWildy || e.asNpc().getName().contains("Revenant") || e.getId() == NPCs.CHAOS_ELEMENTAL_3200); boolean isValidTarget = e instanceof NPC && (isDeepWildy || e.asNpc().getName().contains("Revenant") || e.getId() == NPCs.CHAOS_ELEMENTAL_3200);
if (isDeepWildy) {
DeepWildyThreat.adjustThreat((Player) killer, 250);
}
if (!isValidTarget) return; if (!isValidTarget) return;
int cEleGloveRate = isDeepWildy ? 50 : 150; int cEleGloveRate = isDeepWildy ? 50 : 150;
@ -109,6 +114,7 @@ public final class WildernessZone extends MapZone {
Item reward = new Item(BrawlingGloves.forIndicator(glove).getId()); Item reward = new Item(BrawlingGloves.forIndicator(glove).getId());
GroundItemManager.create(reward, e.asNpc().getDropLocation(), killer.asPlayer()); GroundItemManager.create(reward, e.asNpc().getDropLocation(), killer.asPlayer());
Repository.sendNews(killer.getUsername() + " has received " + reward.getName().toLowerCase() + " from a " + e.asNpc().getName() + "!"); Repository.sendNews(killer.getUsername() + " has received " + reward.getName().toLowerCase() + " from a " + e.asNpc().getName() + "!");
DeepWildyThreat.adjustThreat((Player) killer, 200);
} }
for (int j : PVP_GEAR) { for (int j : PVP_GEAR) {
@ -122,6 +128,7 @@ public final class WildernessZone extends MapZone {
} }
Repository.sendNews(killer.asPlayer().getUsername() + " has received a " + reward.getName() + " from a " + e.asNpc().getName() + "!"); Repository.sendNews(killer.asPlayer().getUsername() + " has received a " + reward.getName() + " from a " + e.asNpc().getName() + "!");
GroundItemManager.create(reward, ((NPC) e).getDropLocation(), killer.asPlayer()); GroundItemManager.create(reward, ((NPC) e).getDropLocation(), killer.asPlayer());
DeepWildyThreat.adjustThreat((Player) killer, 1000);
} }
} }
} }