Made familiar code more testable

Fixed an issue with the bloated leech "blood drain" ability
This commit is contained in:
Ceikry 2023-07-31 15:08:47 +00:00
parent ee79b0750c
commit 105f7d5b86
6 changed files with 168 additions and 26 deletions

View file

@ -23,7 +23,7 @@ class RandomEventManager(val player: Player? = null) : LoginListener, EventHook<
override fun login(player: Player) { override fun login(player: Player) {
if(player.isArtificial) return if(player.isArtificial) return
val instance = content.global.ame.RandomEventManager(player) val instance = RandomEventManager(player)
player.hook(Event.Tick, instance) player.hook(Event.Tick, instance)
setAttribute(player, "random-manager", instance) setAttribute(player, "random-manager", instance)
instance.rollNextSpawn() instance.rollNextSpawn()
@ -66,7 +66,7 @@ class RandomEventManager(val player: Player? = null) : LoginListener, EventHook<
} }
private fun rollNextSpawn() { private fun rollNextSpawn() {
nextSpawn = GameWorld.ticks + RandomFunction.random(content.global.ame.RandomEventManager.Companion.MIN_DELAY_TICKS, content.global.ame.RandomEventManager.Companion.MAX_DELAY_TICKS) nextSpawn = GameWorld.ticks + RandomFunction.random(MIN_DELAY_TICKS, MAX_DELAY_TICKS)
} }
override fun defineCommands() { override fun defineCommands() {
@ -77,16 +77,16 @@ class RandomEventManager(val player: Player? = null) : LoginListener, EventHook<
if (target == null) if (target == null)
reject(player, "Unable to find player $username!") reject(player, "Unable to find player $username!")
content.global.ame.RandomEventManager.Companion.getInstance(target!!)?.fireEvent() getInstance(target!!)?.fireEvent()
} }
} }
companion object { companion object {
const val AVG_DELAY_TICKS = 6000 // 60 minutes var AVG_DELAY_TICKS = 6000 // 60 minutes
const val MIN_DELAY_TICKS = content.global.ame.RandomEventManager.Companion.AVG_DELAY_TICKS / 2 var MIN_DELAY_TICKS = AVG_DELAY_TICKS / 2
const val MAX_DELAY_TICKS = content.global.ame.RandomEventManager.Companion.MIN_DELAY_TICKS + content.global.ame.RandomEventManager.Companion.AVG_DELAY_TICKS // window of 60 min centered on 60 min (30 to 90 min) var MAX_DELAY_TICKS = MIN_DELAY_TICKS + AVG_DELAY_TICKS // window of 60 min centered on 60 min (30 to 90 min)
@JvmStatic fun getInstance(player: Player): content.global.ame.RandomEventManager? @JvmStatic fun getInstance(player: Player): RandomEventManager?
{ {
return getAttribute(player, "random-manager", null) return getAttribute(player, "random-manager", null)
} }

View file

@ -40,13 +40,17 @@ public class BloatedLeechNPC extends Familiar {
@Override @Override
protected boolean specialMove(FamiliarSpecial special) { protected boolean specialMove(FamiliarSpecial special) {
curePoison(owner); curePoison(owner);
removeTimer(owner, "disease");
for (int i = 0; i < Skills.SKILL_NAME.length; i++) { for (int i = 0; i < Skills.SKILL_NAME.length; i++) {
if (owner.getSkills().getLevel(i) < owner.getSkills().getStaticLevel(i)) { if (owner.getSkills().getLevel(i) < owner.getSkills().getStaticLevel(i)) {
owner.getSkills().setLevel(i, owner.getSkills().getStaticLevel(i)); owner.getSkills().updateLevel(
i,
(int) Math.ceil(owner.getSkills().getStaticLevel(i) * 0.2),
owner.getSkills().getStaticLevel(i)
);
} }
} }
owner.getSkills().rechargePrayerPoints(); owner.getImpactHandler().manualHit(owner, RandomFunction.random(1, 5), HitsplatType.NORMAL);
getImpactHandler().manualHit(owner, RandomFunction.random(2), HitsplatType.NORMAL);
return true; return true;
} }

View file

@ -347,26 +347,26 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
* Checks if the familiar can execute its special move and does so if able. * Checks if the familiar can execute its special move and does so if able.
* @param special The familiar special object. * @param special The familiar special object.
*/ */
public void executeSpecialMove(FamiliarSpecial special) { public boolean executeSpecialMove(FamiliarSpecial special) {
if (special.getNode() == this) { if (special.getNode() == this) {
return; return false;
} }
if (specialCost > specialPoints) { if (specialCost > specialPoints) {
owner.getPacketDispatch().sendMessage("Your familiar does not have enough special move points left."); owner.getPacketDispatch().sendMessage("Your familiar does not have enough special move points left.");
return; return false;
} }
SummoningScroll scroll = SummoningScroll.forPouch(pouchId); SummoningScroll scroll = SummoningScroll.forPouch(pouchId);
if (scroll == null) { if (scroll == null) {
owner.getPacketDispatch().sendMessage("Invalid scroll for pouch " + pouchId + " - report!"); owner.getPacketDispatch().sendMessage("Invalid scroll for pouch " + pouchId + " - report!");
return; return false;
} }
if (!owner.getInventory().contains(scroll.getItemId(), 1)) { if (!owner.getInventory().contains(scroll.getItemId(), 1)) {
owner.getPacketDispatch().sendMessage("You do not have enough scrolls left to do this special move."); owner.getPacketDispatch().sendMessage("You do not have enough scrolls left to do this special move.");
return; return false;
} }
if (owner.getLocation().getDistance(getLocation()) > 15) { if (owner.getLocation().getDistance(getLocation()) > 15) {
owner.getPacketDispatch().sendMessage("Your familiar is too far away to use that scroll, or it cannot see you."); owner.getPacketDispatch().sendMessage("Your familiar is too far away to use that scroll, or it cannot see you.");
return; return false;
} }
if (specialMove(special)) { if (specialMove(special)) {
setAttribute("special-delay", GameWorld.getTicks() + 3); setAttribute("special-delay", GameWorld.getTicks() + 3);
@ -376,6 +376,7 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
updateSpecialPoints(specialCost); updateSpecialPoints(specialCost);
owner.getSkills().addExperience(Skills.SUMMONING, scroll.getExperience(), true); owner.getSkills().addExperience(Skills.SUMMONING, scroll.getExperience(), true);
} }
return true;
} }
/** /**

View file

@ -1,3 +1,4 @@
import content.global.ame.RandomEventManager
import core.cache.Cache import core.cache.Cache
import core.cache.crypto.ISAACCipher import core.cache.crypto.ISAACCipher
import core.cache.crypto.ISAACPair import core.cache.crypto.ISAACPair
@ -12,13 +13,18 @@ import org.rs09.consts.Items
import core.ServerConstants import core.ServerConstants
import core.api.log import core.api.log
import core.game.node.Node import core.game.node.Node
import core.game.node.entity.combat.equipment.WeaponInterface
import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPC
import core.game.node.entity.skill.SkillBonus
import core.game.node.item.GroundItem import core.game.node.item.GroundItem
import core.game.shops.Shop import core.game.shops.Shop
import core.game.shops.ShopItem import core.game.shops.ShopItem
import core.tools.SystemLogger import core.tools.SystemLogger
import core.game.system.config.ConfigParser import core.game.system.config.ConfigParser
import core.game.system.config.ServerConfigParser import core.game.system.config.ServerConfigParser
import core.game.system.timer.TimerRegistry
import core.game.system.timer.impl.Disease
import core.game.system.timer.impl.Poison
import core.game.world.GameWorld import core.game.world.GameWorld
import core.game.world.map.Location import core.game.world.map.Location
import core.game.world.map.RegionManager import core.game.world.map.RegionManager
@ -70,9 +76,15 @@ object TestUtils {
Cache.init(this::class.java.getResource("cache").path.toString()) Cache.init(this::class.java.getResource("cache").path.toString())
ConfigParser().prePlugin() ConfigParser().prePlugin()
ConfigParser().postPlugin() ConfigParser().postPlugin()
registerTimers()
} }
} }
fun registerTimers() { //allow timers to be registered for use by tests
TimerRegistry.registerTimer(Poison())
TimerRegistry.registerTimer(Disease())
}
fun loadFile(path: String) : URI? { fun loadFile(path: String) : URI? {
return this::class.java.getResource(path)?.toURI() return this::class.java.getResource(path)?.toURI()
} }
@ -99,7 +111,10 @@ class MockPlayer(name: String) : Player(PlayerDetails(name)), AutoCloseable {
var hasInit = false var hasInit = false
init { init {
this.details.session = MockSession() this.details.session = MockSession()
this.location = ServerConstants.HOME_LOCATION
this.properties.attackStyle = WeaponInterface.AttackStyle(0, WeaponInterface.BONUS_CRUSH)
init() init()
this.setAttribute("tutorial:complete", true)
} }
override fun update() { override fun update() {

View file

@ -1,6 +1,7 @@
package content package content
import TestUtils import TestUtils
import content.global.ame.RandomEventManager
import core.game.node.item.Item import core.game.node.item.Item
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -9,13 +10,13 @@ import content.global.ame.RandomEventNPC
import core.game.world.GameWorld import core.game.world.GameWorld
class RandomEventManagerTests { class RandomEventManagerTests {
companion object {init { init {
TestUtils.preTestSetup() TestUtils.preTestSetup()
}} }
@Test fun loginShouldEnableManager() { @Test fun loginShouldEnableManager() {
val p = TestUtils.getMockPlayer("Bill") val p = TestUtils.getMockPlayer("Bill")
content.global.ame.RandomEventManager().login(p) RandomEventManager().login(p)
val manager = content.global.ame.RandomEventManager.getInstance(p) val manager = content.global.ame.RandomEventManager.getInstance(p)
Assertions.assertNotNull(manager) Assertions.assertNotNull(manager)
Assertions.assertEquals(true, manager!!.enabled) Assertions.assertEquals(true, manager!!.enabled)
@ -23,7 +24,7 @@ class RandomEventManagerTests {
@Test fun loginShouldSetNextSpawn() { @Test fun loginShouldSetNextSpawn() {
val p = TestUtils.getMockPlayer("Bill") val p = TestUtils.getMockPlayer("Bill")
content.global.ame.RandomEventManager().login(p) RandomEventManager().login(p)
val manager = content.global.ame.RandomEventManager.getInstance(p) val manager = content.global.ame.RandomEventManager.getInstance(p)
Assertions.assertNotNull(manager) Assertions.assertNotNull(manager)
Assertions.assertEquals(true, manager!!.nextSpawn > GameWorld.ticks) Assertions.assertEquals(true, manager!!.nextSpawn > GameWorld.ticks)
@ -32,15 +33,19 @@ class RandomEventManagerTests {
@Test fun shouldSpawnRandomEventWithinMAXTICKSGivenNoRestrictions() { @Test fun shouldSpawnRandomEventWithinMAXTICKSGivenNoRestrictions() {
val p = TestUtils.getMockPlayer("Bill") val p = TestUtils.getMockPlayer("Bill")
p.setAttribute("tutorial:complete", true) //tutorial has to be complete to spawn randoms p.setAttribute("tutorial:complete", true) //tutorial has to be complete to spawn randoms
content.global.ame.RandomEventManager().login(p) RandomEventManager.MIN_DELAY_TICKS = 10
TestUtils.advanceTicks(content.global.ame.RandomEventManager.MAX_DELAY_TICKS + 5) RandomEventManager.MAX_DELAY_TICKS = 20
RandomEventManager().login(p)
TestUtils.advanceTicks(RandomEventManager.MAX_DELAY_TICKS + 5)
RandomEventManager.MIN_DELAY_TICKS = 3000
RandomEventManager.MAX_DELAY_TICKS = 9000
Assertions.assertNotNull(p.getAttribute("re-npc", null)) Assertions.assertNotNull(p.getAttribute("re-npc", null))
} }
@Test fun teleportAndNotePunishmentShouldNotAffectAlreadyNotedItems() { @Test fun teleportAndNotePunishmentShouldNotAffectAlreadyNotedItems() {
val p = TestUtils.getMockPlayer("Shitforbrains") val p = TestUtils.getMockPlayer("Shitforbrains")
p.setAttribute("tutorial:complete", true) p.setAttribute("tutorial:complete", true)
content.global.ame.RandomEventManager().login(p) RandomEventManager().login(p)
p.inventory.add(Item(Items.RAW_SHARK_384, 1000)) p.inventory.add(Item(Items.RAW_SHARK_384, 1000))
content.global.ame.RandomEventManager.getInstance(p)?.fireEvent() content.global.ame.RandomEventManager.getInstance(p)?.fireEvent()
@ -52,7 +57,7 @@ class RandomEventManagerTests {
@Test fun teleportAndNotePunishmentShouldNoteNotableUnnotedItems() { @Test fun teleportAndNotePunishmentShouldNoteNotableUnnotedItems() {
val p = TestUtils.getMockPlayer("shitforbrains2") val p = TestUtils.getMockPlayer("shitforbrains2")
p.setAttribute("tutorial:complete", true) p.setAttribute("tutorial:complete", true)
content.global.ame.RandomEventManager().login(p) RandomEventManager().login(p)
p.inventory.add(Item(4151, 5)) p.inventory.add(Item(4151, 5))
content.global.ame.RandomEventManager.getInstance(p)?.fireEvent() content.global.ame.RandomEventManager.getInstance(p)?.fireEvent()
@ -65,7 +70,7 @@ class RandomEventManagerTests {
@Test fun teleportAndNotePunishmentShouldNotAffectUnnotableItems() { @Test fun teleportAndNotePunishmentShouldNotAffectUnnotableItems() {
val p = TestUtils.getMockPlayer("shitforbrains3") val p = TestUtils.getMockPlayer("shitforbrains3")
p.setAttribute("tutorial:complete", true) p.setAttribute("tutorial:complete", true)
content.global.ame.RandomEventManager().login(p) RandomEventManager().login(p)
p.inventory.add(Item(Items.AIR_RUNE_556, 30)) p.inventory.add(Item(Items.AIR_RUNE_556, 30))
content.global.ame.RandomEventManager.getInstance(p)?.fireEvent() content.global.ame.RandomEventManager.getInstance(p)?.fireEvent()

View file

@ -0,0 +1,117 @@
package content.familiar.special
import TestUtils
import content.global.skill.summoning.familiar.BloatedLeechNPC
import content.global.skill.summoning.familiar.FamiliarSpecial
import core.ServerConstants
import core.api.*
import core.game.node.entity.skill.Skills
import core.game.system.timer.TimerRegistry
import core.game.system.timer.impl.Disease
import core.game.system.timer.impl.Poison
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.rs09.consts.Items
import org.rs09.consts.NPCs
class BloodDrainTests {
init {
TestUtils.preTestSetup()
}
@Test fun bloodDrainShouldNotRestorePrayer() {
TestUtils.getMockPlayer("bloodDrainPrayer").use { p ->
p.skills.setStaticLevel(Skills.PRAYER, 99)
p.skills.prayerPoints = 5.0
addItem(p, Items.BLOOD_DRAIN_SCROLL_12444)
val npc = BloatedLeechNPC(p, NPCs.BLOATED_LEECH_6843)
npc.location = ServerConstants.HOME_LOCATION
Assertions.assertEquals(true, npc.executeSpecialMove(FamiliarSpecial(p)))
Assertions.assertEquals(5.0, p.skills.prayerPoints)
}
}
@Test fun bloodDrainShouldDamageOwner() {
TestUtils.getMockPlayer("bloodDrainHealth").use {p ->
addItem(p, Items.BLOOD_DRAIN_SCROLL_12444)
val npc = BloatedLeechNPC(p, NPCs.BLOATED_LEECH_6843)
npc.location = ServerConstants.HOME_LOCATION
val npcBaseline = npc.skills.lifepoints
val ownerBaseline = p.skills.lifepoints
Assertions.assertEquals(true, npc.executeSpecialMove(FamiliarSpecial(p)))
TestUtils.advanceTicks(1, false)
Assertions.assertEquals(npcBaseline, npc.skills.lifepoints)
Assertions.assertEquals(true, p.skills.lifepoints < ownerBaseline)
}
}
@Test fun bloodDrainShouldNotHeal() {
TestUtils.getMockPlayer("bloodDrainHealth2").use {p ->
addItem(p, Items.BLOOD_DRAIN_SCROLL_12444)
p.skills.lifepoints = 5
val npc = BloatedLeechNPC(p, NPCs.BLOATED_LEECH_6843)
npc.location = ServerConstants.HOME_LOCATION
val ownerBaseline = p.skills.lifepoints
Assertions.assertEquals(true, npc.executeSpecialMove(FamiliarSpecial(p)))
TestUtils.advanceTicks(1, false)
Assertions.assertEquals(true, p.skills.lifepoints < ownerBaseline)
}
}
@Test fun bloodDrainShouldOnlyRestorePercentOfStats() {
TestUtils.getMockPlayer("bloodDrainStats").use { p ->
addItem(p, Items.BLOOD_DRAIN_SCROLL_12444)
val npc = BloatedLeechNPC(p, NPCs.BLOATED_LEECH_6843)
npc.location = ServerConstants.HOME_LOCATION
p.skills.setStaticLevel(Skills.STRENGTH, 99)
p.skills.setStaticLevel(Skills.FARMING, 99)
p.skills.setLevel(Skills.STRENGTH, 1)
p.skills.setLevel(Skills.FARMING, 1)
Assertions.assertEquals(true,npc.executeSpecialMove(FamiliarSpecial(p)))
Assertions.assertEquals(21, p.skills.getLevel(Skills.STRENGTH))
Assertions.assertEquals(21, p.skills.getLevel(Skills.FARMING))
}
}
@Test fun bloodDrainShouldNotBoostStats() {
TestUtils.getMockPlayer("bloodDrainStats2").use { p ->
addItem(p, Items.BLOOD_DRAIN_SCROLL_12444)
val npc = BloatedLeechNPC(p, NPCs.BLOATED_LEECH_6843)
npc.location = ServerConstants.HOME_LOCATION
p.skills.setStaticLevel(Skills.STRENGTH, 99)
p.skills.setStaticLevel(Skills.FARMING, 99)
p.skills.setLevel(Skills.STRENGTH, 98)
p.skills.setLevel(Skills.FARMING, 98)
Assertions.assertEquals(true,npc.executeSpecialMove(FamiliarSpecial(p)))
Assertions.assertEquals(99, p.skills.getLevel(Skills.STRENGTH))
Assertions.assertEquals(99, p.skills.getLevel(Skills.FARMING))
}
}
@Test fun bloodDrainCuresPoisonAndDisease() {
TestUtils.getMockPlayer("bloodDrainAilments").use { p ->
addItem(p, Items.BLOOD_DRAIN_SCROLL_12444)
val npc = BloatedLeechNPC(p, NPCs.BLOATED_LEECH_6843)
npc.location = ServerConstants.HOME_LOCATION
applyPoison(p, p, 40)
Assertions.assertNotNull(getOrStartTimer<Disease>(p, 40))
Assertions.assertEquals(true, hasTimerActive<Poison>(p))
Assertions.assertEquals(true, hasTimerActive<Disease>(p))
Assertions.assertEquals(true, npc.executeSpecialMove(FamiliarSpecial(p)))
Assertions.assertEquals(false, hasTimerActive<Poison>(p))
Assertions.assertEquals(false, hasTimerActive<Disease>(p))
}
}
}