Deep wilderness threats now continue at next login if present when logging out

Decreased brawler gloves and PvP gear drop rate from ordinary deep wilderness NPCs (does not apply to revenants or chaos elemental)
Brawler gloves and PvP gear drops from ordinary deep wilderness NPCs now require at least 100k high alchemy risk (does not apply to revenants or chaos elemental)
This commit is contained in:
Avi Weinstock 2023-08-29 07:41:59 +00:00 committed by Ryan
parent 4276ed731d
commit a0435fb890
8 changed files with 159 additions and 21 deletions

View file

@ -14,6 +14,7 @@ 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
@ -45,6 +46,7 @@ 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
@ -61,7 +63,8 @@ class DWThreatTimer : PersistTimer(1, "dw-threat"), Commands {
else if (ticksLeft >= 500) 1500
else 2_000_000
if ((currentRev == null || DeathTask.isDead(currentRev) || !currentRev!!.isActive) && RandomFunction.roll(rollchance)) {
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
@ -80,10 +83,12 @@ class DWThreatTimer : PersistTimer(1, "dw-threat"), Commands {
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() {
@ -140,4 +145,8 @@ class RevGuardianBehavior : NPCBehavior() {
val timer = getOrStartTimer<DWThreatTimer>(target)
timer.ticksLeft = 0
}
override fun getPathfinderOverride(self: NPC): Pathfinder? {
return Pathfinder.SMART
}
}

View file

@ -4,6 +4,7 @@ import core.game.node.Node;
import core.game.node.entity.Entity;
import core.game.node.entity.impl.WalkingQueue;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.npc.NPCBehavior;
import core.game.node.entity.player.Player;
import core.game.system.task.Pulse;
import core.game.world.map.Direction;
@ -175,6 +176,11 @@ public abstract class MovementPulse extends Pulse {
if (pathfinder == null) {
if (mover instanceof Player) {
this.pathfinder = Pathfinder.SMART;
} else if (mover instanceof NPC) {
NPC npc = (NPC)mover;
NPCBehavior behavior = npc.behavior;
Pathfinder pf = behavior != null ? behavior.getPathfinderOverride(npc) : null;
this.pathfinder = pf != null ? pf : Pathfinder.DUMB;
} else {
this.pathfinder = Pathfinder.DUMB;
}

View file

@ -33,7 +33,7 @@ public final class Properties {
/**
* The entity's combat pulse.
*/
private final CombatPulse combatPulse;
private CombatPulse combatPulse;
/**
* If the entity is retaliating.
@ -332,6 +332,14 @@ public final class Properties {
return combatPulse;
}
/**
* Sets the combatPulse.
* @return The void.
*/
public void setCombatPulse(CombatPulse combatPulse) {
this.combatPulse = (CombatPulse)combatPulse;
}
/**
* Gets the retaliating.
* @return The retaliating.

View file

@ -8,6 +8,7 @@ import core.game.interaction.MovementPulse;
import core.game.node.entity.Entity;
import core.game.node.entity.combat.BattleState;
import core.game.node.entity.combat.spell.CombatSpell;
import core.game.node.entity.combat.CombatPulse;
import core.game.node.entity.combat.CombatStyle;
import core.game.node.entity.combat.spell.DefaultCombatSpell;
import core.game.node.entity.combat.equipment.WeaponInterface;
@ -187,6 +188,7 @@ public class NPC extends Entity {
*/
public static NPC create(int id, Location location, Direction direction, Object... objects) {
NPC n = AbstractNPC.forId(id);
if (n != null) {
n = ((AbstractNPC) n).construct(id, location, objects);
}
@ -229,6 +231,8 @@ public class NPC extends Entity {
}
}
behavior.onCreation(this);
// FIXME: hack around MovementPulse's constructor getting run while behavior is null when behavior is set between NPC constructor and init.
getProperties().setCombatPulse(new CombatPulse(this));
}
@Override

View file

@ -9,6 +9,7 @@ import core.game.node.entity.combat.BattleState
import core.game.node.entity.combat.CombatStyle
import core.game.node.entity.combat.CombatSwingHandler
import core.game.world.map.path.ClipMaskSupplier
import core.game.world.map.path.Pathfinder
open class NPCBehavior(vararg val ids: Int = intArrayOf()) : ContentInterface {
companion object {
@ -134,6 +135,13 @@ open class NPCBehavior(vararg val ids: Int = intArrayOf()) : ContentInterface {
*/
open fun getSwingHandlerOverride(self: NPC, original: CombatSwingHandler) : CombatSwingHandler {return original}
/**
* Called by MovementPulse to determine if a non-default Pathfinder should be used (e.g. whether this npc should intelligently walk around obstacles)
*/
open fun getPathfinderOverride(self: NPC): Pathfinder? {
return null
}
/**
* Called by pathfinding code to determine the clipping mask supplier this NPC should use. You can use this to ignore water, etc.
*/

View file

@ -1,7 +1,11 @@
package core.game.node.entity.player.link;
import core.game.container.Container;
import core.game.container.ContainerEvent;
import core.game.container.ContainerListener;
import core.game.node.entity.Entity;
import core.game.node.entity.player.Player;
import core.game.node.item.Item;
import core.ServerConstants;
import static core.api.ContentAPIKt.*;
@ -17,6 +21,35 @@ import static core.game.world.map.zone.impl.WildernessZone.WILDERNESS_PROT_ATTR;
* @author Emperor
*/
public final class SkullManager {
public enum SkullIcon {
NONE(-1),
WHITE(0),
RED(1),
BH_RED5(2),
BH_BLUE4(3),
BH_GREEN3(4),
BH_GREY2(5),
BH_BROWN1(6),
SCREAM(7);
public final int id;
SkullIcon(int id) {
this.id = id;
}
public static SkullIcon forId(int id) {
switch(id) {
case 0: return SkullIcon.WHITE;
case 1: return SkullIcon.RED;
case 2: return SkullIcon.BH_RED5;
case 3: return SkullIcon.BH_BLUE4;
case 4: return SkullIcon.BH_GREEN3;
case 5: return SkullIcon.BH_GREY2;
case 6: return SkullIcon.BH_BROWN1;
case 7: return SkullIcon.SCREAM;
default: return SkullIcon.NONE;
}
}
}
/**
* The player instance.
@ -203,11 +236,70 @@ public final class SkullManager {
}
public void setDeepWilderness (boolean deepWildy) {
setSkullIcon(deepWildy ? 1 : skulled ? 0 : -1);
if(deepWildy) {
updateDWSkullIcon();
} else {
removeDWSkullIcon();
}
setSkullCheckDisabled(deepWildy);
deepWilderness = deepWildy;
}
public static final long DEEP_WILD_DROP_RISK_THRESHOLD = 100000;
public void updateDWSkullIcon() {
if (player.getAttribute("deepwild-value-listener") == null) {
ContainerListener listener = new ContainerListener() {
@Override
public void update(Container c, ContainerEvent event) {
refresh(c);
}
@Override
public void refresh(Container c) {
updateDWSkullIcon();
}
};
player.setAttribute("deepwild-value-listener", listener);
player.getInventory().getListeners().add(listener);
player.getEquipment().getListeners().add(listener);
}
long value = 0;
long maxValue = 0;
for (Item item : player.getInventory().toArray()) {
if (item != null) {
long alchValue = item.getAlchemyValue();
value += alchValue;
maxValue = Math.max(maxValue, alchValue);
}
}
for (Item item : player.getEquipment().toArray()) {
if (item != null) {
long alchValue = item.getAlchemyValue();
value += alchValue;
maxValue = Math.max(maxValue, alchValue);
}
}
// Act as if protect item is always active when calculating risk
value -= maxValue;
player.setAttribute("deepwild-value-risk", value);
SkullIcon skull = SkullIcon.BH_BROWN1;
if (value >= DEEP_WILD_DROP_RISK_THRESHOLD) {
skull = SkullIcon.RED;
}
setSkullIcon(skull.id);
}
public void removeDWSkullIcon() {
setSkullIcon(skulled ? 0 : -1);
ContainerListener listener = player.getAttribute("deepwild-value-listener");
if (listener != null) {
player.getInventory().getListeners().remove(listener);
player.getEquipment().getListeners().remove(listener);
}
player.removeAttribute("deepwild-value-listener");
player.removeAttribute("deepwild-value-risk");
}
/**
* Sets the skulled.
* @param skulled The skulled to set.

View file

@ -106,6 +106,14 @@ public class Item extends Node{
return value * getAmount();
}
public long getAlchemyValue() {
long value = 1;
if (definition.getAlchemyValue(true) > value) {
value = definition.getAlchemyValue(true);
}
return value * getAmount();
}
/**
* Gets a copy of the item.
* @return The item copy.

View file

@ -1,9 +1,10 @@
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 content.global.handlers.item.equipment.brawling_gloves.BrawlingGloves;
import core.game.node.Node;
import core.game.node.entity.Entity;
import core.game.node.entity.combat.CombatStyle;
@ -12,17 +13,17 @@ import core.game.node.entity.npc.agg.AggressiveBehavior;
import core.game.node.entity.npc.agg.AggressiveHandler;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.info.Rights;
import content.global.skill.summoning.familiar.Familiar;
import core.game.node.entity.player.link.SkullManager;
import core.game.node.item.GroundItemManager;
import core.game.node.item.Item;
import core.game.world.GameWorld;
import core.game.world.map.Location;
import core.game.world.map.zone.MapZone;
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.NPCs;
import core.game.world.GameWorld;
import core.game.world.repository.Repository;
/**
@ -94,7 +95,9 @@ public final class WildernessZone extends MapZone {
if (!(killer instanceof Player)) return;
boolean isDeepWildy = ((Player) killer).getSkullManager().isDeepWilderness();
boolean isValidTarget = e instanceof NPC && (isDeepWildy || e.asNpc().getName().contains("Revenant") || e.getId() == NPCs.CHAOS_ELEMENTAL_3200);
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);
@ -102,13 +105,13 @@ public final class WildernessZone extends MapZone {
if (!isValidTarget) return;
int cEleGloveRate = isDeepWildy ? 50 : 150;
int normalGloveRate = isDeepWildy ? 100 : 150;
int pvpGearRate = getNewDropRate(e.asNpc().getDefinition().getCombatLevel());
if (isDeepWildy)
if (isDeepWildy && isRevOrCele)
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);
if (RandomFunction.roll(e.getId() == NPCs.CHAOS_ELEMENTAL_3200 ? cEleGloveRate : normalGloveRate)) {
byte glove = (byte) RandomFunction.random(1, 13);
Item reward = new Item(BrawlingGloves.forIndicator(glove).getId());