feat: Add more complex entity equipment scraper
This commit is contained in:
@@ -7,6 +7,8 @@ import net.minecraft.entity.LivingEntity
|
|||||||
import net.minecraft.entity.data.DataTracker
|
import net.minecraft.entity.data.DataTracker
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
import net.minecraft.network.packet.s2c.play.EntityAttributesS2CPacket
|
import net.minecraft.network.packet.s2c.play.EntityAttributesS2CPacket
|
||||||
|
import moe.nea.firmament.annotations.Subscribe
|
||||||
|
import moe.nea.firmament.util.MC
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This event is fired when some entity properties are updated.
|
* This event is fired when some entity properties are updated.
|
||||||
@@ -15,7 +17,27 @@ import net.minecraft.network.packet.s2c.play.EntityAttributesS2CPacket
|
|||||||
* *after* the values have been applied to the entity.
|
* *after* the values have been applied to the entity.
|
||||||
*/
|
*/
|
||||||
sealed class EntityUpdateEvent : FirmamentEvent() {
|
sealed class EntityUpdateEvent : FirmamentEvent() {
|
||||||
companion object : FirmamentEventBus<EntityUpdateEvent>()
|
companion object : FirmamentEventBus<EntityUpdateEvent>() {
|
||||||
|
@Subscribe
|
||||||
|
fun onPlayerInventoryUpdate(event: PlayerInventoryUpdate) {
|
||||||
|
val p = MC.player ?: return
|
||||||
|
val updatedSlots = listOf(
|
||||||
|
EquipmentSlot.HEAD to 39,
|
||||||
|
EquipmentSlot.CHEST to 38,
|
||||||
|
EquipmentSlot.LEGS to 37,
|
||||||
|
EquipmentSlot.FEET to 36,
|
||||||
|
EquipmentSlot.OFFHAND to 40,
|
||||||
|
EquipmentSlot.MAINHAND to p.inventory.selectedSlot, // TODO: also equipment update when you swap your selected slot perhaps
|
||||||
|
).mapNotNull { (slot, stackIndex) ->
|
||||||
|
val slotIndex = p.playerScreenHandler.getSlotIndex(p.inventory, stackIndex).asInt
|
||||||
|
event.getOrNull(slotIndex)?.let {
|
||||||
|
Pair.of(slot, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updatedSlots.isNotEmpty())
|
||||||
|
publish(EquipmentUpdate(p, updatedSlots))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract val entity: Entity
|
abstract val entity: Entity
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
|
|
||||||
package moe.nea.firmament.events
|
package moe.nea.firmament.events
|
||||||
|
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
|
|
||||||
sealed class PlayerInventoryUpdate : FirmamentEvent() {
|
sealed class PlayerInventoryUpdate : FirmamentEvent() {
|
||||||
companion object : FirmamentEventBus<PlayerInventoryUpdate>()
|
companion object : FirmamentEventBus<PlayerInventoryUpdate>()
|
||||||
data class Single(val slot: Int, val stack: ItemStack) : PlayerInventoryUpdate()
|
data class Single(val slot: Int, val stack: ItemStack) : PlayerInventoryUpdate() {
|
||||||
data class Multi(val contents: List<ItemStack>) : PlayerInventoryUpdate()
|
override fun getOrNull(slot: Int): ItemStack? {
|
||||||
|
if (slot == this.slot) return stack
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Multi(val contents: List<ItemStack>) : PlayerInventoryUpdate() {
|
||||||
|
override fun getOrNull(slot: Int): ItemStack? {
|
||||||
|
return contents.getOrNull(slot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun getOrNull(slot: Int): ItemStack?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ package moe.nea.firmament.features.debug
|
|||||||
|
|
||||||
import net.minecraft.command.argument.RegistryKeyArgumentType
|
import net.minecraft.command.argument.RegistryKeyArgumentType
|
||||||
import net.minecraft.component.ComponentType
|
import net.minecraft.component.ComponentType
|
||||||
import net.minecraft.component.DataComponentTypes
|
|
||||||
import net.minecraft.entity.Entity
|
import net.minecraft.entity.Entity
|
||||||
|
import net.minecraft.entity.decoration.ArmorStandEntity
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
import net.minecraft.nbt.NbtElement
|
import net.minecraft.nbt.NbtElement
|
||||||
import net.minecraft.nbt.NbtOps
|
import net.minecraft.nbt.NbtOps
|
||||||
import net.minecraft.registry.RegistryKeys
|
import net.minecraft.registry.RegistryKeys
|
||||||
import net.minecraft.util.Identifier
|
|
||||||
import moe.nea.firmament.annotations.Subscribe
|
import moe.nea.firmament.annotations.Subscribe
|
||||||
import moe.nea.firmament.commands.get
|
import moe.nea.firmament.commands.get
|
||||||
import moe.nea.firmament.commands.thenArgument
|
import moe.nea.firmament.commands.thenArgument
|
||||||
@@ -16,16 +15,17 @@ import moe.nea.firmament.commands.thenExecute
|
|||||||
import moe.nea.firmament.commands.thenLiteral
|
import moe.nea.firmament.commands.thenLiteral
|
||||||
import moe.nea.firmament.events.CommandEvent
|
import moe.nea.firmament.events.CommandEvent
|
||||||
import moe.nea.firmament.events.EntityUpdateEvent
|
import moe.nea.firmament.events.EntityUpdateEvent
|
||||||
|
import moe.nea.firmament.events.WorldReadyEvent
|
||||||
import moe.nea.firmament.util.ClipboardUtils
|
import moe.nea.firmament.util.ClipboardUtils
|
||||||
import moe.nea.firmament.util.MC
|
import moe.nea.firmament.util.MC
|
||||||
|
import moe.nea.firmament.util.math.GChainReconciliation
|
||||||
|
import moe.nea.firmament.util.math.GChainReconciliation.shortenCycle
|
||||||
import moe.nea.firmament.util.mc.NbtPrism
|
import moe.nea.firmament.util.mc.NbtPrism
|
||||||
import moe.nea.firmament.util.skyBlockId
|
|
||||||
import moe.nea.firmament.util.tr
|
import moe.nea.firmament.util.tr
|
||||||
|
|
||||||
object AnimatedClothingScanner {
|
object AnimatedClothingScanner {
|
||||||
|
|
||||||
data class SubjectOfFashionTheft<T>(
|
data class LensOfFashionTheft<T>(
|
||||||
val observedEntity: Entity,
|
|
||||||
val prism: NbtPrism,
|
val prism: NbtPrism,
|
||||||
val component: ComponentType<T>,
|
val component: ComponentType<T>,
|
||||||
) {
|
) {
|
||||||
@@ -36,75 +36,158 @@ object AnimatedClothingScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var subject: SubjectOfFashionTheft<*>? = null
|
var lens: LensOfFashionTheft<*>? = null
|
||||||
|
var subject: Entity? = null
|
||||||
|
var history: MutableList<String> = mutableListOf()
|
||||||
|
val metaHistory: MutableList<List<String>> = mutableListOf()
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
@Subscribe
|
@Subscribe
|
||||||
fun onUpdate(event: EntityUpdateEvent) {
|
fun onUpdate(event: EntityUpdateEvent) {
|
||||||
val s = subject ?: return
|
val s = subject ?: return
|
||||||
if (event.entity != s.observedEntity) return
|
if (event.entity != s) return
|
||||||
|
val l = lens ?: return
|
||||||
if (event is EntityUpdateEvent.EquipmentUpdate) {
|
if (event is EntityUpdateEvent.EquipmentUpdate) {
|
||||||
val lines = mutableListOf<String>()
|
|
||||||
event.newEquipment.forEach {
|
event.newEquipment.forEach {
|
||||||
val formatted = (s.observe(it.second)).joinToString()
|
val formatted = (l.observe(it.second)).joinToString()
|
||||||
lines.add(formatted)
|
history.add(formatted)
|
||||||
MC.sendChat(
|
// TODO: add a slot filter
|
||||||
tr(
|
|
||||||
"firmament.fitstealer.update",
|
|
||||||
"[FIT CHECK][${MC.currentTick}] ${it.first.asString()} => $formatted"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (lines.isNotEmpty()) {
|
|
||||||
val contents = ClipboardUtils.getTextContents()
|
|
||||||
if (contents.startsWith(EXPORT_WATERMARK))
|
|
||||||
ClipboardUtils.setTextContent(
|
|
||||||
contents + "\n" + lines.joinToString("\n")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val EXPORT_WATERMARK = "[CLOTHES EXPORT]"
|
fun reduceHistory(reducer: (List<String>, List<String>) -> List<String>): List<String> {
|
||||||
|
return metaHistory.fold(history, reducer).shortenCycle()
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
fun onSubCommand(event: CommandEvent.SubCommand) {
|
fun onSubCommand(event: CommandEvent.SubCommand) {
|
||||||
event.subcommand("dev") {
|
event.subcommand("dev") {
|
||||||
thenLiteral("stealthisfit") {
|
thenLiteral("stealthisfit") {
|
||||||
|
thenLiteral("clear") {
|
||||||
|
thenExecute {
|
||||||
|
subject = null
|
||||||
|
metaHistory.clear()
|
||||||
|
history.clear()
|
||||||
|
MC.sendChat(tr("firmament.fitstealer.clear", "Cleared fit stealing history"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thenLiteral("copy") {
|
||||||
|
thenExecute {
|
||||||
|
val history = reduceHistory { a, b -> a + b }
|
||||||
|
copyHistory(history)
|
||||||
|
MC.sendChat(tr("firmament.fitstealer.copied", "Copied the history"))
|
||||||
|
}
|
||||||
|
thenLiteral("deduplicated") {
|
||||||
|
thenExecute {
|
||||||
|
val history = reduceHistory { a, b ->
|
||||||
|
(a.toMutableSet() + b).toList()
|
||||||
|
}
|
||||||
|
copyHistory(history)
|
||||||
|
MC.sendChat(
|
||||||
|
tr(
|
||||||
|
"firmament.fitstealer.copied.deduplicated",
|
||||||
|
"Copied the deduplicated history"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thenLiteral("merged") {
|
||||||
|
thenExecute {
|
||||||
|
val history = reduceHistory(GChainReconciliation::reconcileCycles)
|
||||||
|
copyHistory(history)
|
||||||
|
MC.sendChat(tr("firmament.fitstealer.copied.merged", "Copied the merged history"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thenLiteral("target") {
|
||||||
|
thenLiteral("self") {
|
||||||
|
thenExecute {
|
||||||
|
toggleObserve(MC.player!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thenLiteral("pet") {
|
||||||
|
thenExecute {
|
||||||
|
source.sendFeedback(
|
||||||
|
tr(
|
||||||
|
"firmament.fitstealer.stealingpet",
|
||||||
|
"Observing nearest marker armourstand"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val p = MC.player!!
|
||||||
|
val nearestPet = p.world.getEntitiesByClass(
|
||||||
|
ArmorStandEntity::class.java,
|
||||||
|
p.boundingBox.expand(10.0),
|
||||||
|
{ it.isMarker })
|
||||||
|
.minBy { it.squaredDistanceTo(p) }
|
||||||
|
toggleObserve(nearestPet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thenExecute {
|
||||||
|
val ent = MC.instance.targetedEntity
|
||||||
|
if (ent == null) {
|
||||||
|
source.sendFeedback(
|
||||||
|
tr(
|
||||||
|
"firmament.fitstealer.notargetundercursor",
|
||||||
|
"No entity under cursor"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
toggleObserve(ent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thenLiteral("path") {
|
||||||
thenArgument(
|
thenArgument(
|
||||||
"component",
|
"component",
|
||||||
RegistryKeyArgumentType.registryKey(RegistryKeys.DATA_COMPONENT_TYPE)
|
RegistryKeyArgumentType.registryKey(RegistryKeys.DATA_COMPONENT_TYPE)
|
||||||
) { component ->
|
) { component ->
|
||||||
thenArgument("path", NbtPrism.Argument) { path ->
|
thenArgument("path", NbtPrism.Argument) { path ->
|
||||||
thenExecute {
|
thenExecute {
|
||||||
subject =
|
lens = LensOfFashionTheft(
|
||||||
if (subject == null) run {
|
|
||||||
val entity = MC.instance.targetedEntity ?: return@run null
|
|
||||||
val clipboard = ClipboardUtils.getTextContents()
|
|
||||||
if (!clipboard.startsWith(EXPORT_WATERMARK)) {
|
|
||||||
ClipboardUtils.setTextContent(EXPORT_WATERMARK)
|
|
||||||
} else {
|
|
||||||
ClipboardUtils.setTextContent("$clipboard\n\n[NEW SCANNER]")
|
|
||||||
}
|
|
||||||
SubjectOfFashionTheft(
|
|
||||||
entity,
|
|
||||||
get(path),
|
get(path),
|
||||||
MC.unsafeGetRegistryEntry(get(component))!!,
|
MC.unsafeGetRegistryEntry(get(component))!!,
|
||||||
)
|
)
|
||||||
} else null
|
source.sendFeedback(
|
||||||
|
tr(
|
||||||
|
"firmament.fitstealer.lensset",
|
||||||
|
"Analyzing path ${get(path)} for component ${get(component).value}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyHistory(toCopy: List<String>) {
|
||||||
|
ClipboardUtils.setTextContent(toCopy.joinToString("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
fun onWorldSwap(event: WorldReadyEvent) {
|
||||||
|
subject = null
|
||||||
|
if (history.isNotEmpty()) {
|
||||||
|
metaHistory.add(history)
|
||||||
|
history = mutableListOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleObserve(entity: Entity?) {
|
||||||
|
subject = if (subject == null) entity else null
|
||||||
|
if (subject == null) {
|
||||||
|
metaHistory.add(history)
|
||||||
|
history = mutableListOf()
|
||||||
|
}
|
||||||
MC.sendChat(
|
MC.sendChat(
|
||||||
subject?.let {
|
subject?.let {
|
||||||
tr(
|
tr(
|
||||||
"firmament.fitstealer.targeted",
|
"firmament.fitstealer.targeted",
|
||||||
"Observing the equipment of ${it.observedEntity.name}."
|
"Observing the equipment of ${it.name}."
|
||||||
)
|
)
|
||||||
} ?: tr("firmament.fitstealer.targetlost", "No longer logging equipment."),
|
} ?: tr("firmament.fitstealer.targetlost", "No longer logging equipment."),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
102
src/main/kotlin/util/math/GChainReconciliation.kt
Normal file
102
src/main/kotlin/util/math/GChainReconciliation.kt
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package moe.nea.firmament.util.math
|
||||||
|
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Algorithm for (sort of) cheap reconciliation of two cycles with missing frames.
|
||||||
|
*/
|
||||||
|
object GChainReconciliation {
|
||||||
|
// Step one: Find the most common element and shift the arrays until it is at the start in both (this could be just rotating until minimal levenshtein distance or smth. that would be way better for cycles with duplicates, but i do not want to implement levenshtein as well)
|
||||||
|
// Step two: Find the first different element.
|
||||||
|
// Step three: Find the next index of both of the elements.
|
||||||
|
// Step four: Insert the element that is further away.
|
||||||
|
|
||||||
|
fun <T> Iterable<T>.frequencies(): Map<T, Int> {
|
||||||
|
val acc = mutableMapOf<T, Int>()
|
||||||
|
for (t in this) {
|
||||||
|
acc.compute(t, { _, old -> (old ?: 0) + 1 })
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> findMostCommonlySharedElement(
|
||||||
|
leftChain: List<T>,
|
||||||
|
rightChain: List<T>,
|
||||||
|
): T {
|
||||||
|
val lf = leftChain.frequencies()
|
||||||
|
val rf = rightChain.frequencies()
|
||||||
|
val mostCommonlySharedElement = lf.maxByOrNull { min(it.value, rf[it.key] ?: 0) }?.key
|
||||||
|
if (mostCommonlySharedElement == null || mostCommonlySharedElement !in rf)
|
||||||
|
error("Could not find a shared element")
|
||||||
|
return mostCommonlySharedElement
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> List<T>.getMod(index: Int): T {
|
||||||
|
return this[index.mod(size)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> List<T>.rotated(offset: Int): List<T> {
|
||||||
|
val newList = mutableListOf<T>()
|
||||||
|
for (index in indices) {
|
||||||
|
newList.add(getMod(index - offset))
|
||||||
|
}
|
||||||
|
return newList
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> shiftToFront(list: List<T>, element: T): List<T> {
|
||||||
|
val shiftDistance = list.indexOf(element)
|
||||||
|
require(shiftDistance >= 0)
|
||||||
|
return list.rotated(-shiftDistance)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> List<T>.indexOfOrMaxInt(element: T): Int = indexOf(element).takeUnless { it < 0 } ?: Int.MAX_VALUE
|
||||||
|
|
||||||
|
fun <T> reconcileCycles(
|
||||||
|
leftChain: List<T>,
|
||||||
|
rightChain: List<T>,
|
||||||
|
): List<T> {
|
||||||
|
val mostCommonElement = findMostCommonlySharedElement(leftChain, rightChain)
|
||||||
|
val left = shiftToFront(leftChain, mostCommonElement).toMutableList()
|
||||||
|
val right = shiftToFront(rightChain, mostCommonElement).toMutableList()
|
||||||
|
|
||||||
|
var index = 0
|
||||||
|
while (index < left.size && index < right.size) {
|
||||||
|
val leftEl = left[index]
|
||||||
|
val rightEl = right[index]
|
||||||
|
if (leftEl == rightEl) {
|
||||||
|
index++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val nextLeftInRight = right.subList(index, right.size)
|
||||||
|
.indexOfOrMaxInt(leftEl)
|
||||||
|
|
||||||
|
val nextRightInLeft = left.subList(index, left.size)
|
||||||
|
.indexOfOrMaxInt(rightEl)
|
||||||
|
if (nextLeftInRight < nextRightInLeft) {
|
||||||
|
left.add(index, rightEl)
|
||||||
|
} else if (nextRightInLeft < nextLeftInRight) {
|
||||||
|
right.add(index, leftEl)
|
||||||
|
} else {
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if (left.size < right.size) right else left
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> isValidCycle(longList: List<T>, cycle: List<T>): Boolean {
|
||||||
|
for ((i, value) in longList.withIndex()) {
|
||||||
|
if (cycle.getMod(i) != value)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> List<T>.shortenCycle(): List<T> {
|
||||||
|
for (i in (1..<size)) {
|
||||||
|
if (isValidCycle(this, subList(0, i)))
|
||||||
|
return subList(0, i)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
75
src/test/kotlin/util/math/GChainReconciliationTest.kt
Normal file
75
src/test/kotlin/util/math/GChainReconciliationTest.kt
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package moe.nea.firmament.test.util.math
|
||||||
|
|
||||||
|
import io.kotest.core.spec.style.AnnotationSpec
|
||||||
|
import org.junit.jupiter.api.Assertions
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import moe.nea.firmament.util.math.GChainReconciliation
|
||||||
|
import moe.nea.firmament.util.math.GChainReconciliation.rotated
|
||||||
|
|
||||||
|
class GChainReconciliationTest : AnnotationSpec() {
|
||||||
|
|
||||||
|
fun <T> assertEqualCycles(
|
||||||
|
expected: List<T>,
|
||||||
|
actual: List<T>
|
||||||
|
) {
|
||||||
|
for (offset in expected.indices) {
|
||||||
|
val rotated = expected.rotated(offset)
|
||||||
|
val matchesAtRotation = run {
|
||||||
|
for ((i, v) in actual.withIndex()) {
|
||||||
|
if (rotated[i % rotated.size] != v)
|
||||||
|
return@run false
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
if (matchesAtRotation)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assertEquals(expected, actual, "Expected arrays to be cycle equivalent")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testUnfixableCycleNotBeingModified() {
|
||||||
|
assertEquals(
|
||||||
|
listOf(1, 2, 3, 4, 6, 1, 2, 3, 4, 6),
|
||||||
|
GChainReconciliation.reconcileCycles(
|
||||||
|
listOf(1, 2, 3, 4, 6, 1, 2, 3, 4, 6),
|
||||||
|
listOf(2, 3, 4, 5, 1, 2, 3, 4, 5, 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMultipleIndependentHoles() {
|
||||||
|
assertEqualCycles(
|
||||||
|
listOf(1, 2, 3, 4, 5, 6),
|
||||||
|
GChainReconciliation.reconcileCycles(
|
||||||
|
listOf(1, 3, 4, 5, 6, 1, 3, 4, 5, 6),
|
||||||
|
listOf(2, 3, 4, 5, 1, 2, 3, 4, 5, 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBigHole() {
|
||||||
|
assertEqualCycles(
|
||||||
|
listOf(1, 2, 3, 4, 5, 6),
|
||||||
|
GChainReconciliation.reconcileCycles(
|
||||||
|
listOf(1, 4, 5, 6, 1, 4, 5, 6),
|
||||||
|
listOf(2, 3, 4, 5, 1, 2, 3, 4, 5, 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOneMissingBeingDetected() {
|
||||||
|
assertEqualCycles(
|
||||||
|
listOf(1, 2, 3, 4, 5, 6),
|
||||||
|
GChainReconciliation.reconcileCycles(
|
||||||
|
listOf(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6),
|
||||||
|
listOf(2, 3, 4, 5, 1, 2, 3, 4, 5, 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user