some resources and encapsulating patterns themselves as spelldata
11
src/generated/resources/.cache/cache
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
3509b8ca1efaec4a08f543c9b88708aa0b34a09b assets/hex/models/item/focus.json
|
||||||
|
13e30c053b46626bdabf9fb24baaa16268856743 assets/hex/models/item/focus_double.json
|
||||||
|
1bf03d6ad80d323bceb7c8ffabaa966a7bb6e55a assets/hex/models/item/focus_empty.json
|
||||||
|
1f428723bf0abf9a1dbaa293444fc00d7c4a4acf assets/hex/models/item/focus_entity.json
|
||||||
|
cbcff532ab24e5bf839cf157d7ffbc6697e84725 assets/hex/models/item/focus_list.json
|
||||||
|
322434f18e4d566f2ff3cbf35e766c8cc8419c69 assets/hex/models/item/focus_patterns.json
|
||||||
|
34fceb1b604be4945274183042d0be13fb49f9f3 assets/hex/models/item/focus_spell.json
|
||||||
|
76f4af83258b403304aeaad0ace5e5915df7f213 assets/hex/models/item/focus_vec3.json
|
||||||
|
ce06bdaeaf73d59c85d67e4cd8b51f54160ebef0 assets/hex/models/item/focus_widget.json
|
||||||
|
a354a2dc83d220c43d4c9f156059cbd8255e9e19 assets/hex/models/item/spellbook.json
|
||||||
|
837e7ed749afa44bd3be4114c7d81f3b8fe47852 assets/hex/models/item/wand.json
|
52
src/generated/resources/assets/hex/models/item/focus.json
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"predicate": {
|
||||||
|
"hex:datatype": -0.01
|
||||||
|
},
|
||||||
|
"model": "hex:item/focus_empty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"predicate": {
|
||||||
|
"hex:datatype": 0.99
|
||||||
|
},
|
||||||
|
"model": "hex:item/focus_entity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"predicate": {
|
||||||
|
"hex:datatype": 1.99
|
||||||
|
},
|
||||||
|
"model": "hex:item/focus_double"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"predicate": {
|
||||||
|
"hex:datatype": 2.99
|
||||||
|
},
|
||||||
|
"model": "hex:item/focus_vec3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"predicate": {
|
||||||
|
"hex:datatype": 3.99
|
||||||
|
},
|
||||||
|
"model": "hex:item/focus_spell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"predicate": {
|
||||||
|
"hex:datatype": 4.99
|
||||||
|
},
|
||||||
|
"model": "hex:item/focus_widget"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"predicate": {
|
||||||
|
"hex:datatype": 5.99
|
||||||
|
},
|
||||||
|
"model": "hex:item/focus_list"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"predicate": {
|
||||||
|
"hex:datatype": 6.99
|
||||||
|
},
|
||||||
|
"model": "hex:item/focus_patterns"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "minecraft:item/handheld",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "hex:item/focus_double"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "minecraft:item/handheld",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "hex:item/focus_empty"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "minecraft:item/handheld",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "hex:item/focus_entity"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "minecraft:item/handheld",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "hex:item/focus_list"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "minecraft:item/handheld",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "hex:item/focus_patterns"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "minecraft:item/handheld",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "hex:item/focus_spell"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "minecraft:item/handheld",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "hex:item/focus_vec3"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "minecraft:item/handheld",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "hex:item/focus_widget"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "minecraft:item/handheld",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "hex:item/spellbook"
|
||||||
|
}
|
||||||
|
}
|
6
src/generated/resources/assets/hex/models/item/wand.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "minecraft:item/handheld",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "hex:item/wand"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package at.petrak.hex;
|
package at.petrak.hex;
|
||||||
|
|
||||||
import at.petrak.hex.client.HexRenderOverlays;
|
|
||||||
import at.petrak.hex.common.items.HexItems;
|
import at.petrak.hex.common.items.HexItems;
|
||||||
import at.petrak.hex.common.network.HexMessages;
|
import at.petrak.hex.common.network.HexMessages;
|
||||||
import net.minecraftforge.common.MinecraftForge;
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
|
@ -21,7 +20,6 @@ public class HexMod {
|
||||||
var evbus = FMLJavaModLoadingContext.get().getModEventBus();
|
var evbus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||||
MinecraftForge.EVENT_BUS.register(this);
|
MinecraftForge.EVENT_BUS.register(this);
|
||||||
HexItems.ITEMS.register(evbus);
|
HexItems.ITEMS.register(evbus);
|
||||||
MinecraftForge.EVENT_BUS.register(HexRenderOverlays.class);
|
|
||||||
HexMessages.register();
|
HexMessages.register();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
package at.petrak.hex.client;
|
|
||||||
|
|
||||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
|
||||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
|
||||||
|
|
||||||
// https://github.com/gamma-delta/VCC/blob/master/src/main/java/me/gammadelta/client/VCCRenderOverlays.java⌈
|
|
||||||
public class HexRenderOverlays {
|
|
||||||
@SubscribeEvent
|
|
||||||
public static void renderOverlay(RenderGameOverlayEvent e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
41
src/main/java/at/petrak/hex/client/ShiftScrollListener.java
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package at.petrak.hex.client;
|
||||||
|
|
||||||
|
import at.petrak.hex.HexMod;
|
||||||
|
import at.petrak.hex.common.items.ItemSpellbook;
|
||||||
|
import at.petrak.hex.common.network.HexMessages;
|
||||||
|
import at.petrak.hex.common.network.MsgShiftScrollSyn;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.player.LocalPlayer;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.item.Items;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.client.event.InputEvent;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
|
||||||
|
@Mod.EventBusSubscriber(modid = HexMod.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE)
|
||||||
|
public class ShiftScrollListener {
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onScroll(InputEvent.MouseScrollEvent evt) {
|
||||||
|
HexMod.LOGGER.info("scrolling {}", evt.getScrollDelta());
|
||||||
|
LocalPlayer player = Minecraft.getInstance().player;
|
||||||
|
if (player.isCrouching()) {
|
||||||
|
InteractionHand hand = null;
|
||||||
|
if (player.getMainHandItem().getItem() != Items.AIR) {
|
||||||
|
hand = InteractionHand.MAIN_HAND;
|
||||||
|
} else if (player.getOffhandItem().getItem() != Items.AIR) {
|
||||||
|
hand = InteractionHand.OFF_HAND;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hand != null) {
|
||||||
|
var item = player.getItemInHand(hand).getItem();
|
||||||
|
if (item instanceof ItemSpellbook) {
|
||||||
|
evt.setCanceled(true);
|
||||||
|
|
||||||
|
HexMessages.getNetwork().sendToServer(new MsgShiftScrollSyn(hand, evt.getScrollDelta()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ package at.petrak.hex.common.casting
|
||||||
import at.petrak.hex.hexmath.HexPattern
|
import at.petrak.hex.hexmath.HexPattern
|
||||||
import net.minecraft.world.phys.Vec3
|
import net.minecraft.world.phys.Vec3
|
||||||
|
|
||||||
class CastException(val reason: at.petrak.hex.common.casting.CastException.Reason, vararg val data: Any) : Exception() {
|
class CastException(val reason: Reason, vararg val data: Any) : Exception() {
|
||||||
enum class Reason {
|
enum class Reason {
|
||||||
// Compilation
|
// Compilation
|
||||||
/**
|
/**
|
||||||
|
@ -36,6 +36,13 @@ class CastException(val reason: at.petrak.hex.common.casting.CastException.Reaso
|
||||||
*/
|
*/
|
||||||
NOT_ENOUGH_ARGS,
|
NOT_ENOUGH_ARGS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There are too many close parentheses.
|
||||||
|
*
|
||||||
|
* `no args`
|
||||||
|
*/
|
||||||
|
TOO_MANY_CLOSE_PARENS,
|
||||||
|
|
||||||
// Execution
|
// Execution
|
||||||
/**
|
/**
|
||||||
* Tried to interact with a vector that was too far away
|
* Tried to interact with a vector that was too far away
|
||||||
|
@ -47,10 +54,11 @@ class CastException(val reason: at.petrak.hex.common.casting.CastException.Reaso
|
||||||
|
|
||||||
override val message: String
|
override val message: String
|
||||||
get() = when (this.reason) {
|
get() = when (this.reason) {
|
||||||
at.petrak.hex.common.casting.CastException.Reason.INVALID_PATTERN -> "could not match pattern to operator: ${this.data[0] as HexPattern}"
|
Reason.INVALID_PATTERN -> "could not match pattern to operator: ${this.data[0] as HexPattern}"
|
||||||
at.petrak.hex.common.casting.CastException.Reason.INVALID_TYPE -> "cannot use ${this.data[0]} as a SpellDatum (type ${this.data[0].javaClass.typeName})"
|
Reason.INVALID_TYPE -> "cannot use ${this.data[0]} as a SpellDatum (type ${this.data[0].javaClass.typeName})"
|
||||||
at.petrak.hex.common.casting.CastException.Reason.OP_WRONG_TYPE -> "operator expected ${(this.data[0] as Class<*>).typeName} but got ${this.data[1]} (type ${this.data[1].javaClass.typeName})"
|
Reason.OP_WRONG_TYPE -> "operator expected ${(this.data[0] as Class<*>).typeName} but got ${this.data[1]} (type ${this.data[1].javaClass.typeName})"
|
||||||
at.petrak.hex.common.casting.CastException.Reason.NOT_ENOUGH_ARGS -> "required at least ${this.data[0] as Int} args on the stack but only had ${this.data[1] as Int}"
|
Reason.NOT_ENOUGH_ARGS -> "required at least ${this.data[0] as Int} args on the stack but only had ${this.data[1] as Int}"
|
||||||
at.petrak.hex.common.casting.CastException.Reason.TOO_FAR -> "tried to interact with something too far away at ${this.data[0] as Vec3}"
|
Reason.TOO_MANY_CLOSE_PARENS -> "too many close parentheses"
|
||||||
|
Reason.TOO_FAR -> "tried to interact with something too far away at ${this.data[0] as Vec3}"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,6 +13,9 @@ import net.minecraft.server.level.ServerPlayer
|
||||||
*/
|
*/
|
||||||
class CastingHarness private constructor(
|
class CastingHarness private constructor(
|
||||||
val stack: MutableList<SpellDatum<*>>,
|
val stack: MutableList<SpellDatum<*>>,
|
||||||
|
var parenCount: Int,
|
||||||
|
var parenthesized: MutableList<HexPattern>,
|
||||||
|
var escapeNext: Boolean,
|
||||||
val ctx: CastingContext,
|
val ctx: CastingContext,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
|
@ -21,20 +24,93 @@ class CastingHarness private constructor(
|
||||||
*/
|
*/
|
||||||
fun update(newPat: HexPattern): CastResult {
|
fun update(newPat: HexPattern): CastResult {
|
||||||
return try {
|
return try {
|
||||||
val operator = SpellOperator.fromPattern(newPat)
|
var exn: CastException? = null
|
||||||
HexMod.LOGGER.info("Executing operator: $operator")
|
val operator = try {
|
||||||
operator.modifyStack(this.stack, this.ctx)
|
SpellOperator.fromPattern(newPat)
|
||||||
HexMod.LOGGER.info("Modified stack: ${this.stack}")
|
} catch (e: CastException) {
|
||||||
|
exn = e
|
||||||
if (this.stack.isEmpty()) {
|
null
|
||||||
return CastResult.QuitCasting
|
|
||||||
}
|
}
|
||||||
|
if (this.parenCount > 0) {
|
||||||
|
if (this.escapeNext) {
|
||||||
|
this.escapeNext = false
|
||||||
|
this.parenthesized.add(newPat)
|
||||||
|
HexMod.LOGGER.info("Escaping onto parenthesized")
|
||||||
|
} else if (operator == SpellWidget.ESCAPE) {
|
||||||
|
this.escapeNext = true
|
||||||
|
} else if (operator == SpellWidget.OPEN_PAREN) {
|
||||||
|
// we have escaped the parens onto the stack; we just also record our count.
|
||||||
|
this.parenthesized.add(newPat)
|
||||||
|
this.parenCount++
|
||||||
|
} else if (operator == SpellWidget.CLOSE_PAREN) {
|
||||||
|
this.parenCount--
|
||||||
|
if (this.parenCount == 0) {
|
||||||
|
HexMod.LOGGER.info("Finished parenthesizing things")
|
||||||
|
this.stack.add(SpellDatum.make(this.parenthesized.map { SpellDatum.make(it) }))
|
||||||
|
} else if (this.parenCount < 0) {
|
||||||
|
throw CastException(CastException.Reason.TOO_MANY_CLOSE_PARENS)
|
||||||
|
} else {
|
||||||
|
// we have this situation: "(()"
|
||||||
|
// we need to add the close paren
|
||||||
|
this.parenthesized.add(newPat)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.parenthesized.add(newPat)
|
||||||
|
}
|
||||||
|
} else if (this.escapeNext) {
|
||||||
|
this.escapeNext = false
|
||||||
|
HexMod.LOGGER.info("Escaping onto stack")
|
||||||
|
this.stack.add(SpellDatum.make(newPat))
|
||||||
|
} else {
|
||||||
|
// Plain ol operator
|
||||||
|
val sig = newPat.anglesSignature()
|
||||||
|
if (sig.startsWith("aqaa") || sig.startsWith("dedd")) {
|
||||||
|
val negate = sig.startsWith("dedd")
|
||||||
|
var accumulator = 0.0
|
||||||
|
for (chr in sig.substring(4)) {
|
||||||
|
when (chr) {
|
||||||
|
'w' -> accumulator += 1
|
||||||
|
'q' -> accumulator += 5
|
||||||
|
'e' -> accumulator += 10
|
||||||
|
'a' -> accumulator *= 2
|
||||||
|
'd' -> accumulator /= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (negate) {
|
||||||
|
accumulator = -accumulator
|
||||||
|
}
|
||||||
|
this.stack.add(SpellDatum.make(accumulator))
|
||||||
|
} else if (exn != null) {
|
||||||
|
// there was a problem finding the pattern and it was NOT due to numbers
|
||||||
|
throw exn
|
||||||
|
} else if (operator == SpellWidget.OPEN_PAREN) {
|
||||||
|
this.parenCount++
|
||||||
|
} else if (operator == SpellWidget.CLOSE_PAREN) {
|
||||||
|
throw CastException(CastException.Reason.TOO_MANY_CLOSE_PARENS)
|
||||||
|
} else {
|
||||||
|
// we know the operator is ok here
|
||||||
|
operator!!.modifyStack(this.stack, this.ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.parenCount > 0) {
|
||||||
|
HexMod.LOGGER.info("Paren level ${this.parenCount}; ${this.parenthesized}")
|
||||||
|
}
|
||||||
|
HexMod.LOGGER.info("New stack: ${this.stack}")
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
if (this.parenCount == 0) {
|
||||||
|
CastResult.QuitCasting
|
||||||
|
} else {
|
||||||
|
CastResult.Nothing
|
||||||
|
}
|
||||||
|
} else {
|
||||||
val maybeSpell = this.stack[0]
|
val maybeSpell = this.stack[0]
|
||||||
if (this.stack.size == 1 && maybeSpell.payload is RenderedSpell) {
|
if (this.stack.size == 1 && maybeSpell.payload is RenderedSpell) {
|
||||||
CastResult.Success(maybeSpell.payload)
|
CastResult.Success(maybeSpell.payload)
|
||||||
} else {
|
} else {
|
||||||
CastResult.Nothing
|
CastResult.Nothing
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e: CastException) {
|
} catch (e: CastException) {
|
||||||
CastResult.Error(e)
|
CastResult.Error(e)
|
||||||
}
|
}
|
||||||
|
@ -48,8 +124,13 @@ class CastingHarness private constructor(
|
||||||
stackTag.add(datum.serializeToNBT())
|
stackTag.add(datum.serializeToNBT())
|
||||||
out.put(TAG_STACK, stackTag)
|
out.put(TAG_STACK, stackTag)
|
||||||
|
|
||||||
val pointsTag = ListTag()
|
out.putInt(TAG_PAREN_COUNT, this.parenCount)
|
||||||
out.put(TAG_POINTS, pointsTag)
|
out.putBoolean(TAG_ESCAPE_NEXT, this.escapeNext)
|
||||||
|
|
||||||
|
val parensTag = ListTag()
|
||||||
|
for (pat in this.parenthesized)
|
||||||
|
parensTag.add(pat.serializeToNBT())
|
||||||
|
out.put(TAG_PARENTHESIZED, parensTag)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
@ -57,6 +138,9 @@ class CastingHarness private constructor(
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG_STACK = "stack"
|
const val TAG_STACK = "stack"
|
||||||
const val TAG_POINTS = "points"
|
const val TAG_POINTS = "points"
|
||||||
|
const val TAG_PAREN_COUNT = "open_parens"
|
||||||
|
const val TAG_PARENTHESIZED = "parenthesized"
|
||||||
|
const val TAG_ESCAPE_NEXT = "escape_next"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun DeserializeFromNBT(nbt: Tag?, caster: ServerPlayer): CastingHarness {
|
fun DeserializeFromNBT(nbt: Tag?, caster: ServerPlayer): CastingHarness {
|
||||||
|
@ -71,12 +155,24 @@ class CastingHarness private constructor(
|
||||||
stack.add(datum)
|
stack.add(datum)
|
||||||
}
|
}
|
||||||
|
|
||||||
CastingHarness(stack, ctx)
|
val parenthesized = mutableListOf<HexPattern>()
|
||||||
|
val parenTag = nbt.getList(TAG_PARENTHESIZED, Tag.TAG_COMPOUND.toInt())
|
||||||
|
for (subtag in parenTag) {
|
||||||
|
parenthesized.add(HexPattern.DeserializeFromNBT(subtag as CompoundTag))
|
||||||
|
}
|
||||||
|
|
||||||
|
val parenCount = nbt.getInt(TAG_PAREN_COUNT)
|
||||||
|
val escapeNext = nbt.getBoolean(TAG_ESCAPE_NEXT)
|
||||||
|
|
||||||
|
CastingHarness(stack, parenCount, parenthesized, escapeNext, ctx)
|
||||||
} catch (exn: Exception) {
|
} catch (exn: Exception) {
|
||||||
HexMod.LOGGER.warn("Couldn't load harness from nbt tag, falling back to default: $nbt: $exn")
|
HexMod.LOGGER.warn("Couldn't load harness from nbt tag, falling back to default: $nbt: $exn")
|
||||||
CastingHarness(mutableListOf(), ctx)
|
Default(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun Default(ctx: CastingContext): CastingHarness =
|
||||||
|
CastingHarness(mutableListOf(), 0, mutableListOf(), false, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class CastResult {
|
sealed class CastResult {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import net.minecraft.world.phys.Vec3
|
||||||
* * [Vec3][net.minecraft.world.phys.Vec3] as both position and (when normalized) direction
|
* * [Vec3][net.minecraft.world.phys.Vec3] as both position and (when normalized) direction
|
||||||
* * [RenderedSpell]
|
* * [RenderedSpell]
|
||||||
* * [SpellWidget]; [SpellWidget.NULL] is used as our null value
|
* * [SpellWidget]; [SpellWidget.NULL] is used as our null value
|
||||||
* * [ArrayList<SpellDatum<*>>][ArrayList]
|
* * [List<SpellDatum<*>>][List]
|
||||||
* * [HexPattern]! Yes, we have meta-evaluation everyone.
|
* * [HexPattern]! Yes, we have meta-evaluation everyone.
|
||||||
* The constructor guarantees we won't pass a type that isn't one of those types.
|
* The constructor guarantees we won't pass a type that isn't one of those types.
|
||||||
*
|
*
|
||||||
|
@ -87,9 +87,15 @@ class SpellDatum<T : Any> private constructor(val payload: T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun <T : Any> make(payload: T): SpellDatum<T> =
|
fun make(payload: Any): SpellDatum<*> =
|
||||||
if (!IsValidType(payload)) {
|
if (!IsValidType(payload)) {
|
||||||
|
// Check to see if it's a java boxed double
|
||||||
|
if (payload is java.lang.Double) {
|
||||||
|
val num = payload.toDouble()
|
||||||
|
SpellDatum(if (num.isFinite()) num else 0.0)
|
||||||
|
} else {
|
||||||
throw CastException(CastException.Reason.INVALID_TYPE, payload)
|
throw CastException(CastException.Reason.INVALID_TYPE, payload)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
SpellDatum(payload)
|
SpellDatum(payload)
|
||||||
}
|
}
|
||||||
|
@ -148,9 +154,9 @@ class SpellDatum<T : Any> private constructor(val payload: T) {
|
||||||
Double::class.java,
|
Double::class.java,
|
||||||
Vec3::class.java,
|
Vec3::class.java,
|
||||||
RenderedSpell::class.java,
|
RenderedSpell::class.java,
|
||||||
ArrayList::class.java,
|
List::class.java,
|
||||||
SpellWidget::class.java,
|
SpellWidget::class.java,
|
||||||
PatternList::class.java,
|
HexPattern::class.java,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mapping of string keys for spell implers to their implementation
|
// Mapping of string keys for spell implers to their implementation
|
||||||
|
@ -168,8 +174,10 @@ class SpellDatum<T : Any> private constructor(val payload: T) {
|
||||||
const val TAG_PATTERN = "pattern"
|
const val TAG_PATTERN = "pattern"
|
||||||
|
|
||||||
fun <T : Any> IsValidType(checkee: T): Boolean =
|
fun <T : Any> IsValidType(checkee: T): Boolean =
|
||||||
if (checkee is ArrayList<*>) {
|
if (checkee is List<*>) {
|
||||||
checkee.all { it is SpellDatum<*> && IsValidType(it) }
|
// note it should be impossible to pass a spell datum that doesn't contain a valid type,
|
||||||
|
// but we best make sure.
|
||||||
|
checkee.all { it is SpellDatum<*> && IsValidType(it.payload) }
|
||||||
} else {
|
} else {
|
||||||
ValidTypes.any { clazz -> clazz.isAssignableFrom(checkee.javaClass) }
|
ValidTypes.any { clazz -> clazz.isAssignableFrom(checkee.javaClass) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package at.petrak.hex.common.casting
|
package at.petrak.hex.common.casting
|
||||||
|
|
||||||
import at.petrak.hex.common.casting.operators.*
|
import at.petrak.hex.common.casting.operators.*
|
||||||
|
import at.petrak.hex.common.casting.operators.spells.OpAddMotion
|
||||||
import at.petrak.hex.common.casting.operators.spells.OpExplode
|
import at.petrak.hex.common.casting.operators.spells.OpExplode
|
||||||
import at.petrak.hex.common.casting.operators.spells.OpPrint
|
import at.petrak.hex.common.casting.operators.spells.OpPrint
|
||||||
import at.petrak.hex.hexmath.HexPattern
|
import at.petrak.hex.hexmath.HexPattern
|
||||||
|
@ -23,6 +24,7 @@ interface SpellOperator {
|
||||||
companion object {
|
companion object {
|
||||||
val PatternMap: Map<String, SpellOperator> = mapOf(
|
val PatternMap: Map<String, SpellOperator> = mapOf(
|
||||||
// == Getters ==
|
// == Getters ==
|
||||||
|
|
||||||
// diamond shape to get the caster
|
// diamond shape to get the caster
|
||||||
"qaq" to OpGetCaster,
|
"qaq" to OpGetCaster,
|
||||||
"ede" to OpGetCaster,
|
"ede" to OpGetCaster,
|
||||||
|
@ -41,6 +43,7 @@ interface SpellOperator {
|
||||||
"weaqa" to OpEntityRaycast,
|
"weaqa" to OpEntityRaycast,
|
||||||
|
|
||||||
// == Modify Stack ==
|
// == Modify Stack ==
|
||||||
|
|
||||||
// CCW hook for undo
|
// CCW hook for undo
|
||||||
"a" to OpUndo,
|
"a" to OpUndo,
|
||||||
// and CW for null
|
// and CW for null
|
||||||
|
@ -57,6 +60,15 @@ interface SpellOperator {
|
||||||
"aq" to OpPrint,
|
"aq" to OpPrint,
|
||||||
// nuclear sign for explosion
|
// nuclear sign for explosion
|
||||||
"aawaawaa" to OpExplode,
|
"aawaawaa" to OpExplode,
|
||||||
|
"weeewdq" to OpAddMotion,
|
||||||
|
|
||||||
|
// == Meta stuff ==
|
||||||
|
"qqq" to SpellWidget.OPEN_PAREN,
|
||||||
|
"eee" to SpellWidget.CLOSE_PAREN,
|
||||||
|
"aaaqw" to SpellWidget.ESCAPE,
|
||||||
|
// http://www.toroidalsnark.net/mkss3-pix/CalderheadJMM2014.pdf
|
||||||
|
// eval being a space filling curve feels apt doesn't it
|
||||||
|
"deaqq" to OpEval
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,14 +90,8 @@ interface SpellOperator {
|
||||||
* Try to get a value of the given type.
|
* Try to get a value of the given type.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
inline fun <reified T : Any> List<SpellDatum<*>>.getChecked(idx: Int): T {
|
inline fun <reified T : Any> List<SpellDatum<*>>.getChecked(idx: Int): T =
|
||||||
val datum = this[idx]
|
this[idx].tryGet()
|
||||||
val casted = datum.tryGet<T>()
|
|
||||||
return if (casted is Double && !casted.isFinite())
|
|
||||||
0.0 as T
|
|
||||||
else
|
|
||||||
casted
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the value at the given index is OK. Will throw an error otherwise.
|
* Check if the value at the given index is OK. Will throw an error otherwise.
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package at.petrak.hex.common.casting.operators
|
||||||
|
|
||||||
|
import at.petrak.hex.common.casting.CastingContext
|
||||||
|
import at.petrak.hex.common.casting.CastingHarness
|
||||||
|
import at.petrak.hex.common.casting.SpellDatum
|
||||||
|
import at.petrak.hex.common.casting.SpellOperator.Companion.getChecked
|
||||||
|
|
||||||
|
object OpEval : SimpleOperator {
|
||||||
|
override val argc: Int
|
||||||
|
get() = 1
|
||||||
|
|
||||||
|
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {
|
||||||
|
val instrs: List<SpellDatum<*>> = args.getChecked(0)
|
||||||
|
|
||||||
|
val harness = CastingHarness.Default(ctx)
|
||||||
|
for (pat in instrs) {
|
||||||
|
val res = harness.update(pat.tryGet())
|
||||||
|
if (res is CastingHarness.CastResult.Error) {
|
||||||
|
throw res.exn
|
||||||
|
}
|
||||||
|
// in ANY OTHER CASE JUST KEEP GOING
|
||||||
|
// including if there's RenderedSpells on the stack or the stack becomes clear
|
||||||
|
}
|
||||||
|
return harness.stack
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package at.petrak.hex.common.casting.operators.spells
|
||||||
|
|
||||||
|
import at.petrak.hex.common.casting.CastingContext
|
||||||
|
import at.petrak.hex.common.casting.RenderedSpell
|
||||||
|
import at.petrak.hex.common.casting.RenderedSpellImpl
|
||||||
|
import at.petrak.hex.common.casting.SpellDatum
|
||||||
|
import at.petrak.hex.common.casting.SpellOperator.Companion.getChecked
|
||||||
|
import at.petrak.hex.common.casting.SpellOperator.Companion.spellListOf
|
||||||
|
import at.petrak.hex.common.casting.operators.SimpleOperator
|
||||||
|
import net.minecraft.world.entity.Entity
|
||||||
|
import net.minecraft.world.phys.Vec3
|
||||||
|
|
||||||
|
object OpAddMotion : SimpleOperator, RenderedSpellImpl {
|
||||||
|
override val argc: Int
|
||||||
|
get() = 2
|
||||||
|
|
||||||
|
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {
|
||||||
|
val target = args.getChecked<Entity>(0)
|
||||||
|
val motion = args.getChecked<Vec3>(1)
|
||||||
|
return spellListOf(RenderedSpell(OpAddMotion, spellListOf(target, motion)))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cast(args: List<SpellDatum<*>>, ctx: CastingContext) {
|
||||||
|
val target = args.getChecked<Entity>(0)
|
||||||
|
val motion = args.getChecked<Vec3>(1)
|
||||||
|
target.deltaMovement = target.deltaMovement.add(motion)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,8 @@ public class HexItems {
|
||||||
() -> new ItemWand(unstackable()));
|
() -> new ItemWand(unstackable()));
|
||||||
public static final RegistryObject<Item> FOCUS = ITEMS.register(LibItemNames.FOCUS,
|
public static final RegistryObject<Item> FOCUS = ITEMS.register(LibItemNames.FOCUS,
|
||||||
() -> new ItemFocus(props()));
|
() -> new ItemFocus(props()));
|
||||||
|
public static final RegistryObject<Item> SPELLBOOK = ITEMS.register(LibItemNames.SPELLBOOK,
|
||||||
|
() -> new ItemSpellbook(unstackable()));
|
||||||
|
|
||||||
public static Item.Properties props() {
|
public static Item.Properties props() {
|
||||||
return new Item.Properties();
|
return new Item.Properties();
|
||||||
|
|
64
src/main/java/at/petrak/hex/common/items/ItemSpellbook.java
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package at.petrak.hex.common.items;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.network.chat.TextComponent;
|
||||||
|
import net.minecraft.network.chat.TranslatableComponent;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.TooltipFlag;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class ItemSpellbook extends Item {
|
||||||
|
public static String TAG_SELECTED_PAGE = "page_idx";
|
||||||
|
// this is a CompoundTag of string numerical keys to SpellData
|
||||||
|
public static String TAG_PAGES = "pages";
|
||||||
|
|
||||||
|
public ItemSpellbook(Properties properties) {
|
||||||
|
super(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendHoverText(ItemStack stack, @Nullable Level level, List<Component> tooltip,
|
||||||
|
TooltipFlag isAdvanced) {
|
||||||
|
var tag = stack.getOrCreateTag();
|
||||||
|
if (tag.contains(TAG_SELECTED_PAGE)) {
|
||||||
|
var pageIdx = tag.getInt(TAG_SELECTED_PAGE);
|
||||||
|
var pages = tag.getCompound(ItemSpellbook.TAG_PAGES);
|
||||||
|
tooltip.add(new TranslatableComponent("hex.spellbook.tooltip.page", pageIdx, HighestPage(pages)));
|
||||||
|
|
||||||
|
var key = String.valueOf(pageIdx);
|
||||||
|
if (pages.contains(key)) {
|
||||||
|
var datum = pages.getCompound(String.valueOf(pageIdx));
|
||||||
|
// I know this is ugly i dont care
|
||||||
|
tooltip.add(new TextComponent(datum.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int HighestPage(CompoundTag tag) {
|
||||||
|
var highestKey = tag.getAllKeys().stream().flatMap(s -> {
|
||||||
|
try {
|
||||||
|
return Stream.of(Integer.parseInt(s));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
}).max(Integer::compare);
|
||||||
|
return highestKey.orElse(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RotatePageIdx(CompoundTag tag, boolean increase) {
|
||||||
|
int newIdx;
|
||||||
|
if (tag.contains(ItemSpellbook.TAG_SELECTED_PAGE)) {
|
||||||
|
var delta = increase ? 1 : -1;
|
||||||
|
newIdx = Math.max(0, tag.getInt(ItemSpellbook.TAG_SELECTED_PAGE) + delta);
|
||||||
|
} else {
|
||||||
|
newIdx = 0;
|
||||||
|
}
|
||||||
|
tag.putInt(ItemSpellbook.TAG_SELECTED_PAGE, newIdx);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,4 +3,5 @@ package at.petrak.hex.common.lib;
|
||||||
public class LibItemNames {
|
public class LibItemNames {
|
||||||
public static final String WAND = "wand";
|
public static final String WAND = "wand";
|
||||||
public static final String FOCUS = "focus";
|
public static final String FOCUS = "focus";
|
||||||
|
public static final String SPELLBOOK = "spellbook";
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,5 +27,7 @@ public class HexMessages {
|
||||||
MsgNewSpellPatternAck::deserialize, MsgNewSpellPatternAck::handle);
|
MsgNewSpellPatternAck::deserialize, MsgNewSpellPatternAck::handle);
|
||||||
NETWORK.registerMessage(messageIdx++, MsgQuitSpellcasting.class, MsgQuitSpellcasting::serialize,
|
NETWORK.registerMessage(messageIdx++, MsgQuitSpellcasting.class, MsgQuitSpellcasting::serialize,
|
||||||
MsgQuitSpellcasting::deserialize, MsgQuitSpellcasting::handle);
|
MsgQuitSpellcasting::deserialize, MsgQuitSpellcasting::handle);
|
||||||
|
NETWORK.registerMessage(messageIdx++, MsgShiftScrollSyn.class, MsgShiftScrollSyn::serialize,
|
||||||
|
MsgShiftScrollSyn::deserialize, MsgShiftScrollSyn::handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ public record MsgNewSpellPatternSyn(InteractionHand handUsed, HexPattern pattern
|
||||||
if (res instanceof CastResult.Success success) {
|
if (res instanceof CastResult.Success success) {
|
||||||
success.getSpell().cast(new CastingContext(sender));
|
success.getSpell().cast(new CastingContext(sender));
|
||||||
} else if (res instanceof CastResult.Error error) {
|
} else if (res instanceof CastResult.Error error) {
|
||||||
sender.sendMessage(new TextComponent(error.getExn().toString()), Util.NIL_UUID);
|
sender.sendMessage(new TextComponent(error.getExn().getMessage()), Util.NIL_UUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean quit;
|
boolean quit;
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
package at.petrak.hex.common.network;
|
||||||
|
|
||||||
|
import at.petrak.hex.common.items.ItemSpellbook;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.network.chat.TranslatableComponent;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraftforge.network.NetworkEvent;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sent client->server when the client shift+scrolls with a shift-scrollable item.
|
||||||
|
*/
|
||||||
|
public record MsgShiftScrollSyn(InteractionHand hand, double scrollDelta) {
|
||||||
|
public static MsgShiftScrollSyn deserialize(ByteBuf buffer) {
|
||||||
|
var buf = new FriendlyByteBuf(buffer);
|
||||||
|
var hand = InteractionHand.values()[buf.readInt()];
|
||||||
|
var scrollDelta = buf.readDouble();
|
||||||
|
return new MsgShiftScrollSyn(hand, scrollDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void serialize(ByteBuf buffer) {
|
||||||
|
var buf = new FriendlyByteBuf(buffer);
|
||||||
|
buf.writeInt(this.hand.ordinal());
|
||||||
|
buf.writeDouble(this.scrollDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||||
|
ctx.get().enqueueWork(() -> {
|
||||||
|
ServerPlayer sender = ctx.get().getSender();
|
||||||
|
if (sender != null) {
|
||||||
|
var stack = sender.getItemInHand(hand);
|
||||||
|
|
||||||
|
if (stack.getItem() instanceof ItemSpellbook) {
|
||||||
|
spellbook(sender, stack);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ctx.get().setPacketHandled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spellbook(ServerPlayer sender, ItemStack stack) {
|
||||||
|
var tag = stack.getOrCreateTag();
|
||||||
|
ItemSpellbook.RotatePageIdx(tag, this.scrollDelta < 0.0);
|
||||||
|
|
||||||
|
var newIdx = tag.getInt(ItemSpellbook.TAG_SELECTED_PAGE);
|
||||||
|
var len = ItemSpellbook.HighestPage(tag.getCompound(ItemSpellbook.TAG_PAGES));
|
||||||
|
sender.displayClientMessage(new TranslatableComponent("hex.spellbook.tooltip.page", newIdx, len), true);
|
||||||
|
}
|
||||||
|
}
|
22
src/main/java/at/petrak/hex/datagen/DataGenerators.java
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package at.petrak.hex.datagen;
|
||||||
|
|
||||||
|
import net.minecraft.data.DataGenerator;
|
||||||
|
import net.minecraftforge.common.data.ExistingFileHelper;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.forge.event.lifecycle.GatherDataEvent;
|
||||||
|
|
||||||
|
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||||
|
public class DataGenerators {
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void generateData(GatherDataEvent ev) {
|
||||||
|
DataGenerator gen = ev.getGenerator();
|
||||||
|
ExistingFileHelper efh = ev.getExistingFileHelper();
|
||||||
|
if (ev.includeClient()) {
|
||||||
|
gen.addProvider(new ItemModels(gen, efh));
|
||||||
|
}
|
||||||
|
if (ev.includeServer()) {
|
||||||
|
// recipes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,19 +6,21 @@ import at.petrak.hex.common.items.ItemFocus;
|
||||||
import net.minecraft.data.DataGenerator;
|
import net.minecraft.data.DataGenerator;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraftforge.client.model.generators.ItemModelProvider;
|
||||||
import net.minecraftforge.client.model.generators.ModelFile;
|
import net.minecraftforge.client.model.generators.ModelFile;
|
||||||
import net.minecraftforge.common.data.ExistingFileHelper;
|
import net.minecraftforge.common.data.ExistingFileHelper;
|
||||||
|
|
||||||
public class ItemModelProvider extends net.minecraftforge.client.model.generators.ItemModelProvider {
|
public class ItemModels extends ItemModelProvider {
|
||||||
public ItemModelProvider(DataGenerator generator, ExistingFileHelper existingFileHelper) {
|
public ItemModels(DataGenerator generator, ExistingFileHelper existingFileHelper) {
|
||||||
super(generator, HexMod.MOD_ID, existingFileHelper);
|
super(generator, HexMod.MOD_ID, existingFileHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void registerModels() {
|
protected void registerModels() {
|
||||||
simpleItem(HexItems.WAND.get());
|
simpleItem(HexItems.WAND.get());
|
||||||
|
simpleItem(HexItems.SPELLBOOK.get());
|
||||||
|
|
||||||
simpleItem(modLoc("focus_none"));
|
simpleItem(modLoc("focus_empty"));
|
||||||
simpleItem(modLoc("focus_entity"));
|
simpleItem(modLoc("focus_entity"));
|
||||||
simpleItem(modLoc("focus_double"));
|
simpleItem(modLoc("focus_double"));
|
||||||
simpleItem(modLoc("focus_vec3"));
|
simpleItem(modLoc("focus_vec3"));
|
||||||
|
@ -29,7 +31,7 @@ public class ItemModelProvider extends net.minecraftforge.client.model.generator
|
||||||
getBuilder(HexItems.FOCUS.get().getRegistryName().getPath())
|
getBuilder(HexItems.FOCUS.get().getRegistryName().getPath())
|
||||||
.override()
|
.override()
|
||||||
.predicate(ItemFocus.PREDICATE, -0.01f)
|
.predicate(ItemFocus.PREDICATE, -0.01f)
|
||||||
.model(new ModelFile.UncheckedModelFile(modLoc("item/focus_none")))
|
.model(new ModelFile.UncheckedModelFile(modLoc("item/focus_empty")))
|
||||||
.end()
|
.end()
|
||||||
.override()
|
.override()
|
||||||
.predicate(ItemFocus.PREDICATE, 1 - 0.01f)
|
.predicate(ItemFocus.PREDICATE, 1 - 0.01f)
|
7
src/main/resources/assets/hex/lang/en_US.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"item.hex.wand": "Staff",
|
||||||
|
"item.hex.focus": "Focus",
|
||||||
|
"item.hex.spellbook": "Spellbook",
|
||||||
|
"hex.spellbook.tooltip.page": "Selected Page %d/%d",
|
||||||
|
"hex.spelldata.desc.entity": "Entity %s"
|
||||||
|
}
|
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 421 B |
Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 413 B |
Before Width: | Height: | Size: 414 B After Width: | Height: | Size: 414 B |
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 429 B |
Before Width: | Height: | Size: 416 B After Width: | Height: | Size: 416 B |
Before Width: | Height: | Size: 420 B After Width: | Height: | Size: 420 B |
Before Width: | Height: | Size: 420 B After Width: | Height: | Size: 420 B |
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 421 B |
BIN
src/main/resources/assets/hex/textures/item/spellbook.png
Normal file
After Width: | Height: | Size: 438 B |
Before Width: | Height: | Size: 261 B After Width: | Height: | Size: 261 B |