Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ Gradle [lockfiles](https://docs.gradle.org/current/userguide/dependency_locking.
to specific version numbers. Lockfiles can be updated using the `--write-locks` flag. This may need to be paired with a
build target such as `gradle build --write-locks` so that all dependencies are resolved.

### Tests
Hexchanting uses the Minecraft gametests system. These tests are run as part of the
standard gradle `build` target. They can also be run by the `runGameTest` target and the
`runClientGameTest` target. The latter is useful for debugging tests as you can trigger
specific tests with the in-game `/test run` command.

>>>>>>> conflict 1 of 1 ends
### Reproducibility
The [org.gradlex.reproducible-builds](https://gradlex.org/reproducible-builds/) plugin is used to set defaults for
Gradle's builtin tasks that aid reproducibility. Non-reproducible builds are considered low priority bugs. However,
Expand Down
12 changes: 11 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ dependencies {
// lock dependencies
dependencyLocking {
lockAllConfigurations()
lockMode = LockMode.STRICT
lockMode = LockMode.LENIENT
}

// lock plugins
Expand All @@ -89,6 +89,16 @@ buildscript {
}
}

fabricApi {
configureTests {
createSourceSet = true
modId = "${project.name}-test"
enableGameTests = true
enableClientGameTests = true
eula = true // Agree to the Minecraft EULA.
}
}

tasks.processResources {
inputs.property("version", project.version)
filesMatching("fabric.mod.json") {
Expand Down
716 changes: 358 additions & 358 deletions gradle.lockfile

Large diffs are not rendered by default.

161 changes: 161 additions & 0 deletions src/gametest/kotlin/gay/thehivemind/hexchanting/HexchantingGameTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package gay.thehivemind.hexchanting

import at.petrak.hexcasting.api.casting.eval.SpecialPatterns
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.iota.PatternIota
import at.petrak.hexcasting.api.casting.math.HexPattern
import at.petrak.hexcasting.common.lib.HexItems
import at.petrak.hexcasting.common.lib.hex.HexActions
import at.petrak.hexcasting.xplat.IXplatAbstractions
import gay.thehivemind.hexchanting.casting.HexchantingPatterns
import gay.thehivemind.hexchanting.items.HexImbuedItem
import gay.thehivemind.hexchanting.items.HexchantingItems
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest
import net.minecraft.entity.EquipmentSlot
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.test.GameTest
import net.minecraft.test.GameTestException
import net.minecraft.test.TestContext
import net.minecraft.util.Hand

class HexchantingGameTest : FabricGameTest {
val logger = Hexchanting.LOGGER

@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
fun testImbuingArrow(context: TestContext) {
val player = context.createMockCreativeServerPlayerInWorld()
imbueItemUsingStaff(HexchantingItems.HEX_ARROW, player, context)
context.complete()
}

@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
fun testImbuingAxe(context: TestContext) {
val player = context.createMockCreativeServerPlayerInWorld()
imbueItemUsingStaff(HexchantingItems.HEX_AXE, player, context)
context.complete()
}

@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
fun testImbuingHoe(context: TestContext) {
val player = context.createMockCreativeServerPlayerInWorld()
imbueItemUsingStaff(HexchantingItems.HEX_HOE, player, context)
context.complete()
}

@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
fun testImbuingPickaxe(context: TestContext) {
val player = context.createMockCreativeServerPlayerInWorld()
imbueItemUsingStaff(HexchantingItems.HEX_PICKAXE, player, context)
context.complete()
}

@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
fun testImbuingShovel(context: TestContext) {
val player = context.createMockCreativeServerPlayerInWorld()
imbueItemUsingStaff(HexchantingItems.HEX_SHOVEL, player, context)
context.complete()
}

@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
fun testImbuingSword(context: TestContext) {
val player = context.createMockCreativeServerPlayerInWorld()
imbueItemUsingStaff(HexchantingItems.HEX_SWORD, player, context)
context.complete()
}


@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
fun testImbuingHelmet(context: TestContext) {
val player = context.createMockCreativeServerPlayerInWorld()
imbueItemUsingStaff(HexchantingItems.HEX_HELMET, player, context)
context.complete()
}

@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
fun testImbuingChestplate(context: TestContext) {
val player = context.createMockCreativeServerPlayerInWorld()
imbueItemUsingStaff(HexchantingItems.HEX_CHESTPLATE, player, context)
context.complete()
}

@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
fun testImbuingLeggings(context: TestContext) {
val player = context.createMockCreativeServerPlayerInWorld()
imbueItemUsingStaff(HexchantingItems.HEX_LEGGINGS, player, context)
context.complete()
}

@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
fun testImbuingBoots(context: TestContext) {
val player = context.createMockCreativeServerPlayerInWorld()
imbueItemUsingStaff(HexchantingItems.HEX_BOOTS, player, context)
context.complete()
}

/**
* Creates a hex that casts imbues an item with the input hex.
*
* This wraps the input in introspection/retrospection and then casts
* Imbue Equipment.
*/
private fun makeImbueIotaList(iotaToImbue: List<Iota>): List<Iota> {
val iotas = mutableListOf<Iota>(PatternIota(SpecialPatterns.INTROSPECTION))
iotas.addAll(iotaToImbue)
iotas.add(PatternIota(SpecialPatterns.RETROSPECTION))
iotas.add(PatternIota(HexchantingPatterns.HEX_IMBUE_EQUIPMENT.prototype))
return iotas.toList()
}

/**
* A simple helper to convert a list of HexActions into a list of equivalent pattern iota
*/
private fun actionsAsPatternIota(vararg actions: HexPattern): List<PatternIota> {
return actions.map { p -> PatternIota(p) }
}

/**
* Check if two lists of iota are equal. This means they are the same length and iota in the same index are
* (approximately) equal.
*
* [Iota] don't have an `Iota.equals` function defined. This means they compare using referential equality, which has
* lead to false negatives. However, Hex Casting has an equals pattern. This is implemented using [Iota.tolerates]
* and uses structural equality. This isn't exact equality - doubles use a tolerance - but it is sufficient for our
* purposes.
*/
private fun iotaListsAreEqual(a: List<Iota>, b: List<Iota>): Boolean {
if (a.size == b.size) {
return a.zip(b).all { pair -> Iota.tolerates(pair.first, pair.second) }
}
return false
}

/**
* Simulate imbuing a hex into a Hexchanting item using staff casting
*
* This function includes a number of assertions to validate the result.
*/
private fun imbueItemUsingStaff(item: Item, player: ServerPlayerEntity, context: TestContext): ItemStack? {
// Equip staff and item to imbue
player.equipStack(EquipmentSlot.MAINHAND, HexItems.STAFF_OAK.defaultStack)
player.equipStack(EquipmentSlot.OFFHAND, item.defaultStack)

// Cast imbuing hex with staff
val vm = IXplatAbstractions.INSTANCE.getStaffcastVM(player, Hand.OFF_HAND)
val hexToImbue: List<Iota> = actionsAsPatternIota(HexActions.GET_CASTER.prototype)
val iotas = makeImbueIotaList(hexToImbue)
val castingResult = vm.queueExecuteAndWrapIotas(iotas, player.serverWorld)
logger.debug("Offhand item is {} with nbt {} and count {}", player.offHandStack.item, player.offHandStack.nbt, player.offHandStack.count)
context.assertTrue(castingResult.resolutionType.success, "Imbuing a hex into the item failed")

// Retrieve and verify imbued item
val imbuedItem = player.offHandStack
context.assertTrue(imbuedItem.item == item, "Offhand item does not match expected item type")
val hexHolderItem = imbuedItem.item as? HexImbuedItem ?: throw GameTestException("Imbued item can't be cast to HexImbuedItem")
val imbuedHex = hexHolderItem.getHex(imbuedItem, player.serverWorld) ?: throw GameTestException("Cannot retrieve hex from HexImbuedItem")
logger.debug("Imbued hex is {}, expected hex is {}", imbuedHex.first().serialize(), hexToImbue.first().serialize())
context.assertTrue(iotaListsAreEqual(imbuedHex, hexToImbue ), "Imbued hex differs from expected hex")
return imbuedItem
}
}
13 changes: 13 additions & 0 deletions src/gametest/resources/fabric.mod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"schemaVersion": 1,
"id": "hexchanting-test",
"version": "${version}",
"name": "Gametests for Hexchanting",
"environment": "*",
"entrypoints": {
"fabric-gametest": [{
"value": "gay.thehivemind.hexchanting.HexchantingGameTest",
"adapter": "kotlin"
}]
}
}
5 changes: 4 additions & 1 deletion src/main/kotlin/gay/thehivemind/hexchanting/Hexchanting.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ object Hexchanting : ModInitializer {
FabricLoader.getInstance().configDir.resolve("hexchanting.properties"), copyDefault = true)

// call to init some registries

@Suppress("UnusedExpression")
HexchantingItems
HexchantingPatterns.init()
@Suppress("UnusedExpression")
HexchantingPatterns
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,14 @@ import net.minecraft.registry.Registry
import net.minecraft.util.Identifier

object HexchantingPatterns {
@JvmStatic
fun init() {
register("imbue_equipment", "dqaqdqaqdqaeadawadadawadadawa", HexDir.WEST, OpImbueEquipment())
val HEX_IMBUE_EQUIPMENT = register("imbue_equipment", "dqaqdqaqdqaeadawadadawadadawa", HexDir.WEST, OpImbueEquipment())

}

private fun register(name: String, signature: String, startDir: HexDir, action: Action) {
Registry.register(
private fun register(name: String, signature: String, startDir: HexDir, action: Action): ActionRegistryEntry {
val pattern = HexPattern.fromAngles(signature, startDir)
return Registry.register(
HexActions.REGISTRY,
Identifier.of(MOD_ID, name),
ActionRegistryEntry(HexPattern.fromAngles(signature, startDir), action)
ActionRegistryEntry(pattern, action)
)
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ interface HexImbuedItem : HexHolderItem {
return stack?.hasList(TAG_PROGRAM, NbtElement.COMPOUND_TYPE) ?: false
}

override fun getHex(stack: ItemStack?, level: ServerWorld?): MutableList<Iota>? {
override fun getHex(stack: ItemStack?, level: ServerWorld?): List<Iota>? {
val patsTag = stack?.getList(TAG_PROGRAM, NbtElement.COMPOUND_TYPE.toInt()) ?: return null

val out = ArrayList<Iota>()
val out = mutableListOf<Iota>()
for (patTag in patsTag) {
val tag = patTag.asCompound
out.add(IotaType.deserialize(tag, level))
}
return out
return out.toList()
}

override fun writeHex(stack: ItemStack?, program: MutableList<Iota>?, pigment: FrozenPigment?, media: Long) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,61 +19,54 @@ import net.minecraft.util.Identifier
object HexchantingItems {
val HEX_ARROW = registerWithGroup("amethyst_arrow", HexArrowItem(Item.Settings()), ItemGroups.COMBAT)

init {
registerWithGroup(
"amethyst_axe", HexAxe(HexToolMaterials.AMETHYST, 5F, -3f, Item.Settings()), ItemGroups.TOOLS
)
registerWithGroup(
"amethyst_hoe", HexHoe(HexToolMaterials.AMETHYST, -3, 0f, Item.Settings()), ItemGroups.TOOLS
)
registerWithGroup(
"amethyst_pickaxe", HexPickaxe(HexToolMaterials.AMETHYST, 1, -2.8f, Item.Settings()), ItemGroups.TOOLS
)
registerWithGroup(
"amethyst_shovel", HexShovel(HexToolMaterials.AMETHYST, 1.5F, -3f, Item.Settings()), ItemGroups.TOOLS
)
val HEX_AXE = registerWithGroup(
"amethyst_axe", HexAxe(HexToolMaterials.AMETHYST, 5F, -3f, Item.Settings()), ItemGroups.TOOLS
)
val HEX_HOE = registerWithGroup(
"amethyst_hoe", HexHoe(HexToolMaterials.AMETHYST, -3, 0f, Item.Settings()), ItemGroups.TOOLS
)
val HEX_PICKAXE = registerWithGroup(
"amethyst_pickaxe", HexPickaxe(HexToolMaterials.AMETHYST, 1, -2.8f, Item.Settings()), ItemGroups.TOOLS
)
val HEX_SHOVEL = registerWithGroup(
"amethyst_shovel", HexShovel(HexToolMaterials.AMETHYST, 1.5F, -3f, Item.Settings()), ItemGroups.TOOLS
)
val HEX_SWORD = registerWithGroup(
"amethyst_sword", HexSword(HexToolMaterials.AMETHYST, 3, -2.4F, Item.Settings()), ItemGroups.COMBAT
)

registerWithGroup(
"amethyst_sword", HexSword(HexToolMaterials.AMETHYST, 3, -2.4F, Item.Settings()), ItemGroups.COMBAT
)
val HEX_CHESTPLATE = registerWithGroup(
"amethyst_chestplate",
HexArmorItem(AmethystArmourMaterial, ArmorItem.Type.CHESTPLATE, Item.Settings()),
ItemGroups.COMBAT
)
val HEX_HELMET = registerWithGroup(
"amethyst_helmet",
HexArmorItem(AmethystArmourMaterial, ArmorItem.Type.HELMET, Item.Settings()),
ItemGroups.COMBAT
)
val HEX_LEGGINGS = registerWithGroup(
"amethyst_leggings",
HexArmorItem(AmethystArmourMaterial, ArmorItem.Type.LEGGINGS, Item.Settings()),
ItemGroups.COMBAT
)
val HEX_BOOTS = registerWithGroup(
"amethyst_boots",
HexArmorItem(AmethystArmourMaterial, ArmorItem.Type.BOOTS, Item.Settings()),
ItemGroups.COMBAT
)

registerWithGroup(
"amethyst_chestplate",
HexArmorItem(AmethystArmourMaterial, ArmorItem.Type.CHESTPLATE, Item.Settings()),
ItemGroups.COMBAT
)
registerWithGroup(
"amethyst_helmet",
HexArmorItem(AmethystArmourMaterial, ArmorItem.Type.HELMET, Item.Settings()),
ItemGroups.COMBAT
)
registerWithGroup(
"amethyst_leggings",
HexArmorItem(AmethystArmourMaterial, ArmorItem.Type.LEGGINGS, Item.Settings()),
ItemGroups.COMBAT
)
registerWithGroup(
"amethyst_boots",
HexArmorItem(AmethystArmourMaterial, ArmorItem.Type.BOOTS, Item.Settings()),
ItemGroups.COMBAT
)
// val HEX_SHIELD = registerWithGroup(
// "amethyst_shield", HexShield(Item.Settings()), ItemGroups.COMBAT
// )

// registerWithGroup(
// "amethyst_shield", HexShield(Item.Settings()), ItemGroups.COMBAT
// )
}

private fun register(name: String, item: Item): Item {
private fun registerWithGroup(name: String, item: Item, group: RegistryKey<ItemGroup>): Item {
// Create the item key.
val itemKey: RegistryKey<Item> = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(MOD_ID, name))
// Register the item.
Registry.register(Registries.ITEM, itemKey, item)
return item
}

private fun registerWithGroup(name: String, item: Item, group: RegistryKey<ItemGroup>): Item {
val ri = register(name, item)
ItemGroupEvents.modifyEntriesEvent(group).register { it.add { ri } }
// Register a function to the event that adds the item
ItemGroupEvents.modifyEntriesEvent(group).register { it.add { item } }
return item
}
}
Loading