Added farming patch safety checks

This commit is contained in:
Ceikry
2023-08-02 00:39:04 +00:00
committed by Ryan
parent 105f7d5b86
commit 6d23ec5234
6 changed files with 170 additions and 37 deletions
@@ -74,6 +74,33 @@ enum class FarmingPatch(val varbit: Int, val type: PatchType) {
companion object {
@JvmField
val patches = FarmingPatch.values().map { it.varbit to it }.toMap()
val patchNodes = ArrayList<Int>()
val nodeMap = HashMap<Int, SceneryDefinition>()
init {
patchNodes.addAll(8550..8557) //allotment wrappers
patchNodes.addAll(7847..7853) //flower patch wrappers
patchNodes.addAll(8150..8156) //herb patch wrappers
patchNodes.addAll(8388..8391) // Tree patches
patchNodes.add(19147) //Tree patch
patchNodes.addAll(7962..7965) //fruit trees
patchNodes.addAll(8173..8176) //hops
patchNodes.addAll(7577..7580) //bush
patchNodes.add(23760) //evil turnip
patchNodes.add(7572) //belladonna
patchNodes.add(8337) //mushroom
patchNodes.add(27197) //jade vine
patchNodes.add(7771) //cactus
patchNodes.add(7807) //calquat
patchNodes.addAll(8382..8383)//spirit trees
patchNodes.add(8338) //spirit tree
patchNodes.add(18816) //death plateau wrapper
for (patch in patchNodes) {
val def = SceneryDefinition.forId(patch)
nodeMap[def.varbitID] = def
}
}
@JvmStatic
fun forObject(obj: Scenery): FarmingPatch?{
@@ -85,6 +112,10 @@ enum class FarmingPatch(val varbit: Int, val type: PatchType) {
val objDef = SceneryDefinition.forId(id)
return patches[objDef.varbitID]
}
fun getSceneryDefByVarbit (id: Int) : SceneryDefinition? {
return nodeMap[id]
}
}
fun getPatchFor(player: Player): Patch{
@@ -98,6 +98,35 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
updateBit()
}
fun setVisualState (state: Int) {
val finalState = ensureStateSanity(state)
setVarbit(player, patch.varbit, finalState)
}
fun ensureStateSanity (state: Int) : Int {
val patchDef = FarmingPatch.getSceneryDefByVarbit(patch.varbit) ?: return state
val currentStateDef = patchDef.getChildObjectAtIndex(state)
if (currentStateDef.name == patchDef.getChildObjectAtIndex(3).name) { //if we're weedy
if (state and 0x40 != 0) { //if this invalid state was caused by water/death
//remove water/death
isDead = false
isWatered = false
log(this::class.java, Log.DEBUG, "Patch for ${player.username} at varbit ${patch.varbit} with plantable ${plantable?.name ?: "none"} was set to watered/dead at stage $currentGrowthStage, which isn't valid.")
return (state and (0x40.inv()))
}
else if (state and 0x80 != 0) { //if this invalid state was caused by disease
//remove disease
isDiseased = false
log(this::class.java, Log.DEBUG, "Patch for ${player.username} at varbit ${patch.varbit} with plantable ${plantable?.name ?: "none"} was set to diseased at stage $currentGrowthStage, which isn't valid.")
return (state and (0x80.inv()))
}
else {
log (this::class.java, Log.ERR, "Patch for ${player.username} at varbit ${patch.varbit} with plantable ${plantable?.name ?: "none"} was set to state $state at growth stage $currentGrowthStage, which isn't valid. We're not sure why this is happening.")
}
}
return state
}
fun isFertilized(): Boolean {
return compost != CompostType.NONE
}
@@ -106,7 +135,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
return currentGrowthStage == (plantable?.stages ?: 0)
}
private fun updateBit(){
fun updateBit(){
if(isCheckHealth){
when(patch.type){
PatchType.FRUIT_TREE -> setVarbit(player, patch.varbit, plantable!!.value + plantable!!.stages + 20)
@@ -122,11 +151,11 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
if (isDiseased) state = state or 0x80
if (state != getVarbit(player, patch.varbit))
setVarbit(player, patch.varbit, state)
setVisualState(state)
}
PatchType.BUSH -> {
if(isDead) setVarbit(player, patch.varbit, getBushDeathValue())
else if(isDiseased && !isDead) setVarbit(player, patch.varbit, getBushDiseaseValue())
if(isDead) setVisualState(getBushDeathValue())
else if(isDiseased && !isDead) setVisualState(getBushDiseaseValue())
}
PatchType.TREE -> {
var state = getVarbit(player, patch.varbit)
@@ -135,25 +164,25 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
else if (isDiseased) state = state or 0x40
if (state != getVarbit(player, patch.varbit))
setVarbit(player, patch.varbit, state)
setVisualState(state)
}
PatchType.FRUIT_TREE -> {
if(isDead) setVarbit(player, patch.varbit, getFruitTreeDeathValue())
else if(isDiseased && !isDead) setVarbit(player, patch.varbit, getFruitTreeDiseaseValue())
if(isDead) setVisualState(getFruitTreeDeathValue())
else if(isDiseased && !isDead) setVisualState(getFruitTreeDiseaseValue())
}
PatchType.BELLADONNA -> {
if(isDead) setVarbit(player, patch.varbit, getBelladonnaDeathValue())
else if(isDiseased && !isDead) setVarbit(player, patch.varbit, getBelladonnaDiseaseValue())
else setVarbit(player, patch.varbit, (plantable?.value ?: 0) + currentGrowthStage)
if(isDead) setVisualState(getBelladonnaDeathValue())
else if(isDiseased && !isDead) setVisualState(getBelladonnaDiseaseValue())
else setVisualState((plantable?.value ?: 0) + currentGrowthStage)
}
PatchType.CACTUS -> {
if(isDead) setVarbit(player, patch.varbit, getCactusDeathValue())
else if(isDiseased && !isDead) setVarbit(player, patch.varbit, getCactusDiseaseValue())
if(isDead) setVisualState(getCactusDeathValue())
else if(isDiseased && !isDead) setVisualState(getCactusDiseaseValue())
}
PatchType.HERB -> {
if(isDead) setVarbit(player, patch.varbit, getHerbDeathValue())
else if(isDiseased && !isDead) setVarbit(player, patch.varbit, getHerbDiseaseValue())
else setVarbit(player, patch.varbit, (plantable?.value ?: 0) + currentGrowthStage)
if(isDead) setVisualState(getHerbDeathValue())
else if(isDiseased && !isDead) setVisualState(getHerbDiseaseValue())
else setVisualState((plantable?.value ?: 0) + currentGrowthStage)
}
else -> {}
}
@@ -25,11 +25,10 @@ class UseWithPatchHandler : InteractionListener {
@JvmField
val allowedNodes = ArrayList<Int>()
val patches = ArrayList<Int>()
override fun defineListeners() {
loadNodes()
onUseWith(IntType.SCENERY, allowedNodes.toIntArray(), *patches.toIntArray()) { player, used, with ->
onUseWith(IntType.SCENERY, allowedNodes.toIntArray(), *FarmingPatch.patchNodes.toIntArray()) { player, used, with ->
val patch = FarmingPatch.forObject(with.asScenery()) ?: return@onUseWith true
val usedItem = used.asItem()
@@ -233,23 +232,6 @@ class UseWithPatchHandler : InteractionListener {
}
fun loadNodes(){
patches.addAll(8550..8557) //allotment wrappers
patches.addAll(7847..7853) //flower patch wrappers
patches.addAll(8150..8156) //herb patch wrappers
patches.addAll(8388..8391) // Tree patches
patches.add(19147) //Tree patch
patches.addAll(7962..7965) //fruit trees
patches.addAll(8173..8176) //hops
patches.addAll(7577..7580) //bush
patches.add(23760) //evil turnip
patches.add(7572) //belladonna
patches.add(8337) //mushroom
patches.add(27197) //jade vine
patches.add(7771) //cactus
patches.add(7807) //calquat
patches.addAll(8382..8383)//spirit trees
patches.add(8338) //spirit tree
patches.add(18816) //death plateau wrapper
for(p in Plantable.values()){
allowedNodes.add(p.itemID)
}
+11 -3
View File
@@ -896,15 +896,23 @@ public class SceneryDefinition extends Definition<Scenery> {
} else {
configValue = 0;
}
if (configValue < 0 || configValue >= childrenIds.length - 1 || childrenIds[configValue] == -1) {
SceneryDefinition childDef = getChildObjectAtIndex(configValue);
if (childDef != null) childDef.configFileId = this.configFileId;
return childDef;
}
public SceneryDefinition getChildObjectAtIndex (int index) {
if (childrenIds == null || childrenIds.length < 1) {
return this;
}
if (index < 0 || index >= childrenIds.length - 1 || childrenIds[index] == -1) {
int objectId = childrenIds[childrenIds.length - 1];
if (objectId != -1) {
return forId(objectId);
}
return this;
}
forId(childrenIds[configValue]).configFileId = this.configFileId;
return forId(childrenIds[configValue]);
return forId(childrenIds[index]);
}
/**
+2
View File
@@ -1,4 +1,5 @@
import content.global.ame.RandomEventManager
import content.global.skill.farming.timers.CropGrowth
import core.cache.Cache
import core.cache.crypto.ISAACCipher
import core.cache.crypto.ISAACPair
@@ -83,6 +84,7 @@ object TestUtils {
fun registerTimers() { //allow timers to be registered for use by tests
TimerRegistry.registerTimer(Poison())
TimerRegistry.registerTimer(Disease())
TimerRegistry.registerTimer(CropGrowth())
}
fun loadFile(path: String) : URI? {
@@ -0,0 +1,81 @@
package content.skill.farming
import TestUtils
import content.global.skill.farming.FarmingPatch
import content.global.skill.farming.PatchRaker
import content.global.skill.farming.PatchType
import content.global.skill.farming.Plantable
import content.global.skill.farming.timers.CropGrowth
import core.cache.def.impl.SceneryDefinition
import core.game.node.entity.skill.Skills
import core.game.system.timer.TimerRegistry
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
class PatchTests {
init { TestUtils.preTestSetup() }
@Test fun patchGettingDiseasedOrDyingShouldNotGoIntoInvalidState() {
//Dead/Watered (state | 0x40) and Diseased (state | 0x80) are both invalid stages for limpwurt when fully grown (stage 4/state 32), so we use this as our testcase
//specifically, limpwurt has an issue when the patch is ONLY watered (state | 0x40) OR diseased (state | 0x80), having both (state | 0x40 | 0x80) is fine.
TestUtils.getMockPlayer("invalidPatchState").use {p ->
p.skills.setStaticLevel(Skills.FARMING, 99)
p.skills.setLevel(Skills.FARMING, 99)
val patch = FarmingPatch.S_FALADOR_FLOWER_C.getPatchFor(p)
patch.plant(Plantable.LIMPWURT_SEED)
for (i in 0 until 4) { //grow to full growth while making sure we don't die/get diseased
patch.currentGrowthStage = 4
patch.update()
}
Assertions.assertEquals(32, patch.getCurrentState())
patch.isWatered = true
patch.update()
Assertions.assertEquals(false, patch.isDiseased)
Assertions.assertEquals(false, patch.isDead)
Assertions.assertEquals(false, patch.isWatered)
Assertions.assertEquals(32, patch.getCurrentState())
patch.isDiseased = true
patch.updateBit()
Assertions.assertEquals(false, patch.isDiseased)
Assertions.assertEquals(false, patch.isDead)
Assertions.assertEquals(false, patch.isWatered)
Assertions.assertEquals(32, patch.getCurrentState())
}
}
@Test fun rakingPatchAtInvalidStagesShouldDoNothing() {
TestUtils.getMockPlayer("Patchraker").use {p ->
val patch = FarmingPatch.S_FALADOR_FLOWER_C.getPatchFor(p)
patch.update()
Assertions.assertEquals(0, patch.getCurrentState()) //assert that it's in the max weedy state
PatchRaker.rake(p, patch.patch)
TestUtils.advanceTicks(25, false)
Assertions.assertEquals(3, patch.getCurrentState())
PatchRaker.rake(p, patch.patch)
TestUtils.advanceTicks(10, false)
Assertions.assertEquals(3, patch.getCurrentState())
patch.plant(Plantable.POTATO_SEED)
val plantedBaseline = patch.getCurrentState()
PatchRaker.rake(p, patch.patch)
TestUtils.advanceTicks(10, false)
Assertions.assertEquals(plantedBaseline, patch.getCurrentState())
patch.currentGrowthStage = Plantable.POTATO_SEED.stages
patch.update()
Assertions.assertEquals(true, patch.isGrown())
val stateBaseline = patch.getCurrentState()
PatchRaker.rake(p, patch.patch)
TestUtils.advanceTicks(10, false)
Assertions.assertEquals(stateBaseline, patch.getCurrentState())
}
}
}