3 new spells and fix a bunch of bugs, closes #1

This commit is contained in:
gamma-delta 2021-12-30 16:00:20 -06:00
parent 1a3faecae9
commit 11042f3008
46 changed files with 698 additions and 156 deletions

21
original_ideas_doc.txt Normal file
View file

@ -0,0 +1,21 @@
PieceTrickBlaze.java
PieceTrickBreakLoop.java
PieceTrickChangeSlot.java
PieceTrickDebug.java
PieceTrickDebugSpamless.java
PieceTrickDelay.java
PieceTrickDetonate.java
PieceTrickDie.java
PieceTrickEidosAnchor.java
PieceTrickEidosReversal.java
PieceTrickEvaluate.java
PieceTrickExplode.java
PieceTrickOvergrow.java
PieceTrickParticleTrail.java
PieceTrickPlaySound.java
PieceTrickRussianRoulette.java
PieceTrickSaveVector.java
PieceTrickSmite.java
PieceTrickSpinChamber.java
PieceTrickSwitchTargetSlot.java
PieceTrickTorrent.java

View file

@ -1,2 +1,42 @@
package at.petrak.hex;public class HexConfig {
package at.petrak.hex;
import net.minecraft.world.item.Tier;
import net.minecraft.world.item.Tiers;
import net.minecraftforge.common.ForgeConfigSpec;
public class HexConfig {
public final ForgeConfigSpec.IntValue maxRecurseDepth;
public final ForgeConfigSpec.IntValue wandRechargeRate;
public final ForgeConfigSpec.DoubleValue healthToManaRate;
public final ForgeConfigSpec.IntValue opBreakHarvestLevel;
public HexConfig(ForgeConfigSpec.Builder builder) {
maxRecurseDepth = builder.comment("How many times an Eval can recursively cast itself")
.defineInRange("maxRecurseDepth", 15, 0, Integer.MAX_VALUE);
wandRechargeRate = builder.comment("How many mana points a wand recharges per tick")
.defineInRange("wandRechargeRate", 2, 0, Integer.MAX_VALUE);
healthToManaRate = builder.comment("How many points of mana a half-heart is worth when casting from HP")
.defineInRange("healthToManaRate", 10.0, 0.0, 1_000_000.0);
builder.push("spells");
opBreakHarvestLevel = builder.comment(
"The harvest level of the Break Block spell.",
"0 = wood, 1 = stone, 2 = iron, 3 = diamond, 4 = netherite."
).defineInRange("opBreakHarvestLevel", 3, 0, 4);
builder.pop();
}
/**
* i'm not kidding look upon net.minecraftforge.common.TierSortingRegistry and weep
*/
public Tier getOpBreakHarvestLevelBecauseForgeThoughtItWasAGoodIdeaToImplementHarvestTiersUsingAnHonestToGodTopoSort() {
return switch (this.opBreakHarvestLevel.get()) {
case 0 -> Tiers.WOOD;
case 1 -> Tiers.STONE;
case 2 -> Tiers.IRON;
case 3 -> Tiers.DIAMOND;
case 4 -> Tiers.NETHERITE;
default -> throw new RuntimeException("unreachable");
};
}
}

View file

@ -6,12 +6,12 @@ import at.petrak.hex.common.casting.SpellDatum;
import at.petrak.hex.common.casting.SpellWidget;
import at.petrak.hex.common.casting.operators.*;
import at.petrak.hex.common.casting.operators.math.*;
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.OpPrint;
import at.petrak.hex.common.casting.operators.spells.*;
import at.petrak.hex.common.items.HexItems;
import at.petrak.hex.common.network.HexMessages;
import at.petrak.hex.server.TickScheduler;
import com.mojang.datafixers.util.Pair;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@ -24,6 +24,14 @@ import org.apache.logging.log4j.Logger;
public class HexMod {
// hmm today I will use a popular logging framework :clueless:
public static final Logger LOGGER = LogManager.getLogger();
public static final HexConfig CONFIG;
public static final ForgeConfigSpec CONFIG_SPEC;
static {
final var specPair = new ForgeConfigSpec.Builder().configure(HexConfig::new);
CONFIG = specPair.getLeft();
CONFIG_SPEC = specPair.getRight();
}
public static final String MOD_ID = "hex";
@ -32,8 +40,10 @@ public class HexMod {
var evbus = FMLJavaModLoadingContext.get().getModEventBus();
MinecraftForge.EVENT_BUS.register(this);
evbus.register(HexMod.class);
HexItems.ITEMS.register(evbus);
HexMessages.register();
MinecraftForge.EVENT_BUS.register(TickScheduler.INSTANCE);
}
// I guess this means the client will have a big empty map for patterns
@ -44,32 +54,22 @@ public class HexMod {
for (Pair<String, SpellOperator> p : new Pair[]{
// == Getters ==
// diamond shape to get the caster
new Pair<>("qaq", OpGetCaster.INSTANCE),
new Pair<>("ede", OpGetCaster.INSTANCE),
// small triangle to get the entity pos
new Pair<>("aa", OpEntityPos.INSTANCE),
new Pair<>("dd", OpEntityPos.INSTANCE),
// Arrow to get the look vector
new Pair<>("wa", OpEntityLook.INSTANCE),
new Pair<>("wd", OpEntityLook.INSTANCE),
// CCW battleaxe for block raycast
new Pair<>("wqaawdd", OpBlockRaycast.INSTANCE),
// and CW for axis raycast
new Pair<>("weddwaa", OpBlockAxisRaycast.INSTANCE),
// CCW diamond mace thing for entity raycast
new Pair<>("weaqa", OpEntityRaycast.INSTANCE),
// == Modify Stack ==
// CCW hook for undo
new Pair<>("a", OpUndo.INSTANCE),
// and CW for null
new Pair<>("d", SpellWidget.NULL),
// Two triangles holding hands to duplicate
new Pair<>("aadaa", OpDuplicate.INSTANCE),
// Two opposing triangles to swap
new Pair<>("aawdd", OpSwap.INSTANCE),
// == Math ==
@ -82,12 +82,13 @@ public class HexMod {
// == Spells ==
// hook for debug
new Pair<>("de", OpPrint.INSTANCE),
new Pair<>("aq", OpPrint.INSTANCE),
// nuclear sign for explosion
new Pair<>("aawaawaa", OpExplode.INSTANCE),
new Pair<>("weeewdq", OpAddMotion.INSTANCE),
new Pair<>("qaqqqqq", OpPlaceBlock.INSTANCE),
new Pair<>("eeeeede", OpBreakBlock.INSTANCE),
new Pair<>("waadwawdaaweewq", OpLightning.INSTANCE),
// == Meta stuff ==
new Pair<>("qqq", SpellWidget.OPEN_PAREN),
@ -96,8 +97,8 @@ public class HexMod {
// http://www.toroidalsnark.net/mkss3-pix/CalderheadJMM2014.pdf
// eval being a space filling curve feels apt doesn't it
new Pair<>("deaqq", OpEval.INSTANCE),
new Pair<>("aqqqqq", OpReadFromSpellbook.INSTANCE),
new Pair<>("deeeee", OpWriteToSpellbook.INSTANCE),
new Pair<>("aqqqqq", OpRead.INSTANCE),
new Pair<>("deeeee", OpWrite.INSTANCE),
}) {
PatternRegistry.addRegularPattern(p.getFirst(), p.getSecond());
count++;

View file

@ -7,7 +7,7 @@ import at.petrak.hex.common.casting.SpellDatum
/**
* A SimpleOperator that always costs the same amount of mana.
*/
interface SimpleFreeOperator : SpellOperator {
interface ConstManaOperator : SpellOperator {
val argc: Int
val manaCost: Int
get() = 0

View file

@ -10,16 +10,17 @@ import at.petrak.hex.common.casting.SpellDatum
*/
interface SimpleOperator : SpellOperator {
val argc: Int
fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>>
fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<List<SpellDatum<*>>, Int>
override fun modifyStack(stack: MutableList<SpellDatum<*>>, ctx: CastingContext) {
override fun modifyStack(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): Int {
if (this.argc > stack.size)
throw CastException(CastException.Reason.NOT_ENOUGH_ARGS, this.argc, stack.size)
val args = stack.takeLast(this.argc)
// there's gotta be a better way to do this
for (_idx in 0 until this.argc)
stack.removeLast()
val newData = this.execute(args, ctx)
val (newData, mana) = this.execute(args, ctx)
stack.addAll(newData)
return mana
}
}

View file

@ -1,6 +1,5 @@
package at.petrak.hex.api
import at.petrak.hex.common.casting.CastException
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
import net.minecraft.world.phys.Vec3
@ -14,10 +13,10 @@ import net.minecraft.world.phys.Vec3
* Implementors MUST NOT mutate the context.
*/
interface SpellOperator {
val manaCost: Int
get() = 0
fun modifyStack(stack: MutableList<SpellDatum<*>>, ctx: CastingContext)
/**
* Operate on the stack and return the mana cost.
*/
fun modifyStack(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): Int
companion object {
// I see why vzakii did this: you can't raycast out to infinity!
@ -42,15 +41,6 @@ interface SpellOperator {
this.getChecked<T>(idx)
}
/**
* Make sure the vector is in range of the player.
*/
@JvmStatic
fun assertVecInRange(vec: Vec3, ctx: CastingContext) {
if (vec.distanceToSqr(ctx.caster.position()) > MAX_DISTANCE * MAX_DISTANCE)
throw CastException(CastException.Reason.TOO_FAR, vec)
}
@JvmStatic
fun spellListOf(vararg vs: Any): List<SpellDatum<*>> {
val out = ArrayList<SpellDatum<*>>(vs.size)
@ -61,11 +51,12 @@ interface SpellOperator {
}
@JvmStatic
fun makeConstantOp(x: SpellDatum<*>): SpellOperator = object : SimpleOperator {
fun makeConstantOp(x: SpellDatum<*>): SpellOperator = object : ConstManaOperator {
override val argc: Int
get() = 0
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> = listOf(x)
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> =
listOf(x)
}
}
}

View file

@ -17,7 +17,7 @@ public class RegisterClientStuff {
evt.enqueueWork(() -> ItemProperties.register(HexItems.FOCUS.get(), ItemFocus.PREDICATE,
(stack, level, holder, holderID) -> {
if (stack.hasTag()) {
var tagname = stack.getTag().getAllKeys().iterator().next();
var tagname = stack.getTag().getCompound(ItemFocus.TAG_DATA).getAllKeys().iterator().next();
return switch (tagname) {
case SpellDatum.TAG_ENTITY -> 1f;
case SpellDatum.TAG_DOUBLE -> 2f;

View file

@ -57,6 +57,20 @@ class CastException(val reason: Reason, vararg val data: Any) : Exception() {
* `no args`
*/
REQUIRES_SPELLBOOK,
/**
* An operator needed a data holder in the offhand.
*
* `no args`
*/
REQUIRES_DATA_HOLDER,
/**
* We went too deep!
*
* `maxDepth: Int, gotDepth: Int`
*/
TOO_MANY_RECURSIVE_EVALS,
}
override val message: String
@ -68,5 +82,7 @@ class CastException(val reason: Reason, vararg val data: Any) : Exception() {
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}"
Reason.REQUIRES_SPELLBOOK -> "required a spellbook in the other hand"
Reason.REQUIRES_DATA_HOLDER -> "required a data holder in the other hand"
Reason.TOO_MANY_RECURSIVE_EVALS -> "can only recursively call OpEval ${this.data[0] as Int} times but called it ${this.data[1] as Int} times"
}
}

View file

@ -1,29 +1,166 @@
package at.petrak.hex.common.casting
import at.petrak.hex.HexMod
import at.petrak.hex.HexUtils
import at.petrak.hex.api.SpellOperator
import at.petrak.hex.common.items.ItemDataHolder
import at.petrak.hex.common.items.ItemSpellbook
import at.petrak.hex.common.items.ItemWand
import at.petrak.hex.common.lib.LibDamageSources
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
import java.util.function.Predicate
import kotlin.math.max
import kotlin.math.min
/**
* Info about the moment the spell started being cast.
*/
@JvmRecord
data class CastingContext(
val caster: ServerPlayer,
val wandHand: InteractionHand,
) {
private var depth: Int = 0
val world: ServerLevel get() = caster.getLevel()
val otherHand: InteractionHand get() = HexUtils.OtherHand(this.wandHand)
fun getSpellbook(): ItemStack {
val handItem =
caster.getItemInHand(HexUtils.OtherHand(wandHand))
caster.getItemInHand(this.otherHand)
return if (handItem.item is ItemSpellbook) {
handItem
} else {
throw CastException(CastException.Reason.REQUIRES_SPELLBOOK)
}
}
fun getDataHolder(): ItemStack {
val handItem =
caster.getItemInHand(this.otherHand)
return if (handItem.item is ItemDataHolder) {
handItem
} else {
throw CastException(CastException.Reason.REQUIRES_DATA_HOLDER)
}
}
/**
* Throws if we get too deep
*/
fun withIncDepth(): CastingContext {
val next = this.copy()
next.depth++
val maxAllowedDepth = HexMod.CONFIG.maxRecurseDepth.get()
if (next.depth > maxAllowedDepth) {
throw CastException(CastException.Reason.TOO_MANY_RECURSIVE_EVALS, maxAllowedDepth, next.depth)
} else {
return next
}
}
/**
* Might cast from hitpoints.
* Returns the mana cost still remaining after we deplete everything. It will be <= 0 if we could pay for it. */
fun withdrawMana(manaCost: Int, allowOvercast: Boolean): Int {
var costLeft = manaCost
val held = caster.getItemInHand(this.wandHand)
if (held.item is ItemWand) {
val tag = held.orCreateTag
val manaHere = tag.getInt(ItemWand.TAG_MANA)
val manaLeft = manaHere - costLeft
tag.putInt(ItemWand.TAG_MANA, max(0, manaLeft))
costLeft = max(0, costLeft - manaHere)
}
if (allowOvercast && costLeft > 0) {
// Cast from HP!
val healthToMana = HexMod.CONFIG.healthToManaRate.get()
val healthtoRemove = healthToMana * costLeft.toDouble()
val manaAbleToCastFromHP =
if (caster.isInvulnerable) Double.POSITIVE_INFINITY else caster.health / healthToMana
caster.hurt(LibDamageSources.OVERCAST, healthtoRemove.toFloat())
costLeft = (costLeft.toDouble() - manaAbleToCastFromHP).toInt()
}
return costLeft
}
fun assertVecInRange(vec: Vec3) {
if (vec.distanceToSqr(this.caster.position()) > SpellOperator.MAX_DISTANCE * SpellOperator.MAX_DISTANCE)
throw CastException(CastException.Reason.TOO_FAR, vec)
}
/**
* Return the slot from which to take blocks and items.
*/
// https://wiki.vg/Inventory is WRONG
// slots 0-8 are the hotbar
// for what purpose i cannot imagine
// http://redditpublic.com/images/b/b2/Items_slot_number.png looks right
// and offhand is 150 Inventory.java:464
fun getOperativeSlot(stackOK: Predicate<ItemStack>): Int? {
val otherHandStack = this.caster.getItemInHand(this.otherHand)
if (stackOK.test(otherHandStack)) {
return when (this.otherHand) {
InteractionHand.MAIN_HAND -> this.caster.inventory.selected
InteractionHand.OFF_HAND -> 150
}
}
val anchorSlot = when (this.wandHand) {
// slot to the right of the wand
InteractionHand.MAIN_HAND -> (this.caster.inventory.selected + 1) % 9
// first hotbar slot
InteractionHand.OFF_HAND -> 0
}
for (delta in 0 until 9) {
val slot = (anchorSlot + delta) % 9
val stack = this.caster.inventory.getItem(slot)
if (stackOK.test(stack)) {
return slot
}
}
return null
}
/**
* Remove the given gound of the specified item from somewhere in the inventory, favoring slots not in the hotbar.
* Return whether the withdrawal was successful.
*/
// https://github.com/VazkiiMods/Psi/blob/master/src/main/java/vazkii/psi/common/spell/trick/block/PieceTrickPlaceBlock.java#L143
fun withdrawItem(item: Item, count: Int, actuallyRemove: Boolean): Boolean {
if (this.caster.isCreative) return true
val inv = this.caster.inventory
// TODO: withdraw from ender chest given a specific ender charm?
val stacksToExamine = inv.items.asReversed()
fun matches(stack: ItemStack): Boolean =
!stack.isEmpty && stack.`is`(item)
val presentCount = stacksToExamine.fold(0) { acc, stack ->
acc + if (matches(stack)) stack.count else 0
}
if (presentCount < count) return false
// now that we know we have enough items, if we don't need to remove anything we're through.
if (!actuallyRemove) return true
var remaining = count
for (stack in stacksToExamine) {
if (matches(stack)) {
val toWithdraw = min(stack.count, remaining)
stack.shrink(toWithdraw)
remaining -= toWithdraw
if (remaining == 0) {
return true
}
}
}
throw RuntimeException("unreachable")
}
}

View file

@ -8,6 +8,7 @@ import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import kotlin.math.max
/**
* Keeps track of a player casting a spell on the server.
@ -63,19 +64,23 @@ class CastingHarness private constructor(
this.escapeNext = false
HexMod.LOGGER.info("Escaping onto stack")
this.stack.add(SpellDatum.make(newPat))
} else if (operator == SpellWidget.ESCAPE) {
this.escapeNext = true
} 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 {
// Plain ol operator
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)
}
// we know the operator is ok here
val manaCost = operator!!.modifyStack(this.stack, this.ctx)
// prevent poor impls from gaining you mana
ctx.withdrawMana(max(0, manaCost), true)
if (ctx.caster.isDeadOrDying)
return CastResult.Died
}
if (this.parenCount > 0) {
@ -172,5 +177,8 @@ class CastingHarness private constructor(
/** uh-oh */
data class Error(val exn: CastException) : CastResult()
/** YOU DIED due to casting too hard from hit points. */
object Died : CastResult()
}
}

View file

@ -1,6 +1,6 @@
package at.petrak.hex.common.casting
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
/**
@ -8,7 +8,7 @@ import at.petrak.hex.api.SpellOperator.Companion.spellListOf
*
* They act as operators that push themselves.
*/
enum class SpellWidget : SimpleOperator {
enum class SpellWidget : ConstManaOperator {
NULL,
OPEN_PAREN, CLOSE_PAREN, ESCAPE;

View file

@ -1,6 +1,6 @@
package at.petrak.hex.common.casting.operators
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.common.casting.CastingContext
@ -10,8 +10,9 @@ import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
object OpBlockAxisRaycast : SimpleOperator {
object OpBlockAxisRaycast : ConstManaOperator {
override val argc = 2
override val manaCost = 10
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {
val origin: Vec3 = args.getChecked(0)
val look: Vec3 = args.getChecked(1)

View file

@ -1,6 +1,6 @@
package at.petrak.hex.common.casting.operators
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.common.casting.CastingContext
@ -10,8 +10,9 @@ import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
object OpBlockRaycast : SimpleOperator {
object OpBlockRaycast : ConstManaOperator {
override val argc = 2
override val manaCost = 10
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {
val origin: Vec3 = args.getChecked(0)
val look: Vec3 = args.getChecked(1)

View file

@ -1,12 +1,12 @@
package at.petrak.hex.common.casting.operators
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
object OpDuplicate : SimpleOperator {
object OpDuplicate : ConstManaOperator {
override val argc: Int
get() = 1

View file

@ -1,13 +1,13 @@
package at.petrak.hex.common.casting.operators
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
import net.minecraft.world.entity.Entity
object OpEntityLook : SimpleOperator {
object OpEntityLook : ConstManaOperator {
override val argc = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {

View file

@ -1,6 +1,6 @@
package at.petrak.hex.common.casting.operators
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
import at.petrak.hex.common.casting.CastingContext
@ -8,7 +8,7 @@ import at.petrak.hex.common.casting.SpellDatum
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.player.Player
object OpEntityPos : SimpleOperator {
object OpEntityPos : ConstManaOperator {
override val argc = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {

View file

@ -1,6 +1,6 @@
package at.petrak.hex.common.casting.operators
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.common.casting.CastingContext
@ -10,8 +10,9 @@ import net.minecraft.world.entity.projectile.ProjectileUtil
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
object OpEntityRaycast : SimpleOperator {
object OpEntityRaycast : ConstManaOperator {
override val argc = 2
override val manaCost = 10
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {
val origin: Vec3 = args.getChecked(0)
val look: Vec3 = args.getChecked(1)

View file

@ -1,19 +1,19 @@
package at.petrak.hex.common.casting.operators
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.SpellOperator
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.CastingHarness
import at.petrak.hex.common.casting.SpellDatum
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)
object OpEval : SpellOperator {
override fun modifyStack(stack: MutableList<SpellDatum<*>>, ctx: CastingContext): Int {
val instrs: List<SpellDatum<*>> = stack.getChecked(stack.lastIndex)
stack.removeLastOrNull()
val ctxDeeper = ctx.withIncDepth()
val harness = CastingHarness.Default(ctxDeeper)
harness.stack.addAll(stack)
stack.clear()
for (pat in instrs) {
val res = harness.update(pat.tryGet())
if (res is CastingHarness.CastResult.Error) {
@ -22,6 +22,8 @@ object OpEval : SimpleOperator {
// in ANY OTHER CASE JUST KEEP GOING
// including if there's RenderedSpells on the stack or the stack becomes clear
}
return harness.stack
stack.addAll(harness.stack)
return 50
}
}

View file

@ -1,12 +1,12 @@
package at.petrak.hex.common.casting.operators
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
import net.minecraft.world.entity.Entity
object OpGetCaster : SimpleOperator {
object OpGetCaster : ConstManaOperator {
override val argc = 0
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> =

View file

@ -1,18 +1,18 @@
package at.petrak.hex.common.casting.operators
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
import at.petrak.hex.common.casting.SpellWidget
import at.petrak.hex.common.items.ItemSpellbook
import at.petrak.hex.common.items.ItemDataHolder
object OpReadFromSpellbook : SimpleOperator {
override val argc: Int
get() = 0
object OpRead : ConstManaOperator {
override val argc = 0
override val manaCost = 10
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {
val spellbook = ctx.getSpellbook()
val datum = ItemSpellbook.ReadDatum(spellbook.orCreateTag, ctx)
val dataer = ctx.getDataHolder()
val datum = (dataer.item as ItemDataHolder).readDatum(dataer.orCreateTag, ctx)
return listOf(datum ?: SpellDatum.make(SpellWidget.NULL))
}
}

View file

@ -1,12 +1,12 @@
package at.petrak.hex.common.casting.operators
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
object OpSwap : SimpleOperator {
object OpSwap : ConstManaOperator {
override val argc: Int
get() = 2

View file

@ -1,10 +1,10 @@
package at.petrak.hex.common.casting.operators
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
object OpUndo : SimpleOperator {
object OpUndo : ConstManaOperator {
override val argc = 1
// Do literally nothing!

View file

@ -1,18 +1,18 @@
package at.petrak.hex.common.casting.operators
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
import at.petrak.hex.common.items.ItemSpellbook
import at.petrak.hex.common.items.ItemDataHolder
object OpWriteToSpellbook : SimpleOperator {
override val argc: Int
get() = 1
object OpWrite : ConstManaOperator {
override val argc = 1
override val manaCost = 10
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {
val spellbook = ctx.getSpellbook()
ItemSpellbook.WriteDatum(spellbook.orCreateTag, args[0])
val dataer = ctx.getDataHolder()
(dataer.item as ItemDataHolder).writeDatum(dataer.orCreateTag, args[0])
return spellListOf()
}
}

View file

@ -1,12 +1,12 @@
package at.petrak.hex.common.casting.operators.math
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
import kotlin.math.absoluteValue
object OpAbsLen : SimpleOperator {
object OpAbsLen : ConstManaOperator {
override val argc: Int
get() = 1

View file

@ -1,11 +1,11 @@
package at.petrak.hex.common.casting.operators.math
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
object OpAdd : SimpleOperator {
object OpAdd : ConstManaOperator {
override val argc: Int
get() = 2

View file

@ -1,12 +1,12 @@
package at.petrak.hex.common.casting.operators.math
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
import net.minecraft.world.phys.Vec3
object OpDivCross : SimpleOperator {
object OpDivCross : ConstManaOperator {
override val argc: Int
get() = 2

View file

@ -1,11 +1,11 @@
package at.petrak.hex.common.casting.operators.math
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
object OpMulDot : SimpleOperator {
object OpMulDot : ConstManaOperator {
override val argc: Int
get() = 2

View file

@ -1,13 +1,13 @@
package at.petrak.hex.common.casting.operators.math
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
import net.minecraft.world.phys.Vec3
import kotlin.math.pow
object OpPowProj : SimpleOperator {
object OpPowProj : ConstManaOperator {
override val argc: Int
get() = 2

View file

@ -1,12 +1,12 @@
package at.petrak.hex.common.casting.operators.math
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
import at.petrak.hex.common.casting.CastingContext
import at.petrak.hex.common.casting.SpellDatum
import net.minecraft.world.phys.Vec3
object OpSub : SimpleOperator {
object OpSub : ConstManaOperator {
override val argc: Int
get() = 2

View file

@ -18,10 +18,13 @@ object OpAddMotion : SimpleOperator, RenderedSpellImpl {
override val argc: Int
get() = 2
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<List<SpellDatum<*>>, Int> {
val target = args.getChecked<Entity>(0)
val motion = args.getChecked<Vec3>(1)
return spellListOf(RenderedSpell(OpAddMotion, spellListOf(target, motion)))
return Pair(
spellListOf(RenderedSpell(OpAddMotion, spellListOf(target, motion))),
motion.lengthSqr().toInt() * 100
)
}
override fun cast(args: List<SpellDatum<*>>, ctx: CastingContext) {

View file

@ -0,0 +1,43 @@
package at.petrak.hex.common.casting.operators.spells
import at.petrak.hex.HexMod
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
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 net.minecraft.core.BlockPos
import net.minecraft.world.phys.Vec3
import net.minecraftforge.common.TierSortingRegistry
object OpBreakBlock : SimpleOperator, RenderedSpellImpl {
override val argc: Int
get() = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<List<SpellDatum<*>>, Int> {
val pos = args.getChecked<Vec3>(0)
ctx.assertVecInRange(pos)
return Pair(
spellListOf(RenderedSpell(OpBreakBlock, spellListOf(pos))),
100
)
}
override fun cast(args: List<SpellDatum<*>>, ctx: CastingContext) {
val pos = BlockPos(args.getChecked<Vec3>(0))
val blockstate = ctx.world.getBlockState(pos)
val tier =
HexMod.CONFIG.opBreakHarvestLevelBecauseForgeThoughtItWasAGoodIdeaToImplementHarvestTiersUsingAnHonestToGodTopoSort
if (!blockstate.isAir && (!blockstate.requiresCorrectToolForDrops() || TierSortingRegistry.isCorrectTierForDrops(
tier,
blockstate
))
) {
ctx.world.destroyBlock(pos, true, ctx.caster)
} // TODO: else some kind of failureific particle effect?
}
}

View file

@ -1,30 +1,41 @@
package at.petrak.hex.common.casting.operators.spells
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.SpellOperator.Companion.assertVecInRange
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
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 net.minecraft.util.Mth
import net.minecraft.world.level.Explosion
import net.minecraft.world.phys.Vec3
object OpExplode : SimpleOperator, RenderedSpellImpl {
override val argc: Int
get() = 1
get() = 2
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<List<SpellDatum<*>>, Int> {
val pos = args.getChecked<Vec3>(0)
assertVecInRange(pos, ctx)
return spellListOf(RenderedSpell(OpExplode, spellListOf(pos)))
val strength = args.getChecked<Double>(1)
ctx.assertVecInRange(pos)
return Pair(
spellListOf(RenderedSpell(OpExplode, spellListOf(pos, strength))),
(strength * 100.0).toInt(),
)
}
override fun cast(args: List<SpellDatum<*>>, ctx: CastingContext) {
val pos = args.getChecked<Vec3>(0)
val strength = args.getChecked<Double>(1)
// 4.0 is the strength of TNT, i guess
ctx.world.explode(ctx.caster, pos.x, pos.y, pos.z, 4.0f, Explosion.BlockInteraction.BREAK)
ctx.world.explode(
ctx.caster,
pos.x,
pos.y,
pos.z,
Mth.clamp(strength.toFloat(), 0f, 10f),
Explosion.BlockInteraction.BREAK
)
}
}

View file

@ -0,0 +1,34 @@
package at.petrak.hex.common.casting.operators.spells
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
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 net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.LightningBolt
import net.minecraft.world.phys.Vec3
object OpLightning : SimpleOperator, RenderedSpellImpl {
override val argc: Int
get() = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<List<SpellDatum<*>>, Int> {
val target = args.getChecked<Vec3>(0)
ctx.assertVecInRange(target)
return Pair(
spellListOf(RenderedSpell(OpLightning, spellListOf(target))),
1500
)
}
override fun cast(args: List<SpellDatum<*>>, ctx: CastingContext) {
val target = args.getChecked<Vec3>(0)
val lightning = LightningBolt(EntityType.LIGHTNING_BOLT, ctx.world)
lightning.setPosRaw(target.x, target.y, target.z)
ctx.world.addWithUUID(lightning) // why the hell is it called this it doesnt even involve a uuid
}
}

View file

@ -0,0 +1,71 @@
package at.petrak.hex.common.casting.operators.spells
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
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 net.minecraft.core.BlockPos
import net.minecraft.world.InteractionResult
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.Vec3
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.common.util.BlockSnapshot
import net.minecraftforge.event.world.BlockEvent
object OpPlaceBlock : SimpleOperator, RenderedSpellImpl {
override val argc: Int
get() = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): Pair<List<SpellDatum<*>>, Int> {
val pos = args.getChecked<Vec3>(0)
ctx.assertVecInRange(pos)
return Pair(
spellListOf(RenderedSpell(OpPlaceBlock, spellListOf(pos))),
30
)
}
override fun cast(args: List<SpellDatum<*>>, ctx: CastingContext) {
val vec = args.getChecked<Vec3>(0)
val pos = BlockPos(vec)
val bstate = ctx.world.getBlockState(pos)
if (bstate.isAir || bstate.material.isReplaceable) {
val placeeSlot = ctx.getOperativeSlot { it.item is BlockItem }
if (placeeSlot != null) {
val placeeStack = ctx.caster.inventory.getItem(placeeSlot)
val placee = placeeStack.item as BlockItem
if (ctx.withdrawItem(placee, 1, false)) {
// https://github.com/VazkiiMods/Psi/blob/master/src/main/java/vazkii/psi/common/spell/trick/block/PieceTrickPlaceBlock.java#L143
val evt = BlockEvent.EntityPlaceEvent(
BlockSnapshot.create(ctx.world.dimension(), ctx.world, pos),
ctx.world.getBlockState(pos.above()),
ctx.caster
)
MinecraftForge.EVENT_BUS.post(evt)
// we temporarily give the player the stack, place it using mc code, then give them the old stack back.
val oldStack = ctx.caster.getItemInHand(ctx.wandHand)
val spoofedStack = placeeStack.copy()
spoofedStack.count = 1
ctx.caster.setItemInHand(ctx.wandHand, spoofedStack)
val blockHit = BlockHitResult(
Vec3.ZERO, ctx.caster.direction, pos, false
)
val itemUseCtx = UseOnContext(ctx.caster, ctx.wandHand, blockHit)
val res = spoofedStack.useOn(itemUseCtx)
ctx.caster.setItemInHand(ctx.wandHand, oldStack)
if (res != InteractionResult.FAIL) {
ctx.withdrawItem(placee, 1, true)
}
}
}
}
}
}

View file

@ -1,6 +1,6 @@
package at.petrak.hex.common.casting.operators.spells
import at.petrak.hex.api.SimpleOperator
import at.petrak.hex.api.ConstManaOperator
import at.petrak.hex.api.SpellOperator.Companion.getChecked
import at.petrak.hex.api.SpellOperator.Companion.spellListOf
import at.petrak.hex.common.casting.CastingContext
@ -10,7 +10,7 @@ import at.petrak.hex.common.casting.SpellDatum
import net.minecraft.Util
import net.minecraft.network.chat.TextComponent
object OpPrint : SimpleOperator, RenderedSpellImpl {
object OpPrint : ConstManaOperator, RenderedSpellImpl {
override val argc = 1
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> {
val datum = args.getChecked<Any>(0)

View file

@ -2,23 +2,46 @@ package at.petrak.hex.common.items;
import at.petrak.hex.HexMod;
import at.petrak.hex.common.lib.LibItemNames;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class HexItems {
public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, HexMod.MOD_ID);
public static final CreativeModeTab TAB = new CreativeModeTab("hex") {
@Override
public ItemStack makeIcon() {
return new ItemStack(SPELLBOOK::get);
}
@Override
public void fillItemList(NonNullList<ItemStack> items) {
// Make the wand spawn with some sensible NBT
var tag = new CompoundTag();
tag.putInt(ItemWand.TAG_MANA, 1000);
tag.putInt(ItemWand.TAG_MAX_MANA, 1000);
var stack = new ItemStack(WAND::get);
stack.setTag(tag);
items.add(stack);
super.fillItemList(items);
}
};
public static final RegistryObject<Item> WAND = ITEMS.register(LibItemNames.WAND,
() -> new ItemWand(unstackable()));
() -> new ItemWand(new Item.Properties().stacksTo(1)));
public static final RegistryObject<Item> FOCUS = ITEMS.register(LibItemNames.FOCUS,
() -> new ItemFocus(props()));
public static final RegistryObject<Item> SPELLBOOK = ITEMS.register(LibItemNames.SPELLBOOK,
() -> new ItemSpellbook(unstackable()));
public static Item.Properties props() {
return new Item.Properties();
return new Item.Properties().tab(TAB);
}
public static Item.Properties unstackable() {

View file

@ -1,2 +1,18 @@
package at.petrak.hex.common.items;public class ItemDataDolder {
package at.petrak.hex.common.items;
import at.petrak.hex.common.casting.CastingContext;
import at.petrak.hex.common.casting.SpellDatum;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.Item;
import org.jetbrains.annotations.Nullable;
abstract public class ItemDataHolder extends Item {
public ItemDataHolder(Properties pProperties) {
super(pProperties);
}
@Nullable
public abstract SpellDatum<?> readDatum(CompoundTag tag, CastingContext ctx);
public abstract void writeDatum(CompoundTag tag, SpellDatum<?> datum);
}

View file

@ -1,13 +1,31 @@
package at.petrak.hex.common.items;
import at.petrak.hex.HexMod;
import at.petrak.hex.common.casting.CastingContext;
import at.petrak.hex.common.casting.SpellDatum;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.jetbrains.annotations.Nullable;
public class ItemFocus extends Item {
public class ItemFocus extends ItemDataHolder {
public static final ResourceLocation PREDICATE = new ResourceLocation(HexMod.MOD_ID, "datatype");
public static final String TAG_DATA = "data";
public ItemFocus(Properties pProperties) {
super(pProperties);
}
@Override
public @Nullable SpellDatum<?> readDatum(CompoundTag tag, CastingContext ctx) {
try {
return SpellDatum.DeserializeFromNBT(tag.getCompound(TAG_DATA), ctx);
} catch (Exception e) {
return null;
}
}
@Override
public void writeDatum(CompoundTag tag, SpellDatum<?> datum) {
tag.put(TAG_DATA, datum.serializeToNBT());
}
}

View file

@ -7,7 +7,6 @@ import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
@ -16,7 +15,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.stream.Stream;
public class ItemSpellbook extends Item {
public class ItemSpellbook extends ItemDataHolder {
public static String TAG_SELECTED_PAGE = "page_idx";
// this is a CompoundTag of string numerical keys to SpellData\
// it is 1-indexed, so that 0/0 can be the special case of "it is empty"
@ -64,7 +63,29 @@ public class ItemSpellbook extends Item {
tag.getCompound(ItemSpellbook.TAG_PAGES).isEmpty();
}
public static void WriteDatum(CompoundTag tag, SpellDatum<?> datum) {
@Nullable
public SpellDatum<?> readDatum(CompoundTag tag, CastingContext ctx) {
int idx;
if (tag.contains(TAG_SELECTED_PAGE)) {
idx = tag.getInt(TAG_SELECTED_PAGE);
} else {
idx = 0;
}
var key = String.valueOf(idx);
if (tag.contains(TAG_PAGES)) {
var pagesTag = tag.getCompound(TAG_PAGES);
if (pagesTag.contains(key)) {
return SpellDatum.DeserializeFromNBT(pagesTag.getCompound(key), ctx);
} else {
return null;
}
} else {
return null;
}
}
public void writeDatum(CompoundTag tag, SpellDatum<?> datum) {
int idx;
if (tag.contains(TAG_SELECTED_PAGE)) {
idx = tag.getInt(TAG_SELECTED_PAGE);
@ -85,27 +106,6 @@ public class ItemSpellbook extends Item {
}
}
@Nullable
public static SpellDatum<?> ReadDatum(CompoundTag tag, CastingContext ctx) {
int idx;
if (tag.contains(TAG_SELECTED_PAGE)) {
idx = tag.getInt(TAG_SELECTED_PAGE);
} else {
idx = 0;
}
var key = String.valueOf(idx);
if (tag.contains(TAG_PAGES)) {
var pagesTag = tag.getCompound(TAG_PAGES);
if (pagesTag.contains(key)) {
return SpellDatum.DeserializeFromNBT(pagesTag.getCompound(key), ctx);
} else {
return null;
}
} else {
return null;
}
}
public static int HighestPage(CompoundTag tag) {
var highestKey = tag.getAllKeys().stream().flatMap(s -> {
try {

View file

@ -1,15 +1,22 @@
package at.petrak.hex.common.items;
import at.petrak.hex.HexMod;
import at.petrak.hex.client.gui.GuiSpellcasting;
import net.minecraft.client.Minecraft;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
public class ItemWand extends Item {
public static final String TAG_MANA = "mana";
public static final String TAG_MAX_MANA = "maxMana";
public static final String TAG_HARNESS = "harness";
public ItemWand(Properties pProperties) {
super(pProperties);
}
@ -22,4 +29,61 @@ public class ItemWand extends Item {
return InteractionResultHolder.success(player.getItemInHand(hand));
}
@Override
public void inventoryTick(ItemStack pStack, Level pLevel, Entity pEntity, int pSlotId, boolean pIsSelected) {
var tag = pStack.getOrCreateTag();
var mana = tag.getInt(TAG_MANA);
var maxMana = tag.getInt(TAG_MAX_MANA);
if (mana < maxMana) {
tag.putInt(TAG_MANA, Math.min(maxMana, mana + HexMod.CONFIG.wandRechargeRate.get()));
}
}
@Override
public boolean isDamageable(ItemStack stack) {
return false;
}
@Override
public boolean canBeDepleted() {
return false;
}
@Override
public boolean isBarVisible(ItemStack pStack) {
return true;
}
@Override
public int getBarColor(ItemStack pStack) {
var tag = pStack.getOrCreateTag();
var mana = tag.getInt(TAG_MANA);
var maxMana = tag.getInt(TAG_MAX_MANA);
float amt;
if (maxMana == 0) {
amt = 0f;
} else {
amt = ((float) mana) / ((float) maxMana);
}
var r = Mth.lerp(amt, 149f, 112f);
var g = Mth.lerp(amt, 196f, 219f);
var b = Mth.lerp(amt, 174f, 212f);
return Mth.color(r / 255f, g / 255f, b / 255f);
}
@Override
public int getBarWidth(ItemStack pStack) {
var tag = pStack.getOrCreateTag();
var mana = tag.getInt(TAG_MANA);
var maxMana = tag.getInt(TAG_MAX_MANA);
float amt;
if (maxMana == 0) {
amt = 0f;
} else {
amt = ((float) mana) / ((float) maxMana);
}
return Math.round(13f * amt);
}
}

View file

@ -1,2 +1,7 @@
package at.petrak.hex.common.lib;public class LibDamage {
package at.petrak.hex.common.lib;
import net.minecraft.world.damagesource.DamageSource;
public class LibDamageSources {
public static final DamageSource OVERCAST = new DamageSource("hex.overcast").bypassArmor().bypassMagic().setMagic();
}

View file

@ -1,5 +1,6 @@
package at.petrak.hex.common.network;
import at.petrak.hex.client.gui.GuiSpellcasting;
import io.netty.buffer.ByteBuf;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
@ -27,7 +28,7 @@ public record MsgNewSpellPatternAck(boolean quitCasting) {
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() ->
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
if (quitCasting) {
if (quitCasting && Minecraft.getInstance().screen instanceof GuiSpellcasting) {
Minecraft.getInstance().setScreen(null);
}
})

View file

@ -42,7 +42,8 @@ public record MsgNewSpellPatternSyn(InteractionHand handUsed, HexPattern pattern
var held = sender.getItemInHand(this.handUsed);
if (held.getItem() instanceof ItemWand) {
var tag = held.getOrCreateTag();
var harness = CastingHarness.DeserializeFromNBT(tag, sender, this.handUsed);
var harness = CastingHarness.DeserializeFromNBT(tag.getCompound(ItemWand.TAG_HARNESS), sender,
this.handUsed);
var res = harness.update(this.pattern);
if (res instanceof CastResult.Success success) {
@ -52,15 +53,17 @@ public record MsgNewSpellPatternSyn(InteractionHand handUsed, HexPattern pattern
}
boolean quit;
CompoundTag nextHarnessTag;
if (res instanceof CastResult.Nothing) {
// save the changes
held.setTag(harness.serializeToNBT());
nextHarnessTag = harness.serializeToNBT();
quit = false;
} else {
// Else we requested to quit in some way or another
held.setTag(new CompoundTag());
nextHarnessTag = new CompoundTag();
quit = true;
}
tag.put(ItemWand.TAG_HARNESS, nextHarnessTag);
HexMessages.getNetwork()
.send(PacketDistributor.PLAYER.with(() -> sender),
new MsgNewSpellPatternAck(quit));

View file

@ -26,7 +26,7 @@ public record MsgQuitSpellcasting() {
var held = sender.getMainHandItem();
if (held.getItem() instanceof ItemWand) {
// Todo: appropriate consequences for quitting a spell like this
held.setTag(new CompoundTag());
held.getOrCreateTag().put(ItemWand.TAG_HARNESS, new CompoundTag());
}
}
});

View file

@ -1,4 +1,32 @@
package at.petrak.hex.server
import net.minecraftforge.event.TickEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import java.util.*
object TickScheduler {
val tasks: MutableList<Task> = LinkedList()
fun schedule(task: Task) {
this.tasks.add(task)
}
fun schedule(ticks: Int, task: Runnable) {
this.tasks.add(Task(ticks, task))
}
@SubscribeEvent
fun onTick(evt: TickEvent.ServerTickEvent) {
this.tasks.removeIf {
it.ticks--
if (it.ticks <= 0) {
it.task.run()
true
} else {
false
}
}
}
data class Task(var ticks: Int, val task: Runnable)
}

View file

@ -3,5 +3,7 @@
"item.hex.focus": "Focus",
"item.hex.spellbook": "Spellbook",
"hex.spellbook.tooltip.page": "Selected Page %d/%d",
"hex.spelldata.desc.entity": "Entity %s"
"hex.spelldata.desc.entity": "Entity %s",
"itemGroup.hex": "Hex"
}