merge in talia's fix? hopefully?

This commit is contained in:
petrak@ 2023-04-24 11:38:03 -05:00
commit 954f8918ae
94 changed files with 1961 additions and 1308 deletions

View file

@ -2,7 +2,7 @@ Hello, intrepid Github reader!
The "flavor text" words for things in this mod and the internal names are different. (Sorry.)
- A "Hex" is a `Cast`, cast through a [`CastingHarness`](api/casting/eval/CastingHarness.kt)
- A "Hex" is a `Cast`, cast through a [`CastingHarness`](api/casting/eval/vm/CastingVM.kt)
- A "Pattern" is a [`HexPattern`](api/casting/math/HexPattern.kt)
- An "Action" is an [`Operator`](api/casting/castables/Action.kt)
- An action that pushes a spell is a [`Spell`](api/casting/castables/SpellAction.kt)

View file

@ -1,19 +1,26 @@
package at.petrak.hexcasting.api;
import at.petrak.hexcasting.api.addldata.ADMediaHolder;
import at.petrak.hexcasting.api.casting.ActionRegistryEntry;
import at.petrak.hexcasting.api.casting.castables.SpecialHandler;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.api.player.Sentinel;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import com.google.common.base.Suppliers;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
import java.util.function.Supplier;
@ -137,6 +144,30 @@ public interface HexAPI {
}
//
@Nullable
default Sentinel getSentinel(ServerPlayer player) {
return null;
}
@Nullable
default ADMediaHolder findMediaHolder(ItemStack stack) {
return null;
}
default FrozenColorizer getColorizer(Player player) {
return FrozenColorizer.DEFAULT.get();
}
/**
* Location in the userdata of the ravenmind
*/
String RAVENMIND_USERDATA = modLoc("ravenmind").toString();
/**
* Location in the userdata of the number of ops executed
*/
String OP_COUNT_USERDATA = modLoc("op_count").toString();
String MARKED_MOVED_USERDATA = modLoc("impulsed").toString();
static HexAPI instance() {
return INSTANCE.get();

View file

@ -1,7 +1,7 @@
package at.petrak.hexcasting.api.addldata;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import org.jetbrains.annotations.Nullable;
@ -14,7 +14,7 @@ public interface ADIotaHolder {
default Iota readIota(ServerLevel world) {
var tag = readIotaTag();
if (tag != null) {
return HexIotaTypes.deserialize(tag, world);
return IotaType.deserialize(tag, world);
} else {
return null;
}

View file

@ -1,14 +1,14 @@
package at.petrak.hexcasting.api.block.circle;
import at.petrak.hexcasting.api.block.HexBlockEntity;
import at.petrak.hexcasting.api.casting.ParticleSpray;
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment;
import at.petrak.hexcasting.api.casting.eval.SpellCircleContext;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.api.casting.iota.PatternIota;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.api.misc.MediaConstants;
import at.petrak.hexcasting.api.mod.HexConfig;
import at.petrak.hexcasting.api.casting.ParticleSpray;
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment;
import at.petrak.hexcasting.api.casting.eval.CastingHarness;
import at.petrak.hexcasting.api.casting.eval.SpellCircleContext;
import at.petrak.hexcasting.api.casting.iota.PatternIota;
import at.petrak.hexcasting.api.utils.MediaHelper;
import at.petrak.hexcasting.common.items.magic.ItemCreativeUnlocker;
import at.petrak.hexcasting.common.lib.HexItems;
@ -284,7 +284,7 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen
var ctx = new CastingEnvironment(splayer, InteractionHand.MAIN_HAND,
new SpellCircleContext(this.getBlockPos(), bounds, this.activatorAlwaysInRange()));
var harness = new CastingHarness(ctx);
var harness = new CastingVM(ctx);
BlockPos erroredPos = null;
for (var tracked : this.trackedBlocks) {
@ -292,7 +292,7 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen
if (bs.getBlock() instanceof BlockCircleComponent cc) {
var newPattern = cc.getPattern(tracked, bs, this.level);
if (newPattern != null) {
var info = harness.executeIota(new PatternIota(newPattern), splayer.getLevel());
var info = harness.queueAndExecuteIota(new PatternIota(newPattern), splayer.getLevel());
if (!info.getResolutionType().getSuccess()) {
erroredPos = tracked;
break;

View file

@ -1,9 +1,10 @@
package at.petrak.hexcasting.api.casting.castables
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.iota.Iota
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.phys.Vec3
import java.text.DecimalFormat
@ -17,6 +18,8 @@ import java.text.DecimalFormat
* the client needs about them is stored elsewhere. (For example, their canonical stroke order
* is stored in [ActionRegistryEntry], and their localization key is gotten from the resource key
* via [at.petrak.hexcasting.api.HexAPI.getActionI18nKey].)
*
* Each action is a singleton
*/
interface Action {
/**
@ -24,25 +27,26 @@ interface Action {
*
* Although this is passed a [MutableList], this is only for the convenience of implementors.
* It is a clone of the stack and modifying it does nothing. You must return the new stack
* with the [OperationResult].
* with the [OperationResult]. Similar with the `userData`.
*
* A particle effect at the cast site and various messages and advancements are done automagically.
*/
fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult
companion object {
// I see why vzakii did this: you can't raycast out to infinity!
const val MAX_DISTANCE: Double = 32.0
const val MAX_DISTANCE_FROM_SENTINEL: Double = 16.0
// I see why vazkii did this: you can't raycast out to infinity!
const val RAYCAST_DISTANCE: Double = 32.0
// TODO: currently, this means you can't raycast in a very long spell circle, or out of your local ambit into
// your sentinel's.
@JvmStatic
fun raycastEnd(origin: Vec3, look: Vec3): Vec3 =
origin.add(look.normalize().scale(MAX_DISTANCE))
origin.add(look.normalize().scale(RAYCAST_DISTANCE))
@JvmStatic
fun makeConstantOp(x: Iota): Action = object : ConstMediaAction {

View file

@ -1,11 +1,12 @@
package at.petrak.hexcasting.api.casting.castables
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import net.minecraft.nbt.CompoundTag
/**
* A SimpleOperator that always costs the same amount of media.
@ -18,20 +19,20 @@ interface ConstMediaAction : Action {
fun execute(args: List<Iota>, ctx: CastingEnvironment): List<Iota>
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
if (this.argc > stack.size)
throw MishapNotEnoughArgs(this.argc, stack.size)
val args = stack.takeLast(this.argc)
repeat(this.argc) { stack.removeLast() }
val newData = this.execute(args, ctx)
val newData = this.execute(args, env)
stack.addAll(newData)
val sideEffects = mutableListOf<OperatorSideEffect>(OperatorSideEffect.ConsumeMedia(this.mediaCost))
return OperationResult(continuation, stack, ravenmind, sideEffects)
return OperationResult(stack, userData, sideEffects, continuation)
}
}

View file

@ -1,13 +1,14 @@
package at.petrak.hexcasting.api.casting.castables
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.ParticleSpray
import at.petrak.hexcasting.api.casting.RenderedSpell
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import net.minecraft.nbt.CompoundTag
interface SpellAction : Action {
val argc: Int
@ -21,17 +22,24 @@ interface SpellAction : Action {
ctx: CastingEnvironment
): Triple<RenderedSpell, Int, List<ParticleSpray>>?
fun executeWithUserdata(
args: List<Iota>, ctx: CastingEnvironment, userData: CompoundTag
): Triple<RenderedSpell, Int, List<ParticleSpray>>? {
return this.execute(args, ctx)
}
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
if (this.argc > stack.size)
throw MishapNotEnoughArgs(this.argc, stack.size)
val args = stack.takeLast(this.argc)
for (_i in 0 until this.argc) stack.removeLast()
val executeResult = this.execute(args, ctx) ?: return OperationResult(continuation, stack, ravenmind, listOf())
val executeResult = this.executeWithUserdata(args, env, userData)
?: return OperationResult(stack, userData, listOf(), continuation)
val (spell, media, particles) = executeResult
val sideEffects = mutableListOf<OperatorSideEffect>()
@ -42,15 +50,15 @@ interface SpellAction : Action {
sideEffects.add(
OperatorSideEffect.AttemptSpell(
spell,
this.hasCastingSound(ctx),
this.awardsCastingStat(ctx)
this.hasCastingSound(env),
this.awardsCastingStat(env)
)
)
for (spray in particles)
sideEffects.add(OperatorSideEffect.Particles(spray))
return OperationResult(continuation, stack, ravenmind, sideEffects)
return OperationResult(stack, userData, sideEffects, continuation)
}
}

View file

@ -2,7 +2,7 @@ package at.petrak.hexcasting.api.casting.eval
import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.casting.eval.vm.FunctionalData
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
/**
@ -13,8 +13,8 @@ import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
*/
data class CastResult(
val continuation: SpellContinuation,
val newData: FunctionalData?,
val newData: CastingImage?,
val sideEffects: List<OperatorSideEffect>,
val resolutionType: ResolvedPatternType,
val sound: EvalSound,
)
)

View file

@ -0,0 +1,269 @@
package at.petrak.hexcasting.api.casting.eval;
import at.petrak.hexcasting.api.casting.ParticleSpray;
import at.petrak.hexcasting.api.casting.PatternShapeMatch;
import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound;
import at.petrak.hexcasting.api.casting.mishaps.Mishap;
import at.petrak.hexcasting.api.casting.mishaps.MishapDisallowedSpell;
import at.petrak.hexcasting.api.casting.mishaps.MishapEntityTooFarAway;
import at.petrak.hexcasting.api.casting.mishaps.MishapLocationTooFarAway;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.api.mod.HexConfig;
import at.petrak.hexcasting.api.utils.HexUtils;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
/**
* Environment within which hexes are cast.
* <p>
* Stuff like "the player with a staff," "the player with a trinket," "spell circles,"
*/
public abstract class CastingEnvironment {
protected final ServerLevel world;
protected CastingEnvironment(ServerLevel world) {
this.world = world;
}
public final ServerLevel getWorld() {
return this.world;
}
/**
* Get the caster. Might be null!
* <p>
* Implementations should NOT rely on this in general, use the methods on this class instead.
* This is mostly for spells (flight, etc)
*/
@Nullable
public abstract ServerPlayer getCaster();
/**
* Get an interface used to do mishaps
*/
public abstract MishapEnvironment getMishapEnvironment();
/**
* Get the sound that this I/O module makes upon receiving a pattern
*/
public abstract EvalSound getSoundType();
/**
* If something about this ARE itself is invalid, mishap.
* <p>
* This is used for stuff like requiring enlightenment and pattern denylists
*/
public void precheckAction(PatternShapeMatch match) throws Mishap {
// TODO: this doesn't let you select special handlers.
// Might be worth making a "no casting" tag on each thing
ResourceLocation key;
if (match instanceof PatternShapeMatch.Normal normal) {
key = normal.key.location();
} else if (match instanceof PatternShapeMatch.PerWorld perWorld) {
key = perWorld.key.location();
} else if (match instanceof PatternShapeMatch.Special special) {
key = special.key.location();
} else {
key = null;
}
if (!HexConfig.server().isActionAllowed(key)) {
throw new MishapDisallowedSpell();
}
}
/**
* Do whatever you like after a pattern is executed.
*/
public abstract void postExecution(CastResult result);
public abstract Vec3 mishapSprayPos();
/**
* Attempt to extract the given amount of media. Returns the amount of media left in the cost.
* <p>
* If there was enough media found, it will return less or equal to zero; if there wasn't, it will be
* positive.
*/
public abstract long extractMedia(long cost);
/**
* Get if the vec is close enough, to the player or sentinel ...
* <p>
* Doesn't take into account being out of the <em>world</em>.
*/
public abstract boolean isVecInRange(Vec3 vec);
public final boolean isVecInWorld(Vec3 vec) {
return this.world.isInWorldBounds(new BlockPos(vec))
&& this.world.getWorldBorder().isWithinBounds(vec.x, vec.z, 0.5);
}
public final boolean isVecInAmbit(Vec3 vec) {
return this.isVecInRange(vec) && this.isVecInWorld(vec);
}
public final boolean isEntityInRange(Entity e) {
return this.isVecInRange(e.position());
}
/**
* Convenience function to throw if the vec is out of the caster's range or the world
*/
public final void assertVecInRange(Vec3 vec) throws MishapLocationTooFarAway {
this.assertVecInWorld(vec);
if (this.isVecInRange(vec)) {
throw new MishapLocationTooFarAway(vec, "too_far");
}
}
public final void assertVecInRange(BlockPos vec) throws MishapLocationTooFarAway {
this.assertVecInRange(new Vec3(vec.getX(), vec.getY(), vec.getZ()));
}
public final boolean canEditBlockAt(BlockPos vec) {
// TODO winfy: fill this in
return false;
}
/**
* Convenience function to throw if the entity is out of the caster's range or the world
*/
public final void assertEntityInRange(Entity e) throws MishapEntityTooFarAway {
if (!this.isVecInWorld(e.position())) {
throw new MishapEntityTooFarAway(e);
}
if (this.isVecInRange(e.position())) {
throw new MishapEntityTooFarAway(e);
}
}
/**
* Convenience function to throw if the vec is out of the world (for GTP)
*/
public final void assertVecInWorld(Vec3 vec) throws MishapLocationTooFarAway {
if (!this.isVecInWorld(vec)) {
throw new MishapLocationTooFarAway(vec, "out_of_world");
}
}
public abstract InteractionHand castingHand();
public InteractionHand otherHand() {
return HexUtils.otherHand(this.castingHand());
}
/**
* Get the item in the "other hand."
* <p>
* If that hand is empty, or if they cannot have that hand, return Empty.
* Probably return a clone of Empty, actually...
*/
public abstract ItemStack getAlternateItem();
/**
* Get all the item stacks this env can use.
*/
protected abstract List<ItemStack> getUsableStacks(StackDiscoveryMode mode);
/**
* Return the slot from which to take blocks and items.
*/
@Nullable
public ItemStack queryForMatchingStack(Predicate<ItemStack> stackOk) {
var stacks = this.getUsableStacks(StackDiscoveryMode.QUERY);
for (ItemStack stack : stacks) {
if (stackOk.test(stack)) {
return stack;
}
}
return null;
}
public static record HeldItemInfo(ItemStack stack, InteractionHand hand) {
public ItemStack component1() {
return stack;
}
public InteractionHand component2() {
return hand;
}
}
/**
* Return the slot from which to take blocks and items.
*/
// TODO winfy: resolve the null here
// @Nullable
public HeldItemInfo getHeldItemToOperateOn(Predicate<ItemStack> stackOk) {
// TODO winfy: return something properly
return null;
}
/**
* Attempt to withdraw some number of items from stacks available.
* <p>
* Return whether it was successful.
*/
public boolean withdrawItem(Predicate<ItemStack> stackOk, int count, boolean actuallyRemove) {
var stacks = this.getUsableStacks(StackDiscoveryMode.EXTRACTION);
var presentCount = 0;
var matches = new ArrayList<ItemStack>();
for (ItemStack stack : stacks) {
if (stackOk.test(stack)) {
presentCount += stack.getCount();
matches.add(stack);
}
}
if (presentCount < count) {
return false;
}
if (!actuallyRemove) {
return true;
} // Otherwise do the removal
var remaining = presentCount;
for (ItemStack match : matches) {
var toWithdraw = Math.min(match.getCount(), remaining);
match.shrink(toWithdraw);
remaining -= toWithdraw;
if (remaining <= 0) {
return true;
}
}
throw new IllegalStateException("unreachable");
}
/**
* The order/mode stacks should be discovered in
*/
protected enum StackDiscoveryMode {
/**
* When finding items to pick (hotbar)
*/
QUERY,
/**
* When extracting things
*/
EXTRACTION,
}
public abstract FrozenColorizer getColorizer();
public abstract void produceParticles(ParticleSpray particles, FrozenColorizer colorizer);
}

View file

@ -145,11 +145,6 @@ data class CastingEnvironment(
/**
* 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>): ItemStack? {
val operable = DiscoveryHandlers.collectOperableSlots(this)

View file

@ -1,665 +0,0 @@
package at.petrak.hexcasting.api.casting.eval
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.advancements.HexAdvancementTriggers
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus
import at.petrak.hexcasting.api.casting.ParticleSpray
import at.petrak.hexcasting.api.casting.PatternShapeMatch
import at.petrak.hexcasting.api.casting.PatternShapeMatch.*
import at.petrak.hexcasting.api.casting.SpellList
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.casting.eval.vm.FrameEvaluate
import at.petrak.hexcasting.api.casting.eval.vm.FunctionalData
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.iota.ListIota
import at.petrak.hexcasting.api.casting.iota.PatternIota
import at.petrak.hexcasting.api.casting.math.HexDir
import at.petrak.hexcasting.api.casting.math.HexPattern
import at.petrak.hexcasting.api.casting.mishaps.*
import at.petrak.hexcasting.api.misc.DiscoveryHandlers
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.misc.HexDamageSources
import at.petrak.hexcasting.api.mod.HexConfig
import at.petrak.hexcasting.api.mod.HexStatistics
import at.petrak.hexcasting.api.mod.HexTags
import at.petrak.hexcasting.api.utils.*
import at.petrak.hexcasting.common.casting.PatternRegistryManifest
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes
import at.petrak.hexcasting.xplat.IXplatAbstractions
import com.mojang.datafixers.util.Either
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundSource
import net.minecraft.util.Mth
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.phys.Vec3
import kotlin.math.max
import kotlin.math.min
/**
* Keeps track of a player casting a spell on the server.
* It's stored as NBT on the player.
*
* TODO oh god this entire class needs a gigantic refactor. why are there like 6 different entrypoints for casting
* a pattern. oh god.
*/
class CastingHarness private constructor(
var stack: MutableList<Iota>,
var ravenmind: Iota?,
var parenCount: Int,
var parenthesized: List<Iota>,
var escapeNext: Boolean,
val ctx: CastingEnvironment,
val prepackagedColorizer: FrozenColorizer? // for trinkets with colorizers
) {
@JvmOverloads
constructor(
ctx: CastingEnvironment,
prepackagedColorizer: FrozenColorizer? = null
) : this(mutableListOf(), null, 0, mutableListOf(), false, ctx, prepackagedColorizer)
/**
* Execute a single iota.
*/
fun executeIota(iota: Iota, world: ServerLevel): ControllerInfo = executeIotas(listOf(iota), world)
private fun displayPatternDebug(escapeNext: Boolean, parenCount: Int, iotaRepresentation: Component) {
if (this.ctx.debugPatterns) {
val display = " ".repeat(parenCount).asTextComponent
if (escapeNext)
display.append("\\ ".asTextComponent.gold)
display.append(iotaRepresentation)
this.ctx.caster.sendSystemMessage(display)
}
}
/**
* Given a list of iotas, execute them in sequence.
*/
fun executeIotas(iotas: List<Iota>, world: ServerLevel): ControllerInfo {
// Initialize the continuation stack to a single top-level eval for all iotas.
var continuation = SpellContinuation.Done.pushFrame(FrameEvaluate(SpellList.LList(0, iotas), false))
// Begin aggregating info
val info = TempControllerInfo(earlyExit = false)
var lastResolutionType = ResolvedPatternType.UNRESOLVED
var sound = HexEvalSounds.NOTHING
while (continuation is SpellContinuation.NotDone && !info.earlyExit) {
// Take the top of the continuation stack...
val next = continuation.frame
// ...and execute it.
// TODO there used to be error checking code here; I'm pretty sure any and all mishaps should already
// get caught and folded into CastResult by evaluate.
val result = next.evaluate(continuation.next, world, this)
// Then write all pertinent data back to the harness for the next iteration.
if (result.newData != null) {
this.applyFunctionalData(result.newData)
}
continuation = result.continuation
lastResolutionType = result.resolutionType
performSideEffects(info, result.sideEffects)
info.earlyExit = info.earlyExit || !lastResolutionType.success
sound = if (result.sound == HexEvalSounds.MISHAP) {
HexEvalSounds.MISHAP
} else {
sound.greaterOf(result.sound)
}
}
sound.sound?.let {
this.ctx.world.playSound(
null, this.ctx.position.x, this.ctx.position.y, this.ctx.position.z, it,
SoundSource.PLAYERS, 1f, 1f
)
// TODO: is it worth mixing in to the immut map and making our own game event with blackjack and hookers
this.ctx.world.gameEvent(this.ctx.caster, GameEvent.ITEM_INTERACT_FINISH, this.ctx.position)
}
if (continuation is SpellContinuation.NotDone) {
lastResolutionType =
if (lastResolutionType.success) ResolvedPatternType.EVALUATED else ResolvedPatternType.ERRORED
}
val (stackDescs, parenDescs, ravenmind) = generateDescs()
return ControllerInfo(
this.stack.isEmpty() && this.parenCount == 0 && !this.escapeNext,
lastResolutionType,
stackDescs,
parenDescs,
ravenmind,
this.parenCount
)
}
/**
* this DOES NOT THROW THINGS
*/
@Throws()
fun getUpdate(iota: Iota, world: ServerLevel, continuation: SpellContinuation): CastResult {
try {
// TODO we can have a special intro/retro sound
// ALSO TODO need to add reader macro-style things
try {
this.handleParentheses(iota)?.let { (data, resolutionType) ->
return@getUpdate CastResult(continuation, data, listOf(), resolutionType, HexEvalSounds.OPERATOR)
}
} catch (e: MishapTooManyCloseParens) {
// This is ridiculous and needs to be fixed
return CastResult(
continuation,
null,
listOf(
OperatorSideEffect.DoMishap(
e,
Mishap.Context(
(iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST),
HexAPI.instance().getRawHookI18n(HexAPI.modLoc("close_paren"))
)
)
),
ResolvedPatternType.ERRORED,
HexEvalSounds.MISHAP
)
}
if (iota is PatternIota) {
return updateWithPattern(iota.pattern, world, continuation)
} else {
return CastResult(
continuation,
null,
listOf(
OperatorSideEffect.DoMishap(
MishapUnescapedValue(iota),
Mishap.Context(HexPattern(HexDir.WEST), null)
)
), // Should never matter
ResolvedPatternType.INVALID,
HexEvalSounds.MISHAP
)
}
} catch (exception: Exception) {
// This means something very bad has happened
exception.printStackTrace()
return CastResult(
continuation,
null,
listOf(
OperatorSideEffect.DoMishap(
MishapError(exception),
Mishap.Context(
(iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST),
null
)
)
),
ResolvedPatternType.ERRORED,
HexEvalSounds.MISHAP
)
}
}
/**
* When the server gets a packet from the client with a new pattern,
* handle it functionally.
*/
private fun updateWithPattern(newPat: HexPattern, world: ServerLevel, continuation: SpellContinuation): CastResult {
var castedName: Component? = null
try {
val lookup = PatternRegistryManifest.matchPattern(newPat, world, false)
val lookupResult: Either<Action, List<OperatorSideEffect>> = if (lookup is Normal || lookup is PerWorld) {
val key = when (lookup) {
is Normal -> lookup.key
is PerWorld -> lookup.key
else -> throw IllegalStateException()
}
val reqsEnlightenment = isOfTag(IXplatAbstractions.INSTANCE.actionRegistry, key, HexTags.Actions.REQUIRES_ENLIGHTENMENT)
val canEnlighten = isOfTag(IXplatAbstractions.INSTANCE.actionRegistry, key, HexTags.Actions.CAN_START_ENLIGHTEN)
castedName = HexAPI.instance().getActionI18n(key, reqsEnlightenment)
if (!ctx.isCasterEnlightened && reqsEnlightenment) {
Either.right(listOf(OperatorSideEffect.RequiredEnlightenment(canEnlighten)))
} else {
val regiEntry = IXplatAbstractions.INSTANCE.actionRegistry.get(key)!!
Either.left(regiEntry.action)
}
} else if (lookup is Special) {
castedName = lookup.handler.name
Either.left(lookup.handler.act())
} else if (lookup is PatternShapeMatch.Nothing) {
throw MishapInvalidPattern()
} else {
throw IllegalStateException()
}
// TODO: the config denylist should be handled per VM type.
// I just removed it for now, should re-add it...
val sideEffects = mutableListOf<OperatorSideEffect>()
var stack2: List<Iota>? = null
var cont2 = continuation
var ravenmind2: Iota? = null
if (lookupResult.left().isPresent) {
val action = lookupResult.left().get()
displayPatternDebug(false, 0, castedName)
val result = action.operate(
continuation,
this.stack.toMutableList(),
this.ravenmind,
this.ctx
)
cont2 = result.newContinuation
stack2 = result.newStack
ravenmind2 = result.newRavenmind
// TODO parens also break prescience
sideEffects.addAll(result.sideEffects)
} else {
val problems = lookupResult.right().get()
sideEffects.addAll(problems)
}
// Stick a poofy particle effect at the caster position
// TODO again this should be on the VM lalala
if (this.ctx.spellCircle == null)
sideEffects.add(
OperatorSideEffect.Particles(
ParticleSpray(
this.ctx.position,
Vec3(0.0, 1.0, 0.0),
0.5, 1.0
)
)
)
val hereFd = this.getFunctionalData()
val fd = if (stack2 != null) {
hereFd.copy(
stack = stack2,
ravenmind = ravenmind2
)
} else {
hereFd
}
// TODO again this should be per VM
var soundType = if (this.ctx.source == CastingEnvironment.CastSource.STAFF) {
HexEvalSounds.OPERATOR
} else {
HexEvalSounds.NOTHING
}
for (se in sideEffects) {
if (se is OperatorSideEffect.AttemptSpell) {
soundType = if (se.hasCastingSound) {
soundType.greaterOf(HexEvalSounds.SPELL)
} else {
// WITH CATLIKE TREAD
// UPON OUR PREY WE STEAL
HexEvalSounds.NOTHING
}
} else if (se is OperatorSideEffect.DoMishap) {
soundType = HexEvalSounds.MISHAP
}
}
return CastResult(
cont2,
fd,
sideEffects,
ResolvedPatternType.EVALUATED,
soundType,
)
} catch (mishap: Mishap) {
return CastResult(
continuation,
null,
listOf(OperatorSideEffect.DoMishap(mishap, Mishap.Context(newPat, castedName))),
mishap.resolutionType(ctx),
HexEvalSounds.MISHAP
)
}
}
/**
* Execute the side effects of a pattern, updating our aggregated info.
*/
fun performSideEffects(info: TempControllerInfo, sideEffects: List<OperatorSideEffect>) {
for (haskellProgrammersShakingandCryingRN in sideEffects) {
val mustStop = haskellProgrammersShakingandCryingRN.performEffect(this)
if (mustStop) {
info.earlyExit = true
break
}
}
}
fun generateDescs() = Triple(
stack.map(HexIotaTypes::serialize),
parenthesized.map(HexIotaTypes::serialize),
ravenmind?.let(HexIotaTypes::serialize)
)
/**
* Return the functional update represented by the current state (for use with `copy`)
*/
fun getFunctionalData() = FunctionalData(
this.stack.toList(),
this.parenCount,
this.parenthesized.toList(),
this.escapeNext,
this.ravenmind,
)
/**
* Apply the functional update.
*/
fun applyFunctionalData(data: FunctionalData) {
this.stack.clear()
this.stack.addAll(data.stack)
this.parenCount = data.parenCount
this.parenthesized = data.parenthesized
this.escapeNext = data.escapeNext
this.ravenmind = data.ravenmind
}
/**
* Return a non-null value if we handled this in some sort of parenthesey way,
* either escaping it onto the stack or changing the parenthese-handling state.
*/
@Throws(MishapTooManyCloseParens::class)
private fun handleParentheses(iota: Iota): Pair<FunctionalData, ResolvedPatternType>? {
val sig = (iota as? PatternIota)?.pattern?.anglesSignature()
var displayDepth = this.parenCount
val out = if (this.parenCount > 0) {
if (this.escapeNext) {
val newParens = this.parenthesized.toMutableList()
newParens.add(iota)
this.getFunctionalData().copy(
escapeNext = false,
parenthesized = newParens
) to ResolvedPatternType.ESCAPED
} else {
when (sig) {
SpecialPatterns.CONSIDERATION.anglesSignature() -> {
this.getFunctionalData().copy(
escapeNext = true,
) to ResolvedPatternType.EVALUATED
}
SpecialPatterns.INTROSPECTION.anglesSignature() -> {
// we have escaped the parens onto the stack; we just also record our count.
val newParens = this.parenthesized.toMutableList()
newParens.add(iota)
this.getFunctionalData().copy(
parenthesized = newParens,
parenCount = this.parenCount + 1
) to if (this.parenCount == 0) ResolvedPatternType.EVALUATED else ResolvedPatternType.ESCAPED
}
SpecialPatterns.RETROSPECTION.anglesSignature() -> {
val newParenCount = this.parenCount - 1
displayDepth--
if (newParenCount == 0) {
val newStack = this.stack.toMutableList()
newStack.add(ListIota(this.parenthesized.toList()))
this.getFunctionalData().copy(
stack = newStack,
parenCount = newParenCount,
parenthesized = listOf()
) to ResolvedPatternType.EVALUATED
} else if (newParenCount < 0) {
throw MishapTooManyCloseParens()
} else {
// we have this situation: "(()"
// we need to add the close paren
val newParens = this.parenthesized.toMutableList()
newParens.add(iota)
this.getFunctionalData().copy(
parenCount = newParenCount,
parenthesized = newParens
) to ResolvedPatternType.ESCAPED
}
}
else -> {
val newParens = this.parenthesized.toMutableList()
newParens.add(iota)
this.getFunctionalData().copy(
parenthesized = newParens
) to ResolvedPatternType.ESCAPED
}
}
}
} else if (this.escapeNext) {
val newStack = this.stack.toMutableList()
newStack.add(iota)
this.getFunctionalData().copy(
stack = newStack,
escapeNext = false,
) to ResolvedPatternType.ESCAPED
} else {
when (sig) {
SpecialPatterns.CONSIDERATION.anglesSignature() -> {
this.getFunctionalData().copy(
escapeNext = true
) to ResolvedPatternType.EVALUATED
}
SpecialPatterns.INTROSPECTION.anglesSignature() -> {
this.getFunctionalData().copy(
parenCount = this.parenCount + 1
) to ResolvedPatternType.EVALUATED
}
SpecialPatterns.RETROSPECTION.anglesSignature() -> {
throw MishapTooManyCloseParens()
}
else -> {
null
}
}
}
// TODO: replace this once we can read things from the client
/*
if (out != null) {
val display = if (iota is PatternIota) {
PatternNameHelper.representationForPattern(iota.pattern)
.copy()
.withStyle(if (out.second == ResolvedPatternType.ESCAPED) ChatFormatting.YELLOW else ChatFormatting.AQUA)
} else iota.display()
displayPatternDebug(this.escapeNext, displayDepth, display)
}
*/
return out
}
/**
* Might cast from hitpoints.
* Returns the media cost still remaining after we deplete everything. It will be <= 0 if we could pay for it.
*
* Also awards stats and achievements and such
*/
fun withdrawMedia(mediaCost: Int, allowOvercast: Boolean): Int {
// prevent poor impls from gaining you media
if (mediaCost <= 0) return 0
var costLeft = mediaCost
val fake = this.ctx.caster.isCreative
if (this.ctx.spellCircle != null) {
if (fake)
return 0
val tile = this.ctx.world.getBlockEntity(this.ctx.spellCircle.impetusPos)
if (tile is BlockEntityAbstractImpetus) {
val mediaAvailable = tile.media
if (mediaAvailable < 0)
return 0
val mediaToTake = min(costLeft, mediaAvailable)
costLeft -= mediaToTake
tile.media = mediaAvailable - mediaToTake
}
} else {
val casterStack = this.ctx.caster.getItemInHand(this.ctx.castingHand)
val casterMediaHolder = IXplatAbstractions.INSTANCE.findMediaHolder(casterStack)
val casterHexHolder = IXplatAbstractions.INSTANCE.findHexHolder(casterStack)
val hexHolderDrawsFromInventory = if (casterHexHolder != null) {
if (casterMediaHolder != null) {
val mediaAvailable = casterMediaHolder.withdrawMedia(-1, true)
val mediaToTake = min(costLeft, mediaAvailable)
if (!fake) casterMediaHolder.withdrawMedia(mediaToTake, false)
costLeft -= mediaToTake
}
casterHexHolder.canDrawMediaFromInventory()
} else {
false
}
if (casterStack.`is`(HexTags.Items.STAVES) || hexHolderDrawsFromInventory) {
val mediaSources = DiscoveryHandlers.collectMediaHolders(this)
.sortedWith(Comparator(::compareMediaItem).reversed())
for (source in mediaSources) {
costLeft -= extractMedia(source, costLeft, simulate = fake)
if (costLeft <= 0)
break
}
if (allowOvercast && costLeft > 0) {
// Cast from HP!
val mediaToHealth = HexConfig.common().mediaToHealthRate()
val healthToRemove = max(costLeft.toDouble() / mediaToHealth, 0.5)
val mediaAbleToCastFromHP = this.ctx.caster.health * mediaToHealth
val mediaToActuallyPayFor = min(mediaAbleToCastFromHP.toInt(), costLeft)
costLeft -= if (!fake) {
Mishap.trulyHurt(this.ctx.caster, HexDamageSources.OVERCAST, healthToRemove.toFloat())
val actuallyTaken = Mth.ceil(mediaAbleToCastFromHP - (this.ctx.caster.health * mediaToHealth))
HexAdvancementTriggers.OVERCAST_TRIGGER.trigger(this.ctx.caster, actuallyTaken)
this.ctx.caster.awardStat(HexStatistics.MEDIA_OVERCAST, mediaCost - costLeft)
actuallyTaken
} else {
mediaToActuallyPayFor
}
}
}
}
if (!fake) {
// this might be more than the media cost! for example if we waste a lot of media from an item
this.ctx.caster.awardStat(HexStatistics.MEDIA_USED, mediaCost - costLeft)
HexAdvancementTriggers.SPEND_MEDIA_TRIGGER.trigger(
this.ctx.caster,
mediaCost - costLeft,
if (costLeft < 0) -costLeft else 0
)
}
return if (fake) 0 else costLeft
}
fun getColorizer(): FrozenColorizer {
if (this.prepackagedColorizer != null)
return this.prepackagedColorizer
return IXplatAbstractions.INSTANCE.getColorizer(this.ctx.caster)
}
fun serializeToNBT() = NBTBuilder {
TAG_STACK %= stack.serializeToNBT()
if (ravenmind != null)
TAG_LOCAL %= HexIotaTypes.serialize(ravenmind!!)
TAG_PAREN_COUNT %= parenCount
TAG_ESCAPE_NEXT %= escapeNext
TAG_PARENTHESIZED %= parenthesized.serializeToNBT()
if (prepackagedColorizer != null)
TAG_PREPACKAGED_COLORIZER %= prepackagedColorizer.serializeToNBT()
}
companion object {
const val TAG_STACK = "stack"
const val TAG_LOCAL = "local"
const val TAG_PAREN_COUNT = "open_parens"
const val TAG_PARENTHESIZED = "parenthesized"
const val TAG_ESCAPE_NEXT = "escape_next"
const val TAG_PREPACKAGED_COLORIZER = "prepackaged_colorizer"
init {
DiscoveryHandlers.addMediaHolderDiscoverer {
it.ctx.caster.inventory.items
.filter(::isMediaItem)
.mapNotNull(IXplatAbstractions.INSTANCE::findMediaHolder)
}
DiscoveryHandlers.addMediaHolderDiscoverer {
it.ctx.caster.inventory.armor
.filter(::isMediaItem)
.mapNotNull(IXplatAbstractions.INSTANCE::findMediaHolder)
}
DiscoveryHandlers.addMediaHolderDiscoverer {
it.ctx.caster.inventory.offhand
.filter(::isMediaItem)
.mapNotNull(IXplatAbstractions.INSTANCE::findMediaHolder)
}
}
@JvmStatic
fun fromNBT(nbt: CompoundTag, ctx: CastingEnvironment): CastingHarness {
return try {
val stack = mutableListOf<Iota>()
val stackTag = nbt.getList(TAG_STACK, Tag.TAG_COMPOUND)
for (subtag in stackTag) {
val datum = HexIotaTypes.deserialize(subtag.asCompound, ctx.world)
stack.add(datum)
}
val ravenmind = if (nbt.contains(TAG_LOCAL))
HexIotaTypes.deserialize(nbt.getCompound(TAG_LOCAL), ctx.world)
else
null
val parenthesized = mutableListOf<Iota>()
val parenTag = nbt.getList(TAG_PARENTHESIZED, Tag.TAG_COMPOUND)
for (subtag in parenTag) {
parenthesized.add(HexIotaTypes.deserialize(subtag.downcast(CompoundTag.TYPE), ctx.world))
}
val parenCount = nbt.getInt(TAG_PAREN_COUNT)
val escapeNext = nbt.getBoolean(TAG_ESCAPE_NEXT)
val colorizer = if (nbt.contains(TAG_PREPACKAGED_COLORIZER)) {
FrozenColorizer.fromNBT(nbt.getCompound(TAG_PREPACKAGED_COLORIZER))
} else {
null
}
CastingHarness(stack, ravenmind, parenCount, parenthesized, escapeNext, ctx, colorizer)
} catch (exn: Exception) {
CastingHarness(ctx)
}
}
}
data class TempControllerInfo(
var earlyExit: Boolean,
)
}

View file

@ -1,16 +0,0 @@
package at.petrak.hexcasting.api.casting.eval
import net.minecraft.nbt.CompoundTag
/**
* Information for the sake of the GUI.
*/
data class ControllerInfo(
val isStackClear: Boolean,
val resolutionType: ResolvedPatternType,
val stack: List<CompoundTag>,
val parenthesized: List<CompoundTag>,
val ravenmind: CompoundTag?,
val parenCount: Int,
)

View file

@ -0,0 +1,17 @@
package at.petrak.hexcasting.api.casting.eval
import net.minecraft.nbt.CompoundTag
/**
* Information sent back to the client
*/
data class ExecutionClientView(
val isStackClear: Boolean,
val resolutionType: ResolvedPatternType,
// These must be tags so the wrapping of the text can happen on the client
// otherwise we don't know when to stop rendering
val stackDescs: List<CompoundTag>,
val ravenmind: CompoundTag?,
)

View file

@ -0,0 +1,50 @@
package at.petrak.hexcasting.api.casting.eval;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
/**
* Kinda like {@link CastingEnvironment} but for executing mishaps.
* <p>
* To avoid horrible O(mn) scope problems we offer a set of stock bad effects.
* The player is exposed nullably if you like though.
*/
public abstract class MishapEnvironment {
@Nullable
protected final ServerPlayer caster;
protected final ServerLevel world;
protected MishapEnvironment(ServerLevel world, @Nullable ServerPlayer caster) {
this.caster = caster;
this.world = world;
}
public abstract void yeetHeldItemsTowards(Vec3 targetPos);
public abstract void dropHeldItems();
public abstract void drown();
public abstract void damage(float healthProportion);
public abstract void removeXp(int amount);
public abstract void blind(int ticks);
protected void yeetItem(ItemStack stack, Vec3 srcPos, Vec3 delta) {
var entity = new ItemEntity(
this.world,
srcPos.x, srcPos.y, srcPos.z,
stack,
delta.x + (Math.random() - 0.5) * 0.1,
delta.y + (Math.random() - 0.5) * 0.1,
delta.z + (Math.random() - 0.5) * 0.1
);
entity.setPickUpDelay(40);
this.world.addWithUUID(entity);
}
}

View file

@ -1,15 +1,16 @@
package at.petrak.hexcasting.api.casting
package at.petrak.hexcasting.api.casting.eval
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.iota.Iota
import net.minecraft.nbt.CompoundTag
/**
* What happens when an operator is through?
*/
data class OperationResult(
val newContinuation: SpellContinuation,
val newStack: List<Iota>,
val newRavenmind: Iota?,
val sideEffects: List<OperatorSideEffect>
val newUserdata: CompoundTag,
val sideEffects: List<OperatorSideEffect>,
val newContinuation: SpellContinuation
)

View file

@ -0,0 +1,90 @@
# How Casting Works
because I keep forgetting.
- [CastingVM][] is the casting virtual machine. It is what figures out what to do with an incoming iota to be
executed,
producing a functional state update. This is transient and is reconstructed every time the whole state needs to be
saved and loaded.
- [CastingImage][] is the state of the cast itself. If [CastingVM][] is an emulator, [CastingImage][] is a
snapshot of the
emulator's memory. This is the only thing serialized to NBT.
- [CastingEnvironment][] is what is doing the casting, abstractly. Stuff like "the player with a staff," "the player
with a trinket," "a spell circle." This is an abstract class that Hexcasting (and addons!) make subclasses of.
## Beware The Pipeline
1. An iota or list of iotas come to be executed. This is the entrypoint to the VM,
`CastingVM#queueAndExecuteIotas`, and returns an [ExecutionClientView][] (might change later).
2. Those iotas are put into a [ContinuationFrame][] (specifically, [FrameEvaluate][]).
3. While there are still frames, control flows to the top one.
4. The frame (usually) returns control flow back to the VM by calling `CastingVM#executeInner` with an iota.
5. `executeInner` first does some pre-checks with intro/retro/consideration (like escaping embedded iotas), but usually
makes sure that the passed iota is a pattern and passes *that* on to `executePattern`.
6. `executePattern` is where the execution actually happens. The pattern is matched to an action or special handler.
and executed.
7. The execution returns an [OperationResult], containing:
- the rest of the current continuation (read as: the patterns to execute next);
- the updated state of the [CastingImage];
- a list of `OperatorSideEffects`, like withdrawing media, casting spells or mishaping;
8. The operation result is composed with some misc display info, like the color the pattern should be when drawn to
the staff GUI and the sound.
9. Each of the side effects is applied to the world in turn. If any of them make the casting stop (like mishaping),
then it does!
10. If there's still iotas left in the current continuation, then control goes back to step 5, called on the next iota
in the continuation.
11. Otherwise, control goes to the top frame of the stack (step 3). And if there are no stack frames, execution is
finished! What a ride.
## The Hell's A Continuation And A Continuation Frame
~~IDFK ask Alwinfy.~~ <- Disregard this, I am a goober
A [continuation][Continuation] roughly represents a "call stack" in a more traditional VM structure.
It is a stack (implemented as a linked list) of [continuation frames][ContinuationFrame] ("call frames" in more
traditional nomenclature, where each frame is usually a list of iotas remaining to execute.
While there are frames left on the Continuation stack, the topmost frame is told to execute. During execution,
a frame can push more frames to execute (Hermes' Gambit does this), pop itself (once a Hermes execution
finishes), or even remove frames below it (Charon's Gambit)!
There are three types of frames:
1. [FrameEvaluate][] is a list of iotas to execute. The VM will step through the list and execute each pattern.
Once its patterns are exhausted, it pops itself (returning flow control to the frame below).
- For staffcasting, each pattern drawn spins up the VM with a `FrameEvaluate` containing a single pattern.
- For trinket casting, the VM gets a `FrameEvaluate` again, but the continuation list it provides has
all the patterns in the trinket.
- Hermes' Gambit pushes a new `FrameEvaluate` to the continuation stack with the pattern list argument inside it.
2. [FrameForEach][] manages the state of a Thoth's Gambit.
- It stores the template data-stack, the list of remaining values to foreach over, and the accumulated output list.
- When told to execute, it will push a `FrameEvaluate` for the next iteration and push the next value;
it will also append the previous iteration's values to the accumulated output.
- Once finished, `FrameForEach` will append the final output list to the data stack, then pop itself.
3. [FrameFinishEval][] does not perform any function; when executed it simply pops itself.
- Its purpose is to serve as a marker for Charon's Gambit to know how many frames to abort.
In the "call stack" analogy, this is a "catch" handler which serves as a counterpart to Charon's Gambit "throwing".
- Charon's Gambit pops continuation frames until it reaches a FrameFinishEval.
- Hermes' Gambit pushes this before pushing a FrameEvaluate, so that a Charon will abort down to the FrameEvaluate, and no further.
[CastingVM]: vm/CastingVM.kt
[CastingImage]: vm/CastingImage.kt
[CastingEnvironment]: CastingEnvironment.java
[ExecutionClientView]: ExecutionClientView.kt
[ContinuationFrame]: vm/ContinuationFrame.kt
[Continuation]: vm/SpellContinuation.kt
[FrameEvaluate]: vm/FrameEvaluate.kt
[FrameForEach]: vm/FrameForEach.kt
[FrameFinishEval]: vm/FrameFinishEval.kt
[OperationResult]: OperationResult.kt
[CastResult]: CastResult.kt

View file

@ -1,10 +1,9 @@
package at.petrak.hexcasting.api.casting.eval.sideeffects
import at.petrak.hexcasting.api.advancements.HexAdvancementTriggers
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus
import at.petrak.hexcasting.api.casting.ParticleSpray
import at.petrak.hexcasting.api.casting.RenderedSpell
import at.petrak.hexcasting.api.casting.eval.CastingHarness
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM
import at.petrak.hexcasting.api.casting.mishaps.Mishap
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.mod.HexStatistics
@ -19,14 +18,14 @@ import net.minecraft.world.item.ItemStack
*/
sealed class OperatorSideEffect {
/** Return whether to cancel all further [OperatorSideEffect] */
abstract fun performEffect(harness: CastingHarness): Boolean
abstract fun performEffect(harness: CastingVM): Boolean
data class RequiredEnlightenment(val awardStat: Boolean) : OperatorSideEffect() {
override fun performEffect(harness: CastingHarness): Boolean {
harness.ctx.caster.sendSystemMessage("hexcasting.message.cant_great_spell".asTranslatedComponent)
override fun performEffect(harness: CastingVM): Boolean {
harness.env.caster?.sendSystemMessage("hexcasting.message.cant_great_spell".asTranslatedComponent)
if (awardStat)
HexAdvancementTriggers.FAIL_GREAT_SPELL_TRIGGER.trigger(harness.ctx.caster)
HexAdvancementTriggers.FAIL_GREAT_SPELL_TRIGGER.trigger(harness.env.caster)
return true
}
@ -39,60 +38,45 @@ sealed class OperatorSideEffect {
val awardStat: Boolean = true
) :
OperatorSideEffect() {
override fun performEffect(harness: CastingHarness): Boolean {
this.spell.cast(harness.ctx)
override fun performEffect(harness: CastingVM): Boolean {
this.spell.cast(harness.env)
if (awardStat)
harness.ctx.caster.awardStat(HexStatistics.SPELLS_CAST)
harness.env.caster?.awardStat(HexStatistics.SPELLS_CAST)
return false
}
}
data class ConsumeMedia(val amount: Int) : OperatorSideEffect() {
override fun performEffect(harness: CastingHarness): Boolean {
val overcastOk = harness.ctx.canOvercast
val leftoverMedia = harness.withdrawMedia(this.amount, overcastOk)
if (leftoverMedia > 0 && !overcastOk) {
harness.ctx.caster.sendSystemMessage("hexcasting.message.cant_overcast".asTranslatedComponent)
}
override fun performEffect(harness: CastingVM): Boolean {
val leftoverMedia = harness.env.extractMedia(this.amount.toLong())
return leftoverMedia > 0
}
}
data class Particles(val spray: ParticleSpray) : OperatorSideEffect() {
override fun performEffect(harness: CastingHarness): Boolean {
this.spray.sprayParticles(harness.ctx.world, harness.getColorizer())
override fun performEffect(harness: CastingVM): Boolean {
harness.env.produceParticles(this.spray, harness.env.colorizer)
this.spray.sprayParticles(harness.env.world, harness.env.colorizer)
return false
}
}
data class DoMishap(val mishap: Mishap, val errorCtx: Mishap.Context) : OperatorSideEffect() {
override fun performEffect(harness: CastingHarness): Boolean {
val msg = mishap.errorMessageWithName(harness.ctx, errorCtx);
if (harness.ctx.spellCircle != null) {
val tile = harness.ctx.world.getBlockEntity(harness.ctx.spellCircle.impetusPos)
if (tile is BlockEntityAbstractImpetus) {
tile.lastMishap = msg
tile.setChanged()
}
} else {
// for now
harness.ctx.caster.sendSystemMessage(msg)
}
val spray = mishap.particleSpray(harness.ctx)
val color = mishap.accentColor(harness.ctx, errorCtx)
spray.sprayParticles(harness.ctx.world, color)
override fun performEffect(harness: CastingVM): Boolean {
val spray = mishap.particleSpray(harness.env)
val color = mishap.accentColor(harness.env, errorCtx)
spray.sprayParticles(harness.env.world, color)
spray.sprayParticles(
harness.ctx.world,
harness.env.world,
FrozenColorizer(
ItemStack(HexItems.DYE_COLORIZERS[DyeColor.RED]!!),
Util.NIL_UUID
)
)
mishap.execute(harness.ctx, errorCtx, harness.stack)
mishap.execute(harness.env, errorCtx, harness.image.stack.toMutableList())
return true
}

View file

@ -0,0 +1,91 @@
package at.petrak.hexcasting.api.casting.eval.vm
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.iota.IotaType
import at.petrak.hexcasting.api.utils.*
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.Entity
/**
* The state of a casting VM, containing the stack and all
*/
data class CastingImage private constructor(
val stack: List<Iota>,
val parenCount: Int,
val parenthesized: List<Iota>,
val escapeNext: Boolean,
val userData: CompoundTag
) {
constructor() : this(listOf(), 0, listOf(), false, CompoundTag())
fun serializeToNbt() = NBTBuilder {
TAG_STACK %= stack.serializeToNBT()
TAG_PAREN_COUNT %= parenCount
TAG_ESCAPE_NEXT %= escapeNext
TAG_PARENTHESIZED %= parenthesized.serializeToNBT()
TAG_USERDATA %= userData
}
companion object {
const val TAG_STACK = "stack"
const val TAG_PAREN_COUNT = "open_parens"
const val TAG_PARENTHESIZED = "parenthesized"
const val TAG_ESCAPE_NEXT = "escape_next"
const val TAG_USERDATA = "userdata"
const val TAG_RAVENMIND = "ravenmind"
@JvmStatic
public fun loadFromNbt(tag: CompoundTag, world: ServerLevel): CastingImage {
return try {
val stack = mutableListOf<Iota>()
val stackTag = tag.getList(TAG_STACK, Tag.TAG_COMPOUND)
for (subtag in stackTag) {
val datum = IotaType.deserialize(subtag.asCompound, world)
stack.add(datum)
}
val userData = if (tag.contains(TAG_USERDATA)) {
tag.getCompound(TAG_USERDATA)
} else if (tag.contains("local")) {
NBTBuilder {
TAG_USERDATA %= tag.getCompound("local")
}
} else {
CompoundTag()
}
val parenthesized = mutableListOf<Iota>()
val parenTag = tag.getList(TAG_PARENTHESIZED, Tag.TAG_COMPOUND)
for (subtag in parenTag) {
parenthesized.add(IotaType.deserialize(subtag.downcast(CompoundTag.TYPE), world))
}
val parenCount = tag.getInt(TAG_PAREN_COUNT)
val escapeNext = tag.getBoolean(TAG_ESCAPE_NEXT)
CastingImage(stack, parenCount, parenthesized, escapeNext, userData)
} catch (exn: Exception) {
HexAPI.LOGGER.warn("error while loading a CastingImage", exn)
CastingImage()
}
}
@JvmStatic
fun checkAndMarkGivenMotion(userData: CompoundTag, entity: Entity): Boolean {
val marked = userData.getOrCreateCompound(HexAPI.MARKED_MOVED_USERDATA)
return if (marked.contains(entity.stringUUID)) {
true
} else {
marked.putBoolean(entity.stringUUID, true)
false
}
}
}
}

View file

@ -0,0 +1,379 @@
package at.petrak.hexcasting.api.casting.eval.vm
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.casting.PatternShapeMatch
import at.petrak.hexcasting.api.casting.PatternShapeMatch.*
import at.petrak.hexcasting.api.casting.SpellList
import at.petrak.hexcasting.api.casting.eval.*
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.iota.IotaType
import at.petrak.hexcasting.api.casting.iota.ListIota
import at.petrak.hexcasting.api.casting.iota.PatternIota
import at.petrak.hexcasting.api.casting.math.HexDir
import at.petrak.hexcasting.api.casting.math.HexPattern
import at.petrak.hexcasting.api.casting.mishaps.*
import at.petrak.hexcasting.api.mod.HexConfig
import at.petrak.hexcasting.api.mod.HexTags
import at.petrak.hexcasting.api.utils.*
import at.petrak.hexcasting.common.casting.PatternRegistryManifest
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerLevel
/**
* The virtual machine! This is the glue that determines the next iteration of a [CastingImage], using a
* [CastingEnvironment] to affect the world.
*/
class CastingVM(var image: CastingImage, val env: CastingEnvironment) {
/**
* Execute a single iota.
*/
fun queueAndExecuteIota(iota: Iota, world: ServerLevel): ExecutionClientView = queueAndExecuteIotas(listOf(iota), world)
/**
* The main entrypoint to the VM. Given a list of iotas, execute them in sequence, and return whatever the client
* needs to see.
*/
fun queueAndExecuteIotas(iotas: List<Iota>, world: ServerLevel): ExecutionClientView {
// Initialize the continuation stack to a single top-level eval for all iotas.
var continuation = SpellContinuation.Done.pushFrame(FrameEvaluate(SpellList.LList(0, iotas), false))
// Begin aggregating info
val info = TempControllerInfo(earlyExit = false)
var lastResolutionType = ResolvedPatternType.UNRESOLVED
while (continuation is SpellContinuation.NotDone && !info.earlyExit) {
// Take the top of the continuation stack...
val next = continuation.frame
// ...and execute it.
// TODO there used to be error checking code here; I'm pretty sure any and all mishaps should already
// get caught and folded into CastResult by evaluate.
val image2 = next.evaluate(continuation.next, world, this)
// Then write all pertinent data back to the harness for the next iteration.
if (image2.newData != null) {
this.image = image2.newData
}
this.env.postExecution(image2)
continuation = image2.continuation
lastResolutionType = image2.resolutionType
performSideEffects(info, image2.sideEffects)
info.earlyExit = info.earlyExit || !lastResolutionType.success
}
if (continuation is SpellContinuation.NotDone) {
lastResolutionType =
if (lastResolutionType.success) ResolvedPatternType.EVALUATED else ResolvedPatternType.ERRORED
}
val (stackDescs, ravenmind) = generateDescs()
val isStackClear = image.stack.isEmpty() && image.parenCount == 0 && !image.escapeNext
return ExecutionClientView(isStackClear, lastResolutionType, stackDescs, ravenmind)
}
/**
* this DOES NOT THROW THINGS
*/
@Throws()
fun executeInner(iota: Iota, world: ServerLevel, continuation: SpellContinuation): CastResult {
try {
// TODO we can have a special intro/retro sound
// ALSO TODO need to add reader macro-style things
try {
this.handleParentheses(iota)?.let { (data, resolutionType) ->
return@executeInner CastResult(continuation, data, listOf(), resolutionType, HexEvalSounds.ADD_PATTERN)
}
} catch (e: MishapTooManyCloseParens) {
// This is ridiculous and needs to be fixed
return CastResult(
continuation,
null,
listOf(
OperatorSideEffect.DoMishap(
e,
Mishap.Context(
(iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST),
HexAPI.instance().getRawHookI18n(HexAPI.modLoc("close_paren"))
)
)
),
ResolvedPatternType.ERRORED,
HexEvalSounds.MISHAP
)
}
if (iota is PatternIota) {
return executePattern(iota.pattern, world, continuation)
} else {
return CastResult(
continuation,
null,
listOf(
OperatorSideEffect.DoMishap(
MishapUnescapedValue(iota),
Mishap.Context(HexPattern(HexDir.WEST), null)
)
), // Should never matter
ResolvedPatternType.INVALID,
HexEvalSounds.MISHAP
)
}
} catch (exception: Exception) {
// This means something very bad has happened
exception.printStackTrace()
return CastResult(
continuation,
null,
listOf(
OperatorSideEffect.DoMishap(
MishapInternalException(exception),
Mishap.Context(
(iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST),
null
)
)
),
ResolvedPatternType.ERRORED,
HexEvalSounds.MISHAP
)
}
}
/**
* When the server gets a packet from the client with a new pattern,
* handle it functionally.
*/
private fun executePattern(newPat: HexPattern, world: ServerLevel, continuation: SpellContinuation): CastResult {
var castedName: Component? = null
try {
val lookup = PatternRegistryManifest.matchPattern(newPat, world, false)
this.env.precheckAction(lookup)
val action = if (lookup is Normal || lookup is PerWorld) {
val key = when (lookup) {
is Normal -> lookup.key
is PerWorld -> lookup.key
else -> throw IllegalStateException()
}
val reqsEnlightenment = isOfTag(IXplatAbstractions.INSTANCE.actionRegistry, key, HexTags.Actions.REQUIRES_ENLIGHTENMENT)
castedName = HexAPI.instance().getActionI18n(key, reqsEnlightenment)
IXplatAbstractions.INSTANCE.actionRegistry.get(key)!!.action
} else if (lookup is Special) {
castedName = lookup.handler.name
lookup.handler.act()
} else if (lookup is PatternShapeMatch.Nothing) {
throw MishapInvalidPattern()
} else throw IllegalStateException()
val opCount = if (this.image.userData.contains(HexAPI.OP_COUNT_USERDATA)) {
this.image.userData.getInt(HexAPI.OP_COUNT_USERDATA)
} else {
this.image.userData.putInt(HexAPI.OP_COUNT_USERDATA, 0)
0
}
if (opCount + 1 > HexConfig.server().maxOpCount()) {
throw MishapEvalTooMuch()
}
this.image.userData.putInt(HexAPI.OP_COUNT_USERDATA, opCount + 1)
val sideEffects = mutableListOf<OperatorSideEffect>()
var stack2: List<Iota>? = null
var cont2 = continuation
var userData2: CompoundTag? = null
val result = action.operate(
this.env,
this.image.stack.toMutableList(),
this.image.userData.copy(),
continuation
)
cont2 = result.newContinuation
stack2 = result.newStack
userData2 = result.newUserdata
// TODO parens also break prescience
sideEffects.addAll(result.sideEffects)
val hereFd = this.image
val fd = if (stack2 != null) {
hereFd.copy(
stack = stack2,
userData = userData2,
)
} else {
hereFd
}
return CastResult(
cont2,
fd,
sideEffects,
ResolvedPatternType.EVALUATED,
env.soundType,
)
} catch (mishap: Mishap) {
return CastResult(
continuation,
null,
listOf(OperatorSideEffect.DoMishap(mishap, Mishap.Context(newPat, castedName))),
mishap.resolutionType(env),
HexEvalSounds.MISHAP
)
}
}
/**
* Execute the side effects of a pattern, updating our aggregated info.
*/
fun performSideEffects(info: TempControllerInfo, sideEffects: List<OperatorSideEffect>) {
for (haskellProgrammersShakingandCryingRN in sideEffects) {
val mustStop = haskellProgrammersShakingandCryingRN.performEffect(this)
if (mustStop) {
info.earlyExit = true
break
}
}
}
fun generateDescs(): Pair<List<CompoundTag>, CompoundTag?> {
val stackDescs = this.image.stack.map { IotaType.serialize(it) }
val ravenmind = if (this.image.userData.contains(HexAPI.RAVENMIND_USERDATA)) {
this.image.userData.getCompound(HexAPI.RAVENMIND_USERDATA)
} else null
return Pair(stackDescs, ravenmind)
}
/**
* Return a non-null value if we handled this in some sort of parenthesey way,
* either escaping it onto the stack or changing the parenthese-handling state.
*/
@Throws(MishapTooManyCloseParens::class)
private fun handleParentheses(iota: Iota): Pair<CastingImage, ResolvedPatternType>? {
val sig = (iota as? PatternIota)?.pattern?.anglesSignature()
var displayDepth = this.image.parenCount
val out = if (displayDepth > 0) {
if (this.image.escapeNext) {
val newParens = this.image.parenthesized.toMutableList()
newParens.add(iota)
this.image.copy(
escapeNext = false,
parenthesized = newParens
) to ResolvedPatternType.ESCAPED
} else {
when (sig) {
SpecialPatterns.CONSIDERATION.anglesSignature() -> {
this.image.copy(
escapeNext = true,
) to ResolvedPatternType.EVALUATED
}
SpecialPatterns.INTROSPECTION.anglesSignature() -> {
// we have escaped the parens onto the stack; we just also record our count.
val newParens = this.image.parenthesized.toMutableList()
newParens.add(iota)
this.image.copy(
parenthesized = newParens,
parenCount = this.image.parenCount + 1
) to if (this.image.parenCount == 0) ResolvedPatternType.EVALUATED else ResolvedPatternType.ESCAPED
}
SpecialPatterns.RETROSPECTION.anglesSignature() -> {
val newParenCount = this.image.parenCount - 1
displayDepth--
if (newParenCount == 0) {
val newStack = this.image.stack.toMutableList()
newStack.add(ListIota(this.image.parenthesized.toList()))
this.image.copy(
stack = newStack,
parenCount = newParenCount,
parenthesized = listOf()
) to ResolvedPatternType.EVALUATED
} else if (newParenCount < 0) {
throw MishapTooManyCloseParens()
} else {
// we have this situation: "(()"
// we need to add the close paren
val newParens = this.image.parenthesized.toMutableList()
newParens.add(iota)
this.image.copy(
parenCount = newParenCount,
parenthesized = newParens
) to ResolvedPatternType.ESCAPED
}
}
else -> {
val newParens = this.image.parenthesized.toMutableList()
newParens.add(iota)
this.image.copy(
parenthesized = newParens
) to ResolvedPatternType.ESCAPED
}
}
}
} else if (this.image.escapeNext) {
val newStack = this.image.stack.toMutableList()
newStack.add(iota)
this.image.copy(
stack = newStack,
escapeNext = false,
) to ResolvedPatternType.ESCAPED
} else {
when (sig) {
SpecialPatterns.CONSIDERATION.anglesSignature() -> {
this.image.copy(
escapeNext = true
) to ResolvedPatternType.EVALUATED
}
SpecialPatterns.INTROSPECTION.anglesSignature() -> {
this.image.copy(
parenCount = this.image.parenCount + 1
) to ResolvedPatternType.EVALUATED
}
SpecialPatterns.RETROSPECTION.anglesSignature() -> {
throw MishapTooManyCloseParens()
}
else -> {
null
}
}
}
// TODO: replace this once we can read things from the client
/*
if (out != null) {
val display = if (iota is PatternIota) {
PatternNameHelper.representationForPattern(iota.pattern)
.copy()
.withStyle(if (out.second == ResolvedPatternType.ESCAPED) ChatFormatting.YELLOW else ChatFormatting.AQUA)
} else iota.display()
displayPatternDebug(this.escapeNext, displayDepth, display)
}
*/
return out
}
data class TempControllerInfo(
var earlyExit: Boolean,
)
companion object {
@JvmStatic
fun empty(env: CastingEnvironment): CastingVM {
return CastingVM(CastingImage(), env)
}
}
}

View file

@ -1,7 +1,6 @@
package at.petrak.hexcasting.api.casting.eval.vm
import at.petrak.hexcasting.api.casting.SpellList
import at.petrak.hexcasting.api.casting.eval.CastingHarness
import at.petrak.hexcasting.api.casting.eval.CastResult
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.utils.getList
@ -31,7 +30,7 @@ sealed interface ContinuationFrame {
* For Evaluate, this consumes one pattern; for ForEach this queues the next iteration of the outer loop.
* @return the result of this pattern step
*/
fun evaluate(continuation: SpellContinuation, level: ServerLevel, harness: CastingHarness): CastResult
fun evaluate(continuation: SpellContinuation, level: ServerLevel, harness: CastingVM): CastResult
/**
* The OpHalt instruction wants us to "jump to" the END of the nearest meta-eval.

View file

@ -2,7 +2,6 @@ package at.petrak.hexcasting.api.casting.eval.vm
import at.petrak.hexcasting.api.casting.SpellList
import at.petrak.hexcasting.api.casting.eval.CastResult
import at.petrak.hexcasting.api.casting.eval.CastingHarness
import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.utils.NBTBuilder
@ -23,7 +22,7 @@ data class FrameEvaluate(val list: SpellList, val isMetacasting: Boolean) : Cont
override fun evaluate(
continuation: SpellContinuation,
level: ServerLevel,
harness: CastingHarness
harness: CastingVM
): CastResult {
// If there are patterns left...
return if (list.nonEmpty) {
@ -32,7 +31,7 @@ data class FrameEvaluate(val list: SpellList, val isMetacasting: Boolean) : Cont
continuation.pushFrame(FrameEvaluate(list.cdr, this.isMetacasting))
} else continuation
// ...before evaluating the first one in the list.
val update = harness.getUpdate(list.car, level, newCont)
val update = harness.executeInner(list.car, level, newCont)
if (this.isMetacasting && update.sound != HexEvalSounds.MISHAP) {
update.copy(sound = HexEvalSounds.HERMES)
} else {

View file

@ -1,7 +1,6 @@
package at.petrak.hexcasting.api.casting.eval.vm
import at.petrak.hexcasting.api.casting.eval.CastResult
import at.petrak.hexcasting.api.casting.eval.CastingHarness
import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.utils.NBTBuilder
@ -20,11 +19,11 @@ object FrameFinishEval : ContinuationFrame {
override fun evaluate(
continuation: SpellContinuation,
level: ServerLevel,
harness: CastingHarness
harness: CastingVM
): CastResult {
return CastResult(
continuation,
FunctionalData(harness.stack.toList(), 0, listOf(), false, harness.ravenmind),
null,
listOf(),
ResolvedPatternType.EVALUATED,
HexEvalSounds.NOTHING,
@ -34,4 +33,4 @@ object FrameFinishEval : ContinuationFrame {
override fun serializeToNBT() = NBTBuilder {
"type" %= "end"
}
}
}

View file

@ -2,7 +2,6 @@ package at.petrak.hexcasting.api.casting.eval.vm
import at.petrak.hexcasting.api.casting.SpellList
import at.petrak.hexcasting.api.casting.eval.CastResult
import at.petrak.hexcasting.api.casting.eval.CastingHarness
import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.iota.ListIota
@ -39,38 +38,37 @@ data class FrameForEach(
override fun evaluate(
continuation: SpellContinuation,
level: ServerLevel,
harness: CastingHarness
harness: CastingVM
): CastResult {
// If this isn't the very first Thoth step (i.e. no Thoth computations run yet)...
val stack = if (baseStack == null) {
// init stack to the harness stack...
harness.stack.toList()
harness.image.stack.toList()
} else {
// else save the stack to the accumulator and reuse the saved base stack.
acc.addAll(harness.stack)
acc.addAll(harness.image.stack)
baseStack
}
// If we still have data to process...
val (stackTop, newCont) = if (data.nonEmpty) {
val (stackTop, newImage, newCont) = if (data.nonEmpty) {
// Increment the evaluation depth,
harness.ctx.incDepth()
// push the next datum to the top of the stack,
data.car to continuation
Triple(data.car, harness.image.incDepth(), continuation
// put the next Thoth object back on the stack for the next Thoth cycle,
.pushFrame(FrameForEach(data.cdr, code, stack, acc))
// and prep the Thoth'd code block for evaluation.
.pushFrame(FrameEvaluate(code, true))
.pushFrame(FrameEvaluate(code, true)))
} else {
// Else, dump our final list onto the stack.
ListIota(acc) to continuation
Triple(ListIota(acc), harness.image, continuation)
}
val tStack = stack.toMutableList()
tStack.add(stackTop)
// TODO: this means we could have Thoth casting do a different sound
return CastResult(
newCont,
FunctionalData(tStack, 0, listOf(), false, harness.ravenmind),
newImage.copy(stack = tStack),
listOf(),
ResolvedPatternType.EVALUATED,
HexEvalSounds.THOTH,
@ -85,4 +83,4 @@ data class FrameForEach(
"base" %= baseStack.serializeToNBT()
"accumulator" %= acc.serializeToNBT()
}
}
}

View file

@ -30,7 +30,7 @@ public abstract class Iota {
/**
* Serialize this under the {@code data} tag.
* <p>
* You probably don't want to call this directly; use {@link HexIotaTypes#serialize}.
* You probably don't want to call this directly; use {@link IotaType#serialize}.
*/
abstract public @NotNull Tag serialize();

View file

@ -1,15 +1,27 @@
package at.petrak.hexcasting.api.casting.iota;
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import com.mojang.datafixers.util.Pair;
import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.Font;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextColor;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.FormattedCharSequence;
import javax.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Objects;
// Take notes from ForgeRegistryEntry
public abstract class IotaType<T extends Iota> {
/**
* Spell datums are stored as such: {@code { "type": "modid:type", "datum": a_tag }}.
* <p>
@ -41,4 +53,149 @@ public abstract class IotaType<T extends Iota> {
return Component.translatable("hexcasting.iota." + key)
.withStyle(style -> style.withColor(TextColor.fromRgb(color())));
}
public static CompoundTag serialize(Iota iota) {
var type = iota.getType();
var typeId = HexIotaTypes.REGISTRY.getKey(type);
if (typeId == null) {
throw new IllegalStateException(
"Tried to serialize an unregistered iota type. Iota: " + iota
+ " ; Type" + type.getClass().getTypeName());
}
// We check if it's too big on serialization; if it is we just return a garbage.
if (iota instanceof ListIota listIota && isTooLargeToSerialize(listIota.getList())) {
// Garbage will never be too large so we just recurse
return serialize(new GarbageIota());
}
var dataTag = iota.serialize();
var out = new CompoundTag();
out.putString(HexIotaTypes.KEY_TYPE, typeId.toString());
out.put(HexIotaTypes.KEY_DATA, dataTag);
return out;
}
public static boolean isTooLargeToSerialize(Iterable<Iota> examinee) {
// We don't recurse here, just a work queue (or work stack, if we liked.)
// Each element is a found sub-iota, and how deep it is.
//
// TODO: is it worth trying to cache the depth and size statically on a SpellList.
var listsToExamine = new ArrayDeque<>(Collections.singleton(new Pair<>(examinee, 0)));
int totalEltsFound = 1; // count the first list
while (!listsToExamine.isEmpty()) {
var iotaPair = listsToExamine.removeFirst();
var sublist = iotaPair.getFirst();
int depth = iotaPair.getSecond();
for (var iota : sublist) {
totalEltsFound++;
if (totalEltsFound >= HexIotaTypes.MAX_SERIALIZATION_TOTAL) {
return true; // too bad
}
if (iota instanceof ListIota subsublist) {
if (depth + 1 >= HexIotaTypes.MAX_SERIALIZATION_DEPTH) {
return true;
}
listsToExamine.addLast(new Pair<>(subsublist.getList(), depth + 1));
}
}
}
// we made it!
return false;
}
/**
* This method attempts to find the type from the {@code type} key.
* See {@link HexIotaTypes#serialize(Iota)} for the storage format.
*
* @return {@code null} if it cannot get the type.
*/
@org.jetbrains.annotations.Nullable
public static IotaType<?> getTypeFromTag(CompoundTag tag) {
if (!tag.contains(HexIotaTypes.KEY_TYPE, Tag.TAG_STRING)) {
return null;
}
var typeKey = tag.getString(HexIotaTypes.KEY_TYPE);
if (!ResourceLocation.isValidResourceLocation(typeKey)) {
return null;
}
var typeLoc = new ResourceLocation(typeKey);
return HexIotaTypes.REGISTRY.get(typeLoc);
}
/**
* Attempt to deserialize an iota from a tag.
* <br>
* Iotas are saved as such:
* <code>
* {
* "type": "hexcasting:atype",
* "data": {...}
* }
* </code>
*/
public static Iota deserialize(CompoundTag tag, ServerLevel world) {
var type = getTypeFromTag(tag);
if (type == null) {
return new GarbageIota();
}
var data = tag.get(HexIotaTypes.KEY_DATA);
if (data == null) {
return new GarbageIota();
}
Iota deserialized;
try {
deserialized = Objects.requireNonNullElse(type.deserialize(data, world), new NullIota());
} catch (IllegalArgumentException exn) {
HexAPI.LOGGER.warn("Caught an exception deserializing an iota", exn);
deserialized = new GarbageIota();
}
return deserialized;
}
private static Component brokenIota() {
return Component.translatable("hexcasting.spelldata.unknown")
.withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC);
}
public static Component getDisplay(CompoundTag tag) {
var type = getTypeFromTag(tag);
if (type == null) {
return brokenIota();
}
var data = tag.get(HexIotaTypes.KEY_DATA);
if (data == null) {
return brokenIota();
}
return type.display(data);
}
public static FormattedCharSequence getDisplayWithMaxWidth(CompoundTag tag, int maxWidth, Font font) {
var type = getTypeFromTag(tag);
if (type == null) {
return brokenIota().getVisualOrderText();
}
var data = tag.get(HexIotaTypes.KEY_DATA);
if (data == null) {
return brokenIota().getVisualOrderText();
}
var display = type.display(data);
var splitted = font.split(display, maxWidth - font.width("..."));
if (splitted.isEmpty())
return FormattedCharSequence.EMPTY;
else if (splitted.size() == 1)
return splitted.get(0);
else {
var first = splitted.get(0);
return FormattedCharSequence.fromPair(first,
Component.literal("...").withStyle(ChatFormatting.GRAY).getVisualOrderText());
}
}
public static int getColor(CompoundTag tag) {
var type = getTypeFromTag(tag);
if (type == null) {
return HexUtils.ERROR_COLOR;
}
return type.color();
}
}

View file

@ -68,7 +68,7 @@ public class ListIota extends Iota {
public @NotNull Tag serialize() {
var out = new ListTag();
for (var subdatum : this.getList()) {
out.add(HexIotaTypes.serialize(subdatum));
out.add(IotaType.serialize(subdatum));
}
return out;
}
@ -82,7 +82,7 @@ public class ListIota extends Iota {
for (var sub : listTag) {
var csub = HexUtils.downcast(sub, CompoundTag.TYPE);
var subiota = HexIotaTypes.deserialize(csub, world);
var subiota = IotaType.deserialize(csub, world);
if (subiota == null) {
return null;
}
@ -100,7 +100,7 @@ public class ListIota extends Iota {
Tag sub = list.get(i);
var csub = HexUtils.downcast(sub, CompoundTag.TYPE);
out.append(HexIotaTypes.getDisplay(csub));
out.append(IotaType.getDisplay(csub));
if (i < list.size() - 1) {
out.append(", ");

View file

@ -6,19 +6,15 @@ import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.math.HexPattern
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.mod.HexTags
import at.petrak.hexcasting.api.utils.asTranslatedComponent
import at.petrak.hexcasting.api.utils.lightPurple
import at.petrak.hexcasting.common.lib.HexItems
import at.petrak.hexcasting.ktxt.*
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.Util
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.world.InteractionHand
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
@ -28,7 +24,10 @@ abstract class Mishap : Throwable() {
abstract fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenColorizer
open fun particleSpray(ctx: CastingEnvironment): ParticleSpray {
return ParticleSpray(ctx.position.add(0.0, 0.2, 0.0), Vec3(0.0, 2.0, 0.0), 0.2, Math.PI / 4, 40)
return ParticleSpray(
ctx.mishapSprayPos().add(0.0, 0.2, 0.0),
Vec3(0.0, 2.0, 0.0),
0.2, Math.PI / 4, 40)
}
open fun resolutionType(ctx: CastingEnvironment): ResolvedPatternType = ResolvedPatternType.ERRORED
@ -40,7 +39,7 @@ abstract class Mishap : Throwable() {
*/
abstract fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>)
abstract protected fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component
protected abstract fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component
/**
* Every error message should be prefixed with the name of the action...
@ -67,46 +66,6 @@ abstract class Mishap : Throwable() {
protected fun actionName(name: Component?): Component =
name ?: "hexcasting.spell.null".asTranslatedComponent.lightPurple
protected fun yeetHeldItemsTowards(ctx: CastingEnvironment, targetPos: Vec3) {
// Knock the player's items out of their hands
val items = mutableListOf<ItemStack>()
for (hand in InteractionHand.values()) {
if (hand != ctx.castingHand || ctx.caster.getItemInHand(hand).`is`(HexTags.Items.STAVES)) {
items.add(ctx.caster.getItemInHand(hand).copy())
ctx.caster.setItemInHand(hand, ItemStack.EMPTY)
}
}
val delta = targetPos.subtract(ctx.position).normalize().scale(0.5)
for (item in items) {
yeetItem(item, ctx, delta)
}
}
protected fun yeetHeldItem(ctx: CastingEnvironment, hand: InteractionHand) {
val item = ctx.caster.getItemInHand(hand).copy()
if (hand == ctx.castingHand && IXplatAbstractions.INSTANCE.findHexHolder(item) != null)
return
ctx.caster.setItemInHand(hand, ItemStack.EMPTY)
val delta = ctx.caster.lookAngle.scale(0.5)
yeetItem(item, ctx, delta)
}
protected fun yeetItem(stack: ItemStack, ctx: CastingEnvironment, delta: Vec3) {
val entity = ItemEntity(
ctx.world,
ctx.position.x, ctx.position.y, ctx.position.z,
stack,
delta.x + (Math.random() - 0.5) * 0.1,
delta.y + (Math.random() - 0.5) * 0.1,
delta.z + (Math.random() - 0.5) * 0.1
)
entity.setPickUpDelay(40)
ctx.world.addWithUUID(entity)
}
protected fun blockAtPos(ctx: CastingEnvironment, pos: BlockPos): Component {
return ctx.world.getBlockState(pos).block.name
}
@ -114,6 +73,7 @@ abstract class Mishap : Throwable() {
data class Context(val pattern: HexPattern, val name: Component?)
companion object {
@JvmStatic
fun trulyHurt(entity: LivingEntity, source: DamageSource, amount: Float) {
entity.setHurtWithStamp(source, entity.level.gameTime)

View file

@ -15,7 +15,7 @@ class MishapBadEntity(val entity: Entity, val wanted: Component) : Mishap() {
dyeColor(DyeColor.BROWN)
override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>) {
yeetHeldItemsTowards(ctx, entity.position())
ctx.mishapEnvironment.yeetHeldItemsTowards(entity.position())
}
override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) =

View file

@ -14,7 +14,7 @@ class MishapBadOffhandItem(val item: ItemStack, val hand: InteractionHand, val w
dyeColor(DyeColor.BROWN)
override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>) {
yeetHeldItem(ctx, hand)
ctx.mishapEnvironment.dropHeldItems()
}
override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = if (item.isEmpty)

View file

@ -1,12 +1,11 @@
package at.petrak.hexcasting.api.casting.mishaps
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.misc.HexDamageSources
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.iota.DoubleIota
import at.petrak.hexcasting.api.casting.iota.GarbageIota
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.iota.Vec3Iota
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.utils.asTranslatedComponent
import net.minecraft.network.chat.Component
import net.minecraft.world.item.DyeColor
@ -19,7 +18,7 @@ class MishapDivideByZero(val operand1: Component, val operand2: Component, val s
override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>) {
stack.add(GarbageIota())
trulyHurt(ctx.caster, HexDamageSources.OVERCAST, ctx.caster.health / 2)
ctx.mishapEnvironment.damage(0.5f)
}
override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) =

View file

@ -12,8 +12,7 @@ class MishapEntityTooFarAway(val entity: Entity) : Mishap() {
dyeColor(DyeColor.PINK)
override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>) {
// Knock the player's items out of their hands
yeetHeldItemsTowards(ctx, entity.position())
ctx.mishapEnvironment.yeetHeldItemsTowards(entity.position())
}
override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component =

View file

@ -1,16 +1,16 @@
package at.petrak.hexcasting.api.casting.mishaps
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.misc.FrozenColorizer
import net.minecraft.world.item.DyeColor
class MishapEvalTooDeep : Mishap() {
class MishapEvalTooMuch : Mishap() {
override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenColorizer =
dyeColor(DyeColor.BLUE)
override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>) {
ctx.caster.airSupply -= 290
ctx.mishapEnvironment.drown()
}
override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) =

View file

@ -12,7 +12,7 @@ class MishapImmuneEntity(val entity: Entity) : Mishap() {
dyeColor(DyeColor.BLUE)
override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>) {
yeetHeldItemsTowards(ctx, entity.position())
ctx.mishapEnvironment.yeetHeldItemsTowards(entity.position())
}
override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) =

View file

@ -5,7 +5,7 @@ import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.misc.FrozenColorizer
import net.minecraft.world.item.DyeColor
class MishapError(val exception: Exception) : Mishap() {
class MishapInternalException(val exception: Exception) : Mishap() {
override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenColorizer =
dyeColor(DyeColor.BLACK)

View file

@ -13,7 +13,7 @@ class MishapLocationTooFarAway(val location: Vec3, val type: String = "too_far")
dyeColor(DyeColor.MAGENTA)
override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>) {
yeetHeldItemsTowards(ctx, location)
ctx.mishapEnvironment.yeetHeldItemsTowards(this.location)
}
override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component =

View file

@ -1,8 +1,8 @@
package at.petrak.hexcasting.api.casting.mishaps
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.misc.FrozenColorizer
import net.minecraft.core.BlockPos
import net.minecraft.world.item.DyeColor
@ -11,7 +11,7 @@ class MishapNoAkashicRecord(val pos: BlockPos) : Mishap() {
dyeColor(DyeColor.PURPLE)
override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>) {
ctx.caster.giveExperiencePoints(-100)
ctx.mishapEnvironment.removeXp(100)
}
override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) =

View file

@ -23,10 +23,14 @@ class MishapNoSpellCircle : Mishap() {
}
override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>) {
dropAll(ctx.caster, ctx.caster.inventory.items)
dropAll(ctx.caster, ctx.caster.inventory.offhand)
dropAll(ctx.caster, ctx.caster.inventory.armor) {
!EnchantmentHelper.hasBindingCurse(it)
val caster = ctx.caster
if (caster != null) {
// FIXME: handle null caster case
dropAll(caster, caster.inventory.items)
dropAll(caster, caster.inventory.offhand)
dropAll(caster, caster.inventory.armor) {
!EnchantmentHelper.hasBindingCurse(it)
}
}
}

View file

@ -1,12 +1,10 @@
package at.petrak.hexcasting.api.casting.mishaps
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.iota.EntityIota
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.iota.ListIota
import net.minecraft.world.effect.MobEffectInstance
import net.minecraft.world.effect.MobEffects
import at.petrak.hexcasting.api.misc.FrozenColorizer
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.DyeColor
@ -19,7 +17,7 @@ class MishapOthersName(val confidant: Player) : Mishap() {
override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>) {
val seconds = if (this.confidant == ctx.caster) 5 else 60;
ctx.caster.addEffect(MobEffectInstance(MobEffects.BLINDNESS, seconds * 20))
ctx.mishapEnvironment.blind(seconds * 20)
}
override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) =

View file

@ -11,7 +11,11 @@ class MishapShameOnYou() : Mishap() {
dyeColor(DyeColor.BLACK)
override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>) {
Mishap.trulyHurt(ctx.caster, HexDamageSources.SHAME, 69420f)
val caster = ctx.caster
if (caster != null) {
// FIXME: handle null caster case
Mishap.trulyHurt(caster, HexDamageSources.SHAME, 69420f)
}
}
override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = error("shame")

View file

@ -1,6 +1,7 @@
package at.petrak.hexcasting.api.item;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.client.ClientTickCounter;
@ -45,7 +46,7 @@ public interface IotaHolderItem {
var tag = dh.readIotaTag(stack);
if (tag != null) {
return HexIotaTypes.deserialize(tag, world);
return IotaType.deserialize(tag, world);
} else {
return null;
}
@ -83,7 +84,7 @@ public interface IotaHolderItem {
return HexUtils.ERROR_COLOR;
}
return HexIotaTypes.getColor(tag);
return IotaType.getColor(tag);
}
/**
@ -100,7 +101,7 @@ public interface IotaHolderItem {
TooltipFlag flag) {
var datumTag = self.readIotaTag(stack);
if (datumTag != null) {
var cmp = HexIotaTypes.getDisplay(datumTag);
var cmp = IotaType.getDisplay(datumTag);
components.add(Component.translatable("hexcasting.spelldata.onitem", cmp));
if (flag.isAdvanced()) {

View file

@ -2,7 +2,7 @@ package at.petrak.hexcasting.api.misc;
import at.petrak.hexcasting.api.addldata.ADMediaHolder;
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment;
import at.petrak.hexcasting.api.casting.eval.CastingHarness;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import com.google.common.collect.Lists;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
@ -15,10 +15,11 @@ import java.util.function.Predicate;
public class DiscoveryHandlers {
private static final List<Predicate<Player>> HAS_LENS_PREDICATE = new ArrayList<>();
private static final List<Function<CastingHarness, List<ADMediaHolder>>> MEDIA_HOLDER_DISCOVERY = new ArrayList<>();
private static final List<Function<CastingVM, List<ADMediaHolder>>> MEDIA_HOLDER_DISCOVERY = new ArrayList<>();
private static final List<FunctionToFloat<Player>> GRID_SCALE_MODIFIERS = new ArrayList<>();
private static final List<Function<CastingEnvironment, List<ItemStack>>> ITEM_SLOT_DISCOVERER = new ArrayList<>();
private static final List<Function<CastingEnvironment, List<ItemStack>>> OPERATIVE_SLOT_DISCOVERER = new ArrayList<>();
private static final List<Function<CastingEnvironment, List<ItemStack>>> OPERATIVE_SLOT_DISCOVERER =
new ArrayList<>();
private static final List<BiFunction<Player, String, ItemStack>> DEBUG_DISCOVERER = new ArrayList<>();
public static boolean hasLens(Player player) {
@ -30,7 +31,7 @@ public class DiscoveryHandlers {
return false;
}
public static List<ADMediaHolder> collectMediaHolders(CastingHarness harness) {
public static List<ADMediaHolder> collectMediaHolders(CastingVM harness) {
List<ADMediaHolder> holders = Lists.newArrayList();
for (var discoverer : MEDIA_HOLDER_DISCOVERY) {
holders.addAll(discoverer.apply(harness));
@ -76,7 +77,7 @@ public class DiscoveryHandlers {
HAS_LENS_PREDICATE.add(predicate);
}
public static void addMediaHolderDiscoverer(Function<CastingHarness, List<ADMediaHolder>> discoverer) {
public static void addMediaHolderDiscoverer(Function<CastingVM, List<ADMediaHolder>> discoverer) {
MEDIA_HOLDER_DISCOVERY.add(discoverer);
}

View file

@ -56,7 +56,7 @@ public class HexConfig {
public interface ServerConfigAccess {
int opBreakHarvestLevelBecauseForgeThoughtItWasAGoodIdeaToImplementHarvestTiersUsingAnHonestToGodTopoSort();
int maxRecurseDepth();
int maxOpCount();
int maxSpellCircleLength();
@ -69,7 +69,7 @@ public class HexConfig {
// fun fact, although dimension keys are a RegistryHolder, they aren't a registry, so i can't do tags
boolean canTeleportInThisDimension(ResourceKey<Level> dimension);
int DEFAULT_MAX_RECURSE_DEPTH = 512;
int DEFAULT_MAX_OP_COUNT = 1_000_000;
int DEFAULT_MAX_SPELL_CIRCLE_LENGTH = 1024;
int DEFAULT_OP_BREAK_HARVEST_LEVEL = 3;

View file

@ -4,11 +4,8 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
public record Sentinel(boolean hasSentinel, boolean extendsRange, Vec3 position,
ResourceKey<Level> dimension) {
public static Sentinel none() {
return new Sentinel(false, false, Vec3.ZERO, Level.OVERWORLD);
}
/**
* A null sentinel means no sentinel
*/
public record Sentinel(boolean extendsRange, Vec3 position, ResourceKey<Level> dimension) {
}

View file

@ -3,9 +3,9 @@
package at.petrak.hexcasting.api.utils
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.iota.IotaType
import at.petrak.hexcasting.api.casting.iota.ListIota
import at.petrak.hexcasting.api.casting.math.HexCoord
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes
import net.minecraft.ChatFormatting
import net.minecraft.core.Registry
import net.minecraft.nbt.*
@ -251,7 +251,7 @@ inline operator fun <T> WeakValue<T>.setValue(thisRef: Any?, property: KProperty
* Returns an empty list if it's too complicated.
*/
fun Iterable<Iota>.serializeToNBT() =
if (HexIotaTypes.isTooLargeToSerialize(this))
if (IotaType.isTooLargeToSerialize(this))
ListTag()
else
ListIota(this.toList()).serialize()

View file

@ -2,8 +2,10 @@
package at.petrak.hexcasting.api.utils
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.addldata.ADMediaHolder
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.Mth
import net.minecraft.world.item.ItemStack
import kotlin.math.roundToInt
@ -55,6 +57,25 @@ fun extractMedia(
return holder.withdrawMedia(cost, simulate)
}
/**
* Convenience function to scan the player's inventory, curios, etc for media sources,
* and then sorts them
*/
fun scanPlayerForMediaStuff(player: ServerPlayer): List<ADMediaHolder> {
val sources = mutableListOf<ADMediaHolder>()
(player.inventory.items + player.inventory.armor + player.inventory.offhand).forEach {
val holder = HexAPI.instance().findMediaHolder(it)
if (holder != null) {
sources.add(holder)
}
}
sources.sortWith(::compareMediaItem)
sources.reverse()
return sources
}
/**
* Sorted from least important to most important
*/

View file

@ -1,5 +1,6 @@
package at.petrak.hexcasting.client;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.item.IotaHolderItem;
import at.petrak.hexcasting.api.item.MediaHolderItem;
import at.petrak.hexcasting.api.misc.MediaConstants;
@ -21,7 +22,6 @@ import at.petrak.hexcasting.common.items.storage.*;
import at.petrak.hexcasting.common.lib.HexBlockEntities;
import at.petrak.hexcasting.common.lib.HexBlocks;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import at.petrak.hexcasting.xplat.IClientXplatAbstractions;
import net.minecraft.client.color.block.BlockColor;
import net.minecraft.client.color.item.ItemColor;
@ -158,7 +158,7 @@ public class RegisterClientStuff {
if (iotaTag == null) {
return 0xff_ffffff;
}
return HexIotaTypes.getColor(iotaTag);
return IotaType.getColor(iotaTag);
}, HexBlocks.AKASHIC_BOOKSHELF);
}

View file

@ -1,8 +1,9 @@
package at.petrak.hexcasting.client.gui
import at.petrak.hexcasting.api.casting.eval.ControllerInfo
import at.petrak.hexcasting.api.casting.eval.ExecutionClientView
import at.petrak.hexcasting.api.casting.eval.ResolvedPattern
import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType
import at.petrak.hexcasting.api.casting.iota.IotaType
import at.petrak.hexcasting.api.casting.math.HexAngle
import at.petrak.hexcasting.api.casting.math.HexCoord
import at.petrak.hexcasting.api.casting.math.HexDir
@ -17,7 +18,6 @@ import at.petrak.hexcasting.client.ktxt.accumulatedScroll
import at.petrak.hexcasting.client.render.*
import at.petrak.hexcasting.client.sound.GridSoundInstance
import at.petrak.hexcasting.common.lib.HexSounds
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes
import at.petrak.hexcasting.common.network.MsgNewSpellPatternSyn
import at.petrak.hexcasting.xplat.IClientXplatAbstractions
import com.mojang.blaze3d.systems.RenderSystem
@ -35,11 +35,11 @@ import net.minecraft.world.InteractionHand
import net.minecraft.world.phys.Vec2
import kotlin.math.*
// TODO winfy: fix this class to use ExecutionClientView
class GuiSpellcasting constructor(
private val handOpenedWith: InteractionHand,
private var patterns: MutableList<ResolvedPattern>,
private var cachedStack: List<CompoundTag>,
private var cachedParens: List<CompoundTag>,
private var cachedRavenmind: CompoundTag?,
private var parenCount: Int,
) : Screen("gui.hexcasting.spellcasting".asTranslatedComponent) {
@ -61,15 +61,13 @@ class GuiSpellcasting constructor(
this.calculateIotaDisplays()
}
fun recvServerUpdate(info: ControllerInfo, index: Int) {
fun recvServerUpdate(info: ExecutionClientView, index: Int) {
this.patterns.getOrNull(index)?.let {
it.type = info.resolutionType
}
this.cachedStack = info.stack
this.cachedParens = info.parenthesized
this.cachedStack = info.stackDescs
this.cachedRavenmind = info.ravenmind
this.parenCount = info.parenCount
this.calculateIotaDisplays()
}
@ -77,7 +75,7 @@ class GuiSpellcasting constructor(
val mc = Minecraft.getInstance()
val width = (this.width * LHS_IOTAS_ALLOCATION).toInt()
this.stackDescs =
this.cachedStack.map { HexIotaTypes.getDisplayWithMaxWidth(it, width, mc.font) }
this.cachedStack.map { IotaType.getDisplayWithMaxWidth(it, width, mc.font) }
.asReversed()
// this.parenDescs = if (this.cachedParens.isNotEmpty())
// this.cachedParens.flatMap { HexIotaTypes.getDisplayWithMaxWidth(it, width, mc.font) }
@ -88,7 +86,7 @@ class GuiSpellcasting constructor(
this.parenDescs = emptyList()
this.ravenmind =
this.cachedRavenmind?.let {
HexIotaTypes.getDisplayWithMaxWidth(
IotaType.getDisplayWithMaxWidth(
it,
(this.width * RHS_IOTAS_ALLOCATION).toInt(),
mc.font

View file

@ -1,8 +1,8 @@
package at.petrak.hexcasting.common.blocks.akashic;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
@ -57,7 +57,7 @@ public class BlockAkashicRecord extends Block {
var tile = (BlockEntityAkashicBookshelf) slevel.getBlockEntity(foundPos);
var tag = tile.getIotaTag();
return tag == null ? null : HexIotaTypes.deserialize(tag, slevel);
return tag == null ? null : IotaType.deserialize(tag, slevel);
}
// TODO get comparators working again and also cache the number of iotas somehow?

View file

@ -2,9 +2,9 @@ package at.petrak.hexcasting.common.blocks.akashic;
import at.petrak.hexcasting.api.block.HexBlockEntity;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.common.lib.HexBlockEntities;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.state.BlockState;
@ -38,7 +38,7 @@ public class BlockEntityAkashicBookshelf extends HexBlockEntity {
public void setNewMapping(HexPattern pattern, Iota iota) {
var previouslyEmpty = this.pattern == null;
this.pattern = pattern;
this.iotaTag = HexIotaTypes.serialize(iota);
this.iotaTag = IotaType.serialize(iota);
if (previouslyEmpty) {
var oldBs = this.getBlockState();

View file

@ -0,0 +1,65 @@
package at.petrak.hexcasting.common.casting.env;
import at.petrak.hexcasting.api.casting.eval.CastResult;
import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound;
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
public class PackagedItemCastEnv extends PlayerBasedCastEnv {
protected EvalSound sound = HexEvalSounds.NOTHING;
public PackagedItemCastEnv(ServerPlayer caster, InteractionHand castingHand) {
super(caster, castingHand);
}
public EvalSound getFinalSound() {
return sound;
}
@Override
public void postExecution(CastResult result) {
this.sound = this.sound.greaterOf(result.getSound());
for (var sideEffect : result.getSideEffects()) {
if (sideEffect instanceof OperatorSideEffect.DoMishap doMishap) {
this.sendMishapMsgToPlayer(doMishap);
}
}
}
@Override
public long extractMedia(long costLeft) {
var casterStack = this.caster.getItemInHand(this.castingHand);
var casterHexHolder = IXplatAbstractions.INSTANCE.findHexHolder(casterStack);
var canCastFromInv = casterHexHolder.canDrawMediaFromInventory();
var casterMediaHolder = IXplatAbstractions.INSTANCE.findMediaHolder(casterStack);
// The contracts on the AD and on this function are different.
// ADs return the amount extracted, this wants the amount left
if (casterMediaHolder != null) {
long extracted = casterMediaHolder.withdrawMedia((int) costLeft, false);
costLeft -= extracted;
}
if (canCastFromInv && costLeft > 0) {
costLeft = this.extractMediaFromInventory(costLeft, this.canOvercast());
}
return costLeft;
}
@Override
public InteractionHand castingHand() {
return this.castingHand;
}
@Override
public FrozenColorizer getColorizer() {
return null;
}
}

View file

@ -0,0 +1,187 @@
package at.petrak.hexcasting.common.casting.env;
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.addldata.ADMediaHolder;
import at.petrak.hexcasting.api.advancements.HexAdvancementTriggers;
import at.petrak.hexcasting.api.casting.ParticleSpray;
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment;
import at.petrak.hexcasting.api.casting.eval.MishapEnvironment;
import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound;
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect;
import at.petrak.hexcasting.api.casting.mishaps.Mishap;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.api.misc.HexDamageSources;
import at.petrak.hexcasting.api.mod.HexConfig;
import at.petrak.hexcasting.api.mod.HexStatistics;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.api.utils.MediaHelper;
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static at.petrak.hexcasting.api.HexAPI.modLoc;
public abstract class PlayerBasedCastEnv extends CastingEnvironment {
public static final double AMBIT_RADIUS = 32.0;
public static final double SENTINEL_RADIUS = 16.0;
protected final ServerPlayer caster;
protected final InteractionHand castingHand;
protected PlayerBasedCastEnv(ServerPlayer caster, InteractionHand castingHand) {
super(caster.getLevel());
this.caster = caster;
this.castingHand = castingHand;
}
@Override
public @Nullable ServerPlayer getCaster() {
return this.caster;
}
@Override
public EvalSound getSoundType() {
return HexEvalSounds.ADD_PATTERN;
}
@Override
protected List<ItemStack> getUsableStacks(StackDiscoveryMode mode) {
return switch (mode) {
case QUERY -> {
var out = new ArrayList<ItemStack>();
var offhand = this.caster.getItemInHand(HexUtils.otherHand(this.castingHand));
if (!offhand.isEmpty()) {
out.add(offhand);
}
// If we're casting from the main hand, try to pick from the slot one to the right of the selected slot
// Otherwise, scan the hotbar left to right
var anchorSlot = this.castingHand == InteractionHand.MAIN_HAND
? (this.caster.getInventory().selected + 1) % 9
: 0;
for (int delta = 0; delta < 9; delta++) {
var slot = (anchorSlot + delta) % 9;
out.add(this.caster.getInventory().getItem(slot));
}
yield out;
}
case EXTRACTION -> {
// 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
var out = new ArrayList<ItemStack>();
// First, the inventory backwards
Inventory inv = this.caster.getInventory();
for (int i = inv.getContainerSize(); i >= 0; i--) {
if (i != inv.selected) {
out.add(inv.getItem(i));
}
}
// then the offhand, then the selected hand
out.addAll(inv.offhand);
out.add(inv.getSelected());
yield out;
}
};
}
@Override
public boolean isVecInRange(Vec3 vec) {
var sentinel = HexAPI.instance().getSentinel(this.caster);
if (sentinel != null
&& sentinel.extendsRange()
&& this.caster.getLevel().dimension() == sentinel.dimension()
&& vec.distanceToSqr(sentinel.position()) <= SENTINEL_RADIUS * SENTINEL_RADIUS
) {
return true;
}
return vec.distanceToSqr(this.caster.position()) <= AMBIT_RADIUS * AMBIT_RADIUS;
}
@Override
public ItemStack getAlternateItem() {
var otherHand = HexUtils.otherHand(this.castingHand);
var stack = this.caster.getItemInHand(otherHand);
if (stack.isEmpty()) {
return ItemStack.EMPTY.copy();
} else {
return stack;
}
}
/**
* Search the player's inventory for media ADs and use them.
*/
protected long extractMediaFromInventory(long costLeft, boolean allowOvercast) {
List<ADMediaHolder> sources = MediaHelper.scanPlayerForMediaStuff(this.caster);
for (var source : sources) {
var found = MediaHelper.extractMedia(source, (int) costLeft, true, false);
costLeft -= found;
if (costLeft <= 0) {
break;
}
}
if (costLeft > 0 && allowOvercast) {
double mediaToHealth = HexConfig.common().mediaToHealthRate();
double healthToRemove = Math.max(costLeft / mediaToHealth, 0.5);
var mediaAbleToCastFromHP = this.caster.getHealth() * mediaToHealth;
Mishap.trulyHurt(this.caster, HexDamageSources.OVERCAST, (float) healthToRemove);
var actuallyTaken = Mth.ceil(mediaAbleToCastFromHP - (this.caster.getHealth() * mediaToHealth));
HexAdvancementTriggers.OVERCAST_TRIGGER.trigger(this.caster, actuallyTaken);
this.caster.awardStat(HexStatistics.MEDIA_OVERCAST, actuallyTaken);
costLeft -= actuallyTaken;
}
return costLeft;
}
protected boolean canOvercast() {
var adv = this.world.getServer().getAdvancements().getAdvancement(modLoc("y_u_no_cast_angy"));
var advs = this.caster.getAdvancements();
return advs.getOrStartProgress(adv).isDone();
}
@Override
public void produceParticles(ParticleSpray particles, FrozenColorizer colorizer) {
particles.sprayParticles(this.world, colorizer);
}
@Override
public Vec3 mishapSprayPos() {
return this.caster.position();
}
@Override
public MishapEnvironment getMishapEnvironment() {
return new PlayerBasedMishapEnv(this.caster);
}
protected void sendMishapMsgToPlayer(OperatorSideEffect.DoMishap mishap) {
var msg = mishap.getMishap().errorMessageWithName(this, mishap.getErrorCtx());
this.caster.sendSystemMessage(msg);
}
}

View file

@ -0,0 +1,59 @@
package at.petrak.hexcasting.common.casting.env;
import at.petrak.hexcasting.api.casting.eval.MishapEnvironment;
import at.petrak.hexcasting.api.casting.mishaps.Mishap;
import at.petrak.hexcasting.api.misc.HexDamageSources;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
public class PlayerBasedMishapEnv extends MishapEnvironment {
public PlayerBasedMishapEnv(ServerPlayer player) {
super(player.getLevel(), player);
}
@Override
public void yeetHeldItemsTowards(Vec3 targetPos) {
var pos = this.caster.position();
var delta = targetPos.subtract(pos).normalize().scale(0.5);
for (var hand : InteractionHand.values()) {
var stack = this.caster.getItemInHand(hand);
this.caster.setItemInHand(hand, ItemStack.EMPTY);
this.yeetItem(stack, pos, delta);
}
}
@Override
public void dropHeldItems() {
var delta = this.caster.getLookAngle();
this.yeetHeldItemsTowards(this.caster.position().add(delta));
}
@Override
public void damage(float healthProportion) {
Mishap.trulyHurt(this.caster, HexDamageSources.OVERCAST, this.caster.getHealth() * healthProportion);
}
@Override
public void drown() {
if (this.caster.getAirSupply() < 200) {
this.caster.hurt(DamageSource.DROWN, 2f);
}
this.caster.setAirSupply(0);
}
@Override
public void removeXp(int amount) {
this.caster.giveExperiencePoints(-amount);
}
@Override
public void blind(int ticks) {
this.caster.addEffect(new MobEffectInstance(MobEffects.BLINDNESS, ticks));
}
}

View file

@ -0,0 +1,97 @@
package at.petrak.hexcasting.common.casting.env;
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.casting.eval.CastResult;
import at.petrak.hexcasting.api.casting.eval.ExecutionClientView;
import at.petrak.hexcasting.api.casting.eval.ResolvedPattern;
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect;
import at.petrak.hexcasting.api.casting.iota.PatternIota;
import at.petrak.hexcasting.api.casting.math.HexCoord;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.api.mod.HexStatistics;
import at.petrak.hexcasting.common.network.MsgNewSpellPatternAck;
import at.petrak.hexcasting.common.network.MsgNewSpellPatternSyn;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import java.util.HashSet;
import java.util.List;
public class StaffCastEnv extends PlayerBasedCastEnv {
public StaffCastEnv(ServerPlayer caster, InteractionHand castingHand) {
super(caster, castingHand);
}
@Override
public void postExecution(CastResult result) {
for (var sideEffect : result.getSideEffects()) {
if (sideEffect instanceof OperatorSideEffect.DoMishap doMishap) {
this.sendMishapMsgToPlayer(doMishap);
}
}
}
@Override
public long extractMedia(long cost) {
var canOvercast = this.canOvercast();
var remaining = this.extractMediaFromInventory(cost, canOvercast);
if (remaining > 0 && !canOvercast) {
this.caster.sendSystemMessage(Component.translatable("hexcasting.message.cant_overcast"));
}
return remaining;
}
@Override
public FrozenColorizer getColorizer() {
return HexAPI.instance().getColorizer(this.caster);
}
public static void handleNewPatternOnServer(ServerPlayer sender, MsgNewSpellPatternSyn msg) {
boolean cheatedPatternOverlap = false;
List<ResolvedPattern> resolvedPatterns = msg.resolvedPatterns();
if (!resolvedPatterns.isEmpty()) {
var allPoints = new HashSet<HexCoord>();
for (int i = 0; i < resolvedPatterns.size() - 1; i++) {
ResolvedPattern pat = resolvedPatterns.get(i);
allPoints.addAll(pat.getPattern().positions(pat.getOrigin()));
}
var currentResolvedPattern = resolvedPatterns.get(resolvedPatterns.size() - 1);
var currentSpellPoints = currentResolvedPattern.getPattern()
.positions(currentResolvedPattern.getOrigin());
if (currentSpellPoints.stream().anyMatch(allPoints::contains)) {
cheatedPatternOverlap = true;
}
}
if (cheatedPatternOverlap) {
return;
}
sender.awardStat(HexStatistics.PATTERNS_DRAWN);
var vm = IXplatAbstractions.INSTANCE.getStaffcastVM(sender, msg.handUsed());
// every time we send a new pattern it'll be happening in a different tick, so reset here
// i don't think we can do this in the casting vm itself because it doesn't know if `queueAndExecuteIotas`
// is being called from the top level or not
vm.getImage().getUserData().remove(HexAPI.OP_COUNT_USERDATA);
ExecutionClientView clientInfo = vm.queueAndExecuteIota(new PatternIota(msg.pattern()), sender.getLevel());
if (clientInfo.isStackClear()) {
IXplatAbstractions.INSTANCE.setStaffcastImage(sender, null);
IXplatAbstractions.INSTANCE.setPatterns(sender, List.of());
} else {
IXplatAbstractions.INSTANCE.setStaffcastImage(sender, vm);
if (!resolvedPatterns.isEmpty()) {
resolvedPatterns.get(resolvedPatterns.size() - 1).setType(clientInfo.getResolutionType());
}
IXplatAbstractions.INSTANCE.setPatterns(sender, resolvedPatterns);
}
IXplatAbstractions.INSTANCE.sendPacketToPlayer(sender,
new MsgNewSpellPatternAck(clientInfo, resolvedPatterns.size() - 1));
}
}

View file

@ -11,10 +11,11 @@ class OpCircleBounds(val max: Boolean) : ConstMediaAction {
override val argc = 0
override fun execute(args: List<Iota>, ctx: CastingEnvironment): List<Iota> {
if (ctx.spellCircle == null)
val circle = ctx.spellCircle
if (circle == null)
throw MishapNoSpellCircle()
val aabb = ctx.spellCircle.aabb
val aabb = circle.aabb
return if (max)
Vec3(aabb.maxX - 0.5, aabb.maxY - 0.5, aabb.maxZ - 0.5).asActionResult

View file

@ -11,10 +11,11 @@ object OpImpetusDir : ConstMediaAction {
override val argc = 0
override fun execute(args: List<Iota>, ctx: CastingEnvironment): List<Iota> {
if (ctx.spellCircle == null)
val circle = ctx.spellCircle
if (circle == null)
throw MishapNoSpellCircle()
val pos = ctx.spellCircle.impetusPos
val pos = circle.impetusPos
val bs = ctx.world.getBlockState(pos)
val dir = bs.getValue(BlockAbstractImpetus.FACING)
return dir.step().asActionResult

View file

@ -10,9 +10,10 @@ object OpImpetusPos : ConstMediaAction {
override val argc = 0
override fun execute(args: List<Iota>, ctx: CastingEnvironment): List<Iota> {
if (ctx.spellCircle == null)
val circle = ctx.spellCircle
if (circle == null)
throw MishapNoSpellCircle()
return ctx.spellCircle.impetusPos.asActionResult
return circle.impetusPos.asActionResult
}
}

View file

@ -1,9 +1,11 @@
package at.petrak.hexcasting.common.casting.operators.eval
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.SpellList
import at.petrak.hexcasting.api.casting.asActionResult
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage
import at.petrak.hexcasting.api.casting.eval.vm.FrameEvaluate
import at.petrak.hexcasting.api.casting.eval.vm.FrameFinishEval
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
@ -11,19 +13,21 @@ import at.petrak.hexcasting.api.casting.evaluatable
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.iota.PatternIota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import net.minecraft.nbt.CompoundTag
object OpEval : Action {
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
val datum = stack.removeLastOrNull() ?: throw MishapNotEnoughArgs(1, 0)
val instrs = evaluatable(datum, 0)
instrs.ifRight {
ctx.incDepth()
CastingImage.incDepth(userData)
it.asActionResult
}
// if not installed already...
@ -37,6 +41,6 @@ object OpEval : Action {
val instrsList = instrs.map({ SpellList.LList(0, listOf(PatternIota(it))) }, { it })
val frame = FrameEvaluate(instrsList, true)
return OperationResult(newCont.pushFrame(frame), stack, ravenmind, listOf())
return OperationResult(listOf(), userData, listOf(), newCont.pushFrame(frame))
}
}

View file

@ -1,20 +1,21 @@
package at.petrak.hexcasting.common.casting.operators.eval
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.FrameForEach
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.getList
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import net.minecraft.nbt.CompoundTag
object OpForEach : Action {
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
if (stack.size < 2)
throw MishapNotEnoughArgs(2, stack.size)
@ -27,10 +28,10 @@ object OpForEach : Action {
val frame = FrameForEach(datums, instrs, null, mutableListOf())
return OperationResult(
continuation.pushFrame(frame),
stack,
ravenmind,
listOf()
userData,
listOf(),
continuation.pushFrame(frame)
)
}
}

View file

@ -1,17 +1,18 @@
package at.petrak.hexcasting.common.casting.operators.eval
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.iota.Iota
import net.minecraft.nbt.CompoundTag
object OpHalt : Action {
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
var newStack = stack.toList()
var done = false
@ -29,6 +30,6 @@ object OpHalt : Action {
newStack = listOf()
}
return OperationResult(newCont, newStack, ravenmind, listOf())
return OperationResult(newStack, userData, listOf(), newCont)
}
}

View file

@ -1,20 +1,21 @@
package at.petrak.hexcasting.common.casting.operators.lists
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.asActionResult
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.getPositiveIntUnderInclusive
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import net.minecraft.nbt.CompoundTag
object OpLastNToList : Action {
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
if (stack.isEmpty())
throw MishapNotEnoughArgs(1, 0)
@ -27,6 +28,6 @@ object OpLastNToList : Action {
}
stack.addAll(output.asActionResult)
return OperationResult(continuation, stack, ravenmind, listOf())
return OperationResult(stack, userData, listOf(), continuation)
}
}

View file

@ -1,20 +1,28 @@
package at.petrak.hexcasting.common.casting.operators.local
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.orNull
import at.petrak.hexcasting.api.casting.iota.IotaType
import at.petrak.hexcasting.api.casting.iota.NullIota
import net.minecraft.nbt.CompoundTag
object OpPeekLocal : Action {
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
stack.add(ravenmind.orNull())
return OperationResult(continuation, stack, ravenmind, listOf())
val rm = if (userData.contains(HexAPI.RAVENMIND_USERDATA)) {
IotaType.deserialize(userData.getCompound(HexAPI.RAVENMIND_USERDATA), env.world)
} else {
NullIota()
}
stack.add(rm)
return OperationResult(stack, userData, listOf(), continuation)
}
}

View file

@ -1,22 +1,27 @@
package at.petrak.hexcasting.common.casting.operators.local
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import net.minecraft.nbt.CompoundTag
object OpPushLocal : Action {
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
if (stack.isEmpty())
throw MishapNotEnoughArgs(1, 0)
val newLocal = stack.removeLast()
return OperationResult(continuation, stack, newLocal, listOf())
userData.put(HexAPI.RAVENMIND_USERDATA, newLocal.serialize())
return OperationResult(stack, userData, listOf(), continuation)
}
}

View file

@ -4,10 +4,12 @@ import at.petrak.hexcasting.api.casting.ParticleSpray
import at.petrak.hexcasting.api.casting.RenderedSpell
import at.petrak.hexcasting.api.casting.castables.SpellAction
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage
import at.petrak.hexcasting.api.casting.getEntity
import at.petrak.hexcasting.api.casting.getVec3
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.misc.MediaConstants
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.Vec3
@ -18,17 +20,18 @@ object OpAddMotion : SpellAction {
// for bug #387
val MAX_MOTION: Double = 8192.0
override fun execute(
override fun executeWithUserdata(
args: List<Iota>,
ctx: CastingEnvironment
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
ctx: CastingEnvironment,
userData: CompoundTag
): Triple<RenderedSpell, Int, List<ParticleSpray>>? {
val target = args.getEntity(0, argc)
val motion = args.getVec3(1, argc)
ctx.assertEntityInRange(target)
var motionForCost = motion.lengthSqr()
if (ctx.hasBeenGivenMotion(target))
if (CastingImage.checkAndMarkGivenMotion(userData, target))
motionForCost++
ctx.markEntityAsMotionAdded(target)
val shrunkMotion = if (motion.lengthSqr() > MAX_MOTION * MAX_MOTION)
motion.normalize().scale(MAX_MOTION)
@ -48,6 +51,10 @@ object OpAddMotion : SpellAction {
)
}
override fun execute(args: List<Iota>, ctx: CastingEnvironment): Triple<RenderedSpell, Int, List<ParticleSpray>>? {
throw IllegalStateException()
}
private data class Spell(val target: Entity, val motion: Vec3) : RenderedSpell {
override fun cast(ctx: CastingEnvironment) {
target.push(motion.x, motion.y, motion.z)

View file

@ -36,10 +36,11 @@ object OpColorize : SpellAction {
private data class Spell(val stack: ItemStack) : RenderedSpell {
override fun cast(ctx: CastingEnvironment) {
val copy = stack.copy()
if (ctx.withdrawItem(copy, 1, true)) {
val caster = ctx.caster
if (caster != null && ctx.withdrawItem(copy::equals, 1, true)) {
IXplatAbstractions.INSTANCE.setColorizer(
ctx.caster,
FrozenColorizer(copy, ctx.caster.uuid)
FrozenColorizer(copy, caster.uuid)
)
}
}

View file

@ -49,7 +49,7 @@ class OpCreateFluid(val cost: Int, val bucket: Item, val cauldron: BlockState, v
ctx.world.setBlock(pos, cauldron, 3)
else if (!IXplatAbstractions.INSTANCE.tryPlaceFluid(
ctx.world,
ctx.castingHand,
ctx.castingHand(),
pos,
fluid
) && bucket is BucketItem) {

View file

@ -1,30 +1,31 @@
package at.petrak.hexcasting.common.casting.operators.spells
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.RenderedSpell
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import net.minecraft.nbt.CompoundTag
// TODO should this dump the whole stack
object OpPrint : Action {
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
if (stack.isEmpty()) {
throw MishapNotEnoughArgs(1, 0)
}
val datum = stack[stack.lastIndex]
return OperationResult(
continuation, stack, ravenmind, listOf(
stack, userData, listOf(
OperatorSideEffect.AttemptSpell(Spell(datum), hasCastingSound = false, awardStat = false)
)
), continuation
)
}

View file

@ -1,21 +1,22 @@
package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.getPositiveInt
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import it.unimi.dsi.fastutil.ints.IntArrayList
import net.minecraft.nbt.CompoundTag
// "lehmer code"
object OpAlwinfyHasAscendedToABeingOfPureMath : Action {
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
if (stack.isEmpty())
throw MishapNotEnoughArgs(1, 0)
@ -45,10 +46,10 @@ object OpAlwinfyHasAscendedToABeingOfPureMath : Action {
}
return OperationResult(
continuation,
stack,
ravenmind,
listOf()
userData,
listOf(),
continuation
)
}

View file

@ -1,21 +1,22 @@
package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.getPositiveInt
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import net.minecraft.nbt.CompoundTag
// Yes this is weird in that 1=remove, 0=keep, but i think the UX is better
// todo this is untested
object OpBitMask : Action {
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
if (stack.size < 1)
throw MishapNotEnoughArgs(1, 0)
@ -38,6 +39,6 @@ object OpBitMask : Action {
}
}
return OperationResult(continuation, out.asReversed(), ravenmind, listOf())
return OperationResult(out.asReversed(), userData, listOf(), continuation)
}
}

View file

@ -1,22 +1,23 @@
package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.iota.DoubleIota
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidIota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import net.minecraft.nbt.CompoundTag
import kotlin.math.abs
import kotlin.math.roundToInt
object OpFisherman : Action {
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
if (stack.size < 2)
throw MishapNotEnoughArgs(2, stack.size)
@ -38,6 +39,6 @@ object OpFisherman : Action {
val fish = stack.removeAt(stack.size - depth)
stack.add(fish)
return OperationResult(continuation, stack, ravenmind, listOf())
return OperationResult(stack, userData, listOf(), continuation)
}
}

View file

@ -1,19 +1,20 @@
package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.getPositiveIntUnderInclusive
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import net.minecraft.nbt.CompoundTag
object OpFishermanButItCopies : Action {
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
if (stack.size < 2)
throw MishapNotEnoughArgs(2, stack.size)
@ -23,6 +24,6 @@ object OpFishermanButItCopies : Action {
val fish = stack.get(stack.size - 1 - depth)
stack.add(fish)
return OperationResult(continuation, stack, ravenmind, listOf())
return OperationResult(stack, userData, listOf(), continuation)
}
}

View file

@ -1,20 +1,21 @@
package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.OperationResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.iota.DoubleIota
import at.petrak.hexcasting.api.casting.iota.Iota
import net.minecraft.nbt.CompoundTag
object OpStackSize : Action {
override fun operate(
continuation: SpellContinuation,
env: CastingEnvironment,
stack: MutableList<Iota>,
ravenmind: Iota?,
ctx: CastingEnvironment
userData: CompoundTag,
continuation: SpellContinuation
): OperationResult {
stack.add(DoubleIota(stack.size.toDouble()))
return OperationResult(continuation, stack, ravenmind, listOf())
return OperationResult(stack, userData, listOf(), continuation)
}
}

View file

@ -1,10 +1,18 @@
package at.petrak.hexcasting.common.impl;
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.addldata.ADMediaHolder;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.api.player.Sentinel;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -36,6 +44,7 @@ public class HexAPIImpl implements HexAPI {
return entity.getDeltaMovement();
}
<<<<<<< HEAD
//region brainsweeping
@Override
@ -66,4 +75,20 @@ public class HexAPIImpl implements HexAPI {
}
//endregion
=======
@Override
public @Nullable Sentinel getSentinel(ServerPlayer player) {
return IXplatAbstractions.INSTANCE.getSentinel(player);
}
@Override
public @Nullable ADMediaHolder findMediaHolder(ItemStack stack) {
return IXplatAbstractions.INSTANCE.findMediaHolder(stack);
}
@Override
public FrozenColorizer getColorizer(Player player) {
return IXplatAbstractions.INSTANCE.getColorizer(player);
}
>>>>>>> casting-context
}

View file

@ -33,7 +33,7 @@ public class ItemStaff extends Item {
}
if (!world.isClientSide() && player instanceof ServerPlayer serverPlayer) {
var harness = IXplatAbstractions.INSTANCE.getHarness(serverPlayer, hand);
var harness = IXplatAbstractions.INSTANCE.getStaffcastVM(serverPlayer, hand);
var patterns = IXplatAbstractions.INSTANCE.getPatternsSavedInUi(serverPlayer);
var descs = harness.generateDescs();

View file

@ -1,11 +1,11 @@
package at.petrak.hexcasting.common.items.magic;
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment;
import at.petrak.hexcasting.api.casting.eval.CastingHarness;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.item.HexHolderItem;
import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import at.petrak.hexcasting.common.casting.env.PackagedItemCastEnv;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
@ -68,7 +68,7 @@ public abstract class ItemPackagedHex extends ItemMediaHolder implements HexHold
var out = new ArrayList<Iota>();
for (var patTag : patsTag) {
CompoundTag tag = NBTHelper.getAsCompound(patTag);
out.add(HexIotaTypes.deserialize(tag, level));
out.add(IotaType.deserialize(tag, level));
}
return out;
}
@ -77,7 +77,7 @@ public abstract class ItemPackagedHex extends ItemMediaHolder implements HexHold
public void writeHex(ItemStack stack, List<Iota> program, int media) {
ListTag patsTag = new ListTag();
for (Iota pat : program) {
patsTag.add(HexIotaTypes.serialize(pat));
patsTag.add(IotaType.serialize(pat));
}
NBTHelper.putList(stack, TAG_PROGRAM, patsTag);
@ -108,9 +108,9 @@ public abstract class ItemPackagedHex extends ItemMediaHolder implements HexHold
return InteractionResultHolder.fail(stack);
}
var sPlayer = (ServerPlayer) player;
var ctx = new CastingEnvironment(sPlayer, usedHand, CastingEnvironment.CastSource.PACKAGED_HEX);
var harness = new CastingHarness(ctx);
var info = harness.executeIotas(instrs, sPlayer.getLevel());
var ctx = new PackagedItemCastEnv(sPlayer, usedHand);
var harness = CastingVM.empty(ctx);
harness.queueAndExecuteIotas(instrs, sPlayer.getLevel());
boolean broken = breakAfterDepletion() && getMedia(stack) == 0;
@ -133,11 +133,6 @@ public abstract class ItemPackagedHex extends ItemMediaHolder implements HexHold
}
}
@Override
public int getUseDuration(ItemStack pStack) {
return 16;
}
@Override
public UseAnim getUseAnimation(ItemStack pStack) {
return UseAnim.BLOCK;

View file

@ -2,10 +2,10 @@ package at.petrak.hexcasting.common.items.storage;
import at.petrak.hexcasting.api.casting.iota.DoubleIota;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.item.IotaHolderItem;
import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.common.lib.HexSounds;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionHand;
@ -30,7 +30,7 @@ public class ItemAbacus extends Item implements IotaHolderItem {
public @Nullable
CompoundTag readIotaTag(ItemStack stack) {
var datum = new DoubleIota(NBTHelper.getDouble(stack, TAG_VALUE));
return HexIotaTypes.serialize(datum);
return IotaType.serialize(datum);
}
@Override

View file

@ -1,10 +1,10 @@
package at.petrak.hexcasting.common.items.storage;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.casting.iota.NullIota;
import at.petrak.hexcasting.api.item.IotaHolderItem;
import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
@ -57,7 +57,7 @@ public class ItemFocus extends Item implements IotaHolderItem {
stack.removeTagKey(TAG_DATA);
stack.removeTagKey(TAG_SEALED);
} else if (!isSealed(stack)) {
NBTHelper.put(stack, TAG_DATA, HexIotaTypes.serialize(datum));
NBTHelper.put(stack, TAG_DATA, IotaType.serialize(datum));
}
}

View file

@ -1,10 +1,10 @@
package at.petrak.hexcasting.common.items.storage;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.casting.iota.NullIota;
import at.petrak.hexcasting.api.item.IotaHolderItem;
import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import net.minecraft.ChatFormatting;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
@ -148,14 +148,14 @@ public class ItemSpellbook extends Item implements IotaHolderItem {
pages.remove(key);
NBTHelper.remove(NBTHelper.getCompound(stack, TAG_SEALED), key);
} else {
pages.put(key, HexIotaTypes.serialize(datum));
pages.put(key, IotaType.serialize(datum));
}
if (pages.isEmpty()) {
NBTHelper.remove(stack, TAG_PAGES);
}
} else if (datum != null) {
NBTHelper.getOrCreateCompound(stack, TAG_PAGES).put(key, HexIotaTypes.serialize(datum));
NBTHelper.getOrCreateCompound(stack, TAG_PAGES).put(key, IotaType.serialize(datum));
} else {
NBTHelper.remove(NBTHelper.getCompound(stack, TAG_SEALED), key);
}

View file

@ -2,8 +2,6 @@ package at.petrak.hexcasting.common.lib.hex;
import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound;
import at.petrak.hexcasting.common.lib.HexSounds;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import java.util.LinkedHashMap;
@ -13,22 +11,24 @@ import java.util.function.BiConsumer;
import static at.petrak.hexcasting.api.HexAPI.modLoc;
public class HexEvalSounds {
public static final Registry<EvalSound> REGISTRY = IXplatAbstractions.INSTANCE.getEvalSoundRegistry();
private static final Map<ResourceLocation, EvalSound> SOUNDS = new LinkedHashMap<>();
public static final EvalSound NOTHING = make("nothing",
new EvalSound(null, Integer.MIN_VALUE));
public static final EvalSound OPERATOR = make("operator",
public static final EvalSound ADD_PATTERN = make("operator",
new EvalSound(HexSounds.ADD_PATTERN, 0));
public static final EvalSound SPELL = make("spell",
new EvalSound(HexSounds.ACTUALLY_CAST, 1000));
public static final EvalSound MISHAP = make("mishap",
new EvalSound(HexSounds.FAIL_PATTERN, Integer.MAX_VALUE));
public static final EvalSound HERMES = make("hermes",
new EvalSound(HexSounds.CAST_HERMES, Integer.MAX_VALUE));
new EvalSound(HexSounds.CAST_HERMES, 2000));
public static final EvalSound THOTH = make("thoth",
new EvalSound(HexSounds.CAST_THOTH, Integer.MAX_VALUE));
new EvalSound(HexSounds.CAST_THOTH, 2500));
public static final EvalSound MUTE = make("mute",
new EvalSound(null, 3000));
public static final EvalSound MISHAP = make("mishap",
new EvalSound(HexSounds.FAIL_PATTERN, 4000));
private static EvalSound make(String name, EvalSound sound) {
var old = SOUNDS.put(modLoc(name), sound);

View file

@ -2,22 +2,13 @@ package at.petrak.hexcasting.common.lib.hex;
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.casting.iota.*;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import com.mojang.datafixers.util.Pair;
import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.Font;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.FormattedCharSequence;
import org.jetbrains.annotations.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import static at.petrak.hexcasting.api.HexAPI.modLoc;
@ -34,151 +25,6 @@ public class HexIotaTypes {
public static final int MAX_SERIALIZATION_DEPTH = 256;
public static final int MAX_SERIALIZATION_TOTAL = 1024;
public static CompoundTag serialize(Iota iota) {
var type = iota.getType();
var typeId = REGISTRY.getKey(type);
if (typeId == null) {
throw new IllegalStateException(
"Tried to serialize an unregistered iota type. Iota: " + iota
+ " ; Type" + type.getClass().getTypeName());
}
// We check if it's too big on serialization; if it is we just return a garbage.
if (iota instanceof ListIota listIota && isTooLargeToSerialize(listIota.getList())) {
// Garbage will never be too large so we just recurse
return serialize(new GarbageIota());
}
var dataTag = iota.serialize();
var out = new CompoundTag();
out.putString(KEY_TYPE, typeId.toString());
out.put(KEY_DATA, dataTag);
return out;
}
public static boolean isTooLargeToSerialize(Iterable<Iota> examinee) {
// We don't recurse here, just a work queue (or work stack, if we liked.)
// Each element is a found sub-iota, and how deep it is.
//
// TODO: is it worth trying to cache the depth and size statically on a SpellList.
var listsToExamine = new ArrayDeque<>(Collections.singleton(new Pair<>(examinee, 0)));
int totalEltsFound = 1; // count the first list
while (!listsToExamine.isEmpty()) {
var iotaPair = listsToExamine.removeFirst();
var sublist = iotaPair.getFirst();
int depth = iotaPair.getSecond();
for (var iota : sublist) {
totalEltsFound++;
if (totalEltsFound >= MAX_SERIALIZATION_TOTAL) {
return true; // too bad
}
if (iota instanceof ListIota subsublist) {
if (depth + 1 >= MAX_SERIALIZATION_DEPTH) {
return true;
}
listsToExamine.addLast(new Pair<>(subsublist.getList(), depth + 1));
}
}
}
// we made it!
return false;
}
/**
* This method attempts to find the type from the {@code type} key.
* See {@link HexIotaTypes#serialize(Iota)} for the storage format.
*
* @return {@code null} if it cannot get the type.
*/
@Nullable
public static IotaType<?> getTypeFromTag(CompoundTag tag) {
if (!tag.contains(KEY_TYPE, Tag.TAG_STRING)) {
return null;
}
var typeKey = tag.getString(KEY_TYPE);
if (!ResourceLocation.isValidResourceLocation(typeKey)) {
return null;
}
var typeLoc = new ResourceLocation(typeKey);
return REGISTRY.get(typeLoc);
}
/**
* Attempt to deserialize an iota from a tag.
* <br>
* Iotas are saved as such:
* <code>
* {
* "type": "hexcasting:atype",
* "data": {...}
* }
* </code>
*/
public static Iota deserialize(CompoundTag tag, ServerLevel world) {
var type = getTypeFromTag(tag);
if (type == null) {
return new GarbageIota();
}
var data = tag.get(KEY_DATA);
if (data == null) {
return new GarbageIota();
}
Iota deserialized;
try {
deserialized = Objects.requireNonNullElse(type.deserialize(data, world), new NullIota());
} catch (IllegalArgumentException exn) {
HexAPI.LOGGER.warn("Caught an exception deserializing an iota", exn);
deserialized = new GarbageIota();
}
return deserialized;
}
private static Component brokenIota() {
return Component.translatable("hexcasting.spelldata.unknown")
.withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC);
}
public static Component getDisplay(CompoundTag tag) {
var type = getTypeFromTag(tag);
if (type == null) {
return brokenIota();
}
var data = tag.get(KEY_DATA);
if (data == null) {
return brokenIota();
}
return type.display(data);
}
public static FormattedCharSequence getDisplayWithMaxWidth(CompoundTag tag, int maxWidth, Font font) {
var type = getTypeFromTag(tag);
if (type == null) {
return brokenIota().getVisualOrderText();
}
var data = tag.get(KEY_DATA);
if (data == null) {
return brokenIota().getVisualOrderText();
}
var display = type.display(data);
var splitted = font.split(display, maxWidth - font.width("..."));
if (splitted.isEmpty())
return FormattedCharSequence.EMPTY;
else if (splitted.size() == 1)
return splitted.get(0);
else {
var first = splitted.get(0);
return FormattedCharSequence.fromPair(first,
Component.literal("...").withStyle(ChatFormatting.GRAY).getVisualOrderText());
}
}
public static int getColor(CompoundTag tag) {
var type = getTypeFromTag(tag);
if (type == null) {
return HexUtils.ERROR_COLOR;
}
return type.color();
}
public static void registerTypes(BiConsumer<IotaType<?>, ResourceLocation> r) {
for (var e : TYPES.entrySet()) {
r.accept(e.getValue(), e.getKey());

View file

@ -1,6 +1,6 @@
package at.petrak.hexcasting.common.network;
import at.petrak.hexcasting.api.casting.eval.ControllerInfo;
import at.petrak.hexcasting.api.casting.eval.ExecutionClientView;
import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType;
import at.petrak.hexcasting.client.gui.GuiSpellcasting;
import at.petrak.hexcasting.common.lib.HexSounds;
@ -16,7 +16,7 @@ import static at.petrak.hexcasting.api.HexAPI.modLoc;
/**
* Sent server->client when the player finishes casting a spell.
*/
public record MsgNewSpellPatternAck(ControllerInfo info, int index) implements IMessage {
public record MsgNewSpellPatternAck(ExecutionClientView info, int index) implements IMessage {
public static final ResourceLocation ID = modLoc("pat_sc");
@Override
@ -38,7 +38,7 @@ public record MsgNewSpellPatternAck(ControllerInfo info, int index) implements I
var parenCount = buf.readVarInt();
return new MsgNewSpellPatternAck(
new ControllerInfo(isStackEmpty, resolutionType, stack, parens, raven, parenCount), index
new ExecutionClientView(isStackEmpty, resolutionType, stack, parens, raven, parenCount), index
);
}
@ -66,7 +66,7 @@ public record MsgNewSpellPatternAck(ControllerInfo info, int index) implements I
}
var screen = Minecraft.getInstance().screen;
if (screen instanceof GuiSpellcasting spellGui) {
if (self.info().isStackClear()) {
if (self.info().isStackClear() && self.info.getRavenmind() == null) {
mc.setScreen(null);
} else {
spellGui.recvServerUpdate(self.info(), self.index());

View file

@ -1,14 +1,8 @@
package at.petrak.hexcasting.common.network;
import at.petrak.hexcasting.api.mod.HexStatistics;
import at.petrak.hexcasting.api.mod.HexTags;
import at.petrak.hexcasting.api.casting.eval.ControllerInfo;
import at.petrak.hexcasting.api.casting.eval.ResolvedPattern;
import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType;
import at.petrak.hexcasting.api.casting.iota.PatternIota;
import at.petrak.hexcasting.api.casting.math.HexCoord;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import at.petrak.hexcasting.common.casting.env.StaffCastEnv;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
@ -59,54 +53,6 @@ public record MsgNewSpellPatternSyn(InteractionHand handUsed, HexPattern pattern
}
public void handle(MinecraftServer server, ServerPlayer sender) {
server.execute(() -> {
// TODO: should we maybe not put tons of logic in a packet class
var held = sender.getItemInHand(this.handUsed);
if (held.is(HexTags.Items.STAVES)) {
boolean autoFail = false;
if (!resolvedPatterns.isEmpty()) {
var allPoints = new ArrayList<HexCoord>();
for (int i = 0; i < resolvedPatterns.size() - 1; i++) {
ResolvedPattern pat = resolvedPatterns.get(i);
allPoints.addAll(pat.getPattern().positions(pat.getOrigin()));
}
var currentResolvedPattern = resolvedPatterns.get(resolvedPatterns.size() - 1);
var currentSpellPoints = currentResolvedPattern.getPattern()
.positions(currentResolvedPattern.getOrigin());
if (currentSpellPoints.stream().anyMatch(allPoints::contains)) {
autoFail = true;
}
}
sender.awardStat(HexStatistics.PATTERNS_DRAWN);
var harness = IXplatAbstractions.INSTANCE.getHarness(sender, this.handUsed);
ControllerInfo clientInfo;
if (autoFail) {
var descs = harness.generateDescs();
clientInfo = new ControllerInfo(harness.getStack().isEmpty(), ResolvedPatternType.INVALID,
descs.getFirst(), descs.getSecond(), descs.getThird(), harness.getParenCount());
} else {
clientInfo = harness.executeIota(new PatternIota(this.pattern), sender.getLevel());
}
if (clientInfo.isStackClear()) {
IXplatAbstractions.INSTANCE.setHarness(sender, null);
IXplatAbstractions.INSTANCE.setPatterns(sender, List.of());
} else {
IXplatAbstractions.INSTANCE.setHarness(sender, harness);
if (!resolvedPatterns.isEmpty()) {
resolvedPatterns.get(resolvedPatterns.size() - 1).setType(clientInfo.getResolutionType());
}
IXplatAbstractions.INSTANCE.setPatterns(sender, resolvedPatterns);
}
IXplatAbstractions.INSTANCE.sendPacketToPlayer(sender,
new MsgNewSpellPatternAck(clientInfo, resolvedPatterns.size() - 1));
}
});
server.execute(() -> StaffCastEnv.handleNewPatternOnServer(sender, this));
}
}

View file

@ -1,11 +1,11 @@
package at.petrak.hexcasting.common.network;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.common.items.storage.ItemAbacus;
import at.petrak.hexcasting.common.items.storage.ItemSpellbook;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.lib.HexSounds;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import io.netty.buffer.ByteBuf;
import net.minecraft.ChatFormatting;
import net.minecraft.network.FriendlyByteBuf;
@ -144,7 +144,7 @@ public record MsgShiftScrollSyn(double mainHandDelta, double offHandDelta, boole
var datumTag = HexItems.ABACUS.readIotaTag(stack);
if (datumTag != null) {
var popup = HexIotaTypes.getDisplay(datumTag);
var popup = IotaType.getDisplay(datumTag);
sender.displayClientMessage(
Component.translatable("hexcasting.tooltip.abacus", popup).withStyle(ChatFormatting.GREEN), true);
}

View file

@ -6,9 +6,10 @@ import at.petrak.hexcasting.api.addldata.ADIotaHolder;
import at.petrak.hexcasting.api.addldata.ADMediaHolder;
import at.petrak.hexcasting.api.casting.ActionRegistryEntry;
import at.petrak.hexcasting.api.casting.castables.SpecialHandler;
import at.petrak.hexcasting.api.casting.eval.CastingHarness;
import at.petrak.hexcasting.api.casting.eval.ResolvedPattern;
import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.api.player.AltioraAbility;
@ -85,7 +86,7 @@ public interface IXplatAbstractions {
void setAltiora(Player target, @Nullable AltioraAbility altiora);
void setHarness(ServerPlayer target, @Nullable CastingHarness harness);
void setStaffcastImage(ServerPlayer target, @Nullable CastingImage image);
void setPatterns(ServerPlayer target, List<ResolvedPattern> patterns);
@ -97,7 +98,7 @@ public interface IXplatAbstractions {
Sentinel getSentinel(Player player);
CastingHarness getHarness(ServerPlayer player, InteractionHand hand);
CastingVM getStaffcastVM(ServerPlayer player, InteractionHand hand);
List<ResolvedPattern> getPatternsSavedInUi(ServerPlayer player);
@ -106,6 +107,9 @@ public interface IXplatAbstractions {
@Nullable
ADMediaHolder findMediaHolder(ItemStack stack);
@Nullable
ADMediaHolder findMediaHolder(ServerPlayer player);
@Nullable
ADIotaHolder findDataHolder(ItemStack stack);

View file

@ -211,7 +211,7 @@
"text.autoconfig.hexcasting.option.client.gridSnapThreshold.@Tooltip": "When using a staff, the distance from one dot you have to go to snap to the next dot, where 0.5 means 50% of the way (0.5-1)",
"text.autoconfig.hexcasting.option.server.opBreakHarvestLevel": "Break Harvest Level",
"text.autoconfig.hexcasting.option.server.maxRecurseDepth": "Max Recurse Depth",
"text.autoconfig.hexcasting.option.server.maxOpCount": "Max Action Count",
"text.autoconfig.hexcasting.option.server.maxSpellCircleLength": "Max Spell Circle Length",
"text.autoconfig.hexcasting.option.server.actionDenyList": "Action Deny List",
"text.autoconfig.hexcasting.option.server.circleActionDenyList": "Circle Action Deny List",
@ -219,7 +219,7 @@
"text.autoconfig.hexcasting.option.server.scrollInjectionsRaw": "Scroll Injection Weights",
"text.autoconfig.hexcasting.option.server.amethystShardModification": "Amethyst Shard Drop Rate Change",
"text.autoconfig.hexcasting.option.server.opBreakHarvestLevel.@Tooltip": "The harvest level of the Break Block spell.\n0 = wood, 1 = stone, 2 = iron, 3 = diamond, 4 = netherite.",
"text.autoconfig.hexcasting.option.server.maxRecurseDepth.@Tooltip": "How many times an action can recursively cast other actions",
"text.autoconfig.hexcasting.option.server.maxOpCount.@Tooltip": "The maximum number of actions that can be executed in one tick, to avoid hanging the server.",
"text.autoconfig.hexcasting.option.server.maxSpellCircleLength.@Tooltip": "The maximum number of slates in a spell circle",
"text.autoconfig.hexcasting.option.server.actionDenyList.@Tooltip": "Resource locations of disallowed actions. Trying to cast one of these will result in a mishap. For example, hexcasting:get_caster will prevent Mind's Reflection",
"text.autoconfig.hexcasting.option.server.circleActionDenyList.@Tooltip": "Resource locations of disallowed actions within circles. Trying to cast one of these from a circle will result in a mishap.",

View file

@ -159,7 +159,7 @@ public class FabricHexConfig extends PartitioningSerializer.GlobalData {
@ConfigEntry.Gui.Tooltip
private int opBreakHarvestLevel = DEFAULT_OP_BREAK_HARVEST_LEVEL;
@ConfigEntry.Gui.Tooltip
private int maxRecurseDepth = DEFAULT_MAX_RECURSE_DEPTH;
private int maxOpCount = DEFAULT_MAX_OP_COUNT;
@ConfigEntry.Gui.Tooltip
private int maxSpellCircleLength = DEFAULT_MAX_SPELL_CIRCLE_LENGTH;
@ConfigEntry.Gui.Tooltip
@ -189,7 +189,7 @@ public class FabricHexConfig extends PartitioningSerializer.GlobalData {
@Override
public void validatePostLoad() throws ValidationException {
this.maxRecurseDepth = Math.max(this.maxRecurseDepth, 0);
this.maxOpCount = Math.max(this.maxOpCount, 0);
this.maxSpellCircleLength = Math.max(this.maxSpellCircleLength, 4);
this.scrollInjections = new Object2IntOpenHashMap<>();
@ -206,8 +206,8 @@ public class FabricHexConfig extends PartitioningSerializer.GlobalData {
}
@Override
public int maxRecurseDepth() {
return maxRecurseDepth;
public int maxOpCount() {
return maxOpCount;
}
@Override

View file

@ -1,43 +0,0 @@
package at.petrak.hexcasting.fabric.cc;
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment;
import at.petrak.hexcasting.api.casting.eval.CastingHarness;
import dev.onyxstudios.cca.api.v3.component.Component;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import org.jetbrains.annotations.Nullable;
public class CCHarness implements Component {
public static final String TAG_HARNESS = "harness";
private final ServerPlayer owner;
private CompoundTag lazyLoadedTag = new CompoundTag();
public CCHarness(ServerPlayer owner) {
this.owner = owner;
}
public CastingHarness getHarness(InteractionHand hand) {
var ctx = new CastingEnvironment(this.owner, hand, CastingEnvironment.CastSource.STAFF);
if (this.lazyLoadedTag.isEmpty()) {
return new CastingHarness(ctx);
} else {
return CastingHarness.fromNBT(this.lazyLoadedTag, ctx);
}
}
public void setHarness(@Nullable CastingHarness harness) {
this.lazyLoadedTag = harness == null ? new CompoundTag() : harness.serializeToNBT();
}
@Override
public void readFromNbt(CompoundTag tag) {
this.lazyLoadedTag = tag.getCompound(TAG_HARNESS);
}
@Override
public void writeToNbt(CompoundTag tag) {
tag.put(TAG_HARNESS, this.lazyLoadedTag);
}
}

View file

@ -0,0 +1,49 @@
package at.petrak.hexcasting.fabric.cc;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.common.casting.env.StaffCastEnv;
import dev.onyxstudios.cca.api.v3.component.Component;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import org.jetbrains.annotations.Nullable;
public class CCStaffcastImage implements Component {
public static final String TAG_HARNESS = "harness";
private final ServerPlayer owner;
private CompoundTag lazyLoadedTag = new CompoundTag();
public CCStaffcastImage(ServerPlayer owner) {
this.owner = owner;
}
/**
* Turn the saved image into a VM in a player staffcasting environment
*/
public CastingVM getVM(InteractionHand hand) {
var img = this.lazyLoadedTag.isEmpty()
? new CastingImage()
: CastingImage.loadFromNbt(this.lazyLoadedTag, this.owner.getLevel());
var env = new StaffCastEnv(this.owner, hand);
return new CastingVM(img, env);
}
public void setImage(@Nullable CastingImage image) {
this.lazyLoadedTag =
image == null
? new CompoundTag()
: image.serializeToNbt();
}
@Override
public void readFromNbt(CompoundTag tag) {
this.lazyLoadedTag = tag.getCompound(TAG_HARNESS);
}
@Override
public void writeToNbt(CompoundTag tag) {
tag.put(TAG_HARNESS, this.lazyLoadedTag);
}
}

View file

@ -45,8 +45,9 @@ public class HexCardinalComponents implements EntityComponentInitializer, ItemCo
public static final ComponentKey<CCAltiora> ALTIORA = ComponentRegistry.getOrCreate(modLoc("altiora"),
CCAltiora.class);
public static final ComponentKey<CCHarness> HARNESS = ComponentRegistry.getOrCreate(modLoc("harness"),
CCHarness.class);
public static final ComponentKey<CCStaffcastImage> STAFFCAST_IMAGE = ComponentRegistry.getOrCreate(modLoc(
"harness"),
CCStaffcastImage.class);
public static final ComponentKey<CCPatterns> PATTERNS = ComponentRegistry.getOrCreate(modLoc("patterns"),
CCPatterns.class);
@ -67,7 +68,7 @@ public class HexCardinalComponents implements EntityComponentInitializer, ItemCo
registry.registerForPlayers(ALTIORA, CCAltiora::new, RespawnCopyStrategy.LOSSLESS_ONLY);
// Fortunately these are all both only needed on the server and don't want to be copied across death
registry.registerFor(ServerPlayer.class, FLIGHT, CCFlight::new);
registry.registerFor(ServerPlayer.class, HARNESS, CCHarness::new);
registry.registerFor(ServerPlayer.class, STAFFCAST_IMAGE, CCStaffcastImage::new);
registry.registerFor(ServerPlayer.class, PATTERNS, CCPatterns::new);

View file

@ -1,8 +1,8 @@
package at.petrak.hexcasting.fabric.cc.adimpl;
import at.petrak.hexcasting.api.item.IotaHolderItem;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.item.IotaHolderItem;
import at.petrak.hexcasting.fabric.cc.HexCardinalComponents;
import dev.onyxstudios.cca.api.v3.item.ItemComponent;
import net.minecraft.nbt.CompoundTag;
@ -56,7 +56,7 @@ public abstract class CCItemIotaHolder extends ItemComponent implements CCIotaHo
@Override
public @Nullable CompoundTag readIotaTag() {
var iota = this.provider.apply(this.stack);
return iota == null ? null : HexIotaTypes.serialize(iota);
return iota == null ? null : IotaType.serialize(iota);
}
@Override

View file

@ -6,9 +6,10 @@ import at.petrak.hexcasting.api.addldata.ADIotaHolder;
import at.petrak.hexcasting.api.addldata.ADMediaHolder;
import at.petrak.hexcasting.api.casting.ActionRegistryEntry;
import at.petrak.hexcasting.api.casting.castables.SpecialHandler;
import at.petrak.hexcasting.api.casting.eval.CastingHarness;
import at.petrak.hexcasting.api.casting.eval.ResolvedPattern;
import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.api.mod.HexConfig;
@ -158,16 +159,14 @@ public class FabricXplatImpl implements IXplatAbstractions {
cc.setFlight(flight);
}
@Override
public void setAltiora(Player target, @Nullable AltioraAbility altiora) {
var cc = HexCardinalComponents.ALTIORA.get(target);
cc.setAltiora(altiora);
}
@Override
public void setHarness(ServerPlayer target, CastingHarness harness) {
var cc = HexCardinalComponents.HARNESS.get(target);
cc.setHarness(harness);
public void setStaffcastImage(ServerPlayer target, CastingImage image) {
var cc = HexCardinalComponents.STAFFCAST_IMAGE.get(target);
cc.setImage(image);
}
@Override
@ -207,9 +206,9 @@ public class FabricXplatImpl implements IXplatAbstractions {
}
@Override
public CastingHarness getHarness(ServerPlayer player, InteractionHand hand) {
var cc = HexCardinalComponents.HARNESS.get(player);
return cc.getHarness(hand);
public CastingVM getStaffcastVM(ServerPlayer player, InteractionHand hand) {
var cc = HexCardinalComponents.STAFFCAST_IMAGE.get(player);
return cc.getVM(hand);
}
@Override
@ -220,7 +219,7 @@ public class FabricXplatImpl implements IXplatAbstractions {
@Override
public void clearCastingData(ServerPlayer player) {
this.setHarness(player, null);
this.setStaffcastImage(player, null);
this.setPatterns(player, List.of());
}
@ -231,6 +230,12 @@ public class FabricXplatImpl implements IXplatAbstractions {
return cc.orElse(null);
}
@Override
public @Nullable ADMediaHolder findMediaHolder(ServerPlayer player) {
var cc = HexCardinalComponents.MEDIA_HOLDER.maybeGet(player);
return cc.orElse(null);
}
@Override
public @Nullable
ADIotaHolder findDataHolder(ItemStack stack) {

View file

@ -123,7 +123,7 @@ public class ForgeHexConfig implements HexConfig.CommonConfigAccess {
public static class Server implements HexConfig.ServerConfigAccess {
private static ForgeConfigSpec.IntValue opBreakHarvestLevel;
private static ForgeConfigSpec.IntValue maxRecurseDepth;
private static ForgeConfigSpec.IntValue maxOpCount;
private static ForgeConfigSpec.IntValue maxSpellCircleLength;
@ -141,8 +141,9 @@ public class ForgeHexConfig implements HexConfig.CommonConfigAccess {
public Server(ForgeConfigSpec.Builder builder) {
builder.push("Spells");
maxRecurseDepth = builder.comment("How many times a spell can recursively cast other spells")
.defineInRange("maxRecurseDepth", DEFAULT_MAX_RECURSE_DEPTH, 0, Integer.MAX_VALUE);
maxOpCount = builder.comment("The maximum number of actions that can be executed in one tick, to avoid " +
"hanging the server.")
.defineInRange("maxOpCount", DEFAULT_MAX_OP_COUNT, 0, Integer.MAX_VALUE);
opBreakHarvestLevel = builder.comment(
"The harvest level of the Break Block spell.",
"0 = wood, 1 = stone, 2 = iron, 3 = diamond, 4 = netherite."
@ -177,8 +178,8 @@ public class ForgeHexConfig implements HexConfig.CommonConfigAccess {
}
@Override
public int maxRecurseDepth() {
return maxRecurseDepth.get();
public int maxOpCount() {
return maxOpCount.get();
}
@Override

View file

@ -29,7 +29,7 @@ public class CapSyncers {
x.setAltiora(player, x.getAltiora(proto));
x.setSentinel(player, x.getSentinel(proto));
x.setColorizer(player, x.getColorizer(proto));
x.setHarness(player, x.getHarness(proto, InteractionHand.MAIN_HAND));
x.setStaffcastImage(player, x.getStaffcastVM(proto, InteractionHand.MAIN_HAND));
x.setPatterns(player, x.getPatternsSavedInUi(proto));
}

View file

@ -2,7 +2,7 @@ package at.petrak.hexcasting.forge.cap.adimpl;
import at.petrak.hexcasting.api.addldata.ADIotaHolder;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
@ -17,7 +17,7 @@ public record CapStaticIotaHolder(Function<ItemStack, Iota> provider,
public @Nullable
CompoundTag readIotaTag() {
var iota = provider.apply(stack);
return iota == null ? null : HexIotaTypes.serialize(iota);
return iota == null ? null : IotaType.serialize(iota);
}
@Override

View file

@ -8,9 +8,9 @@ import at.petrak.hexcasting.api.addldata.ADMediaHolder;
import at.petrak.hexcasting.api.casting.ActionRegistryEntry;
import at.petrak.hexcasting.api.casting.castables.SpecialHandler;
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment;
import at.petrak.hexcasting.api.casting.eval.CastingHarness;
import at.petrak.hexcasting.api.casting.eval.ResolvedPattern;
import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.api.mod.HexTags;
@ -207,7 +207,7 @@ public class ForgeXplatImpl implements IXplatAbstractions {
}
@Override
public void setHarness(ServerPlayer player, CastingHarness harness) {
public void setStaffcastImage(ServerPlayer player, CastingVM harness) {
player.getPersistentData().put(TAG_HARNESS, harness == null ? new CompoundTag() : harness.serializeToNBT());
}
@ -261,7 +261,7 @@ public class ForgeXplatImpl implements IXplatAbstractions {
CompoundTag tag = player.getPersistentData();
var exists = tag.getBoolean(TAG_SENTINEL_EXISTS);
if (!exists) {
return Sentinel.none();
return null;
}
var extendsRange = tag.getBoolean(TAG_SENTINEL_GREATER);
var position = HexUtils.vecFromNBT(tag.getLongArray(TAG_SENTINEL_POSITION));
@ -272,10 +272,10 @@ public class ForgeXplatImpl implements IXplatAbstractions {
}
@Override
public CastingHarness getHarness(ServerPlayer player, InteractionHand hand) {
public CastingVM getStaffcastVM(ServerPlayer player, InteractionHand hand) {
// This is always from a staff because we don't need to load the harness when casting from item
var ctx = new CastingEnvironment(player, hand, CastingEnvironment.CastSource.STAFF);
return CastingHarness.fromNBT(player.getPersistentData().getCompound(TAG_HARNESS), ctx);
return CastingVM.fromNBT(player.getPersistentData().getCompound(TAG_HARNESS), ctx);
}
@Override
@ -303,6 +303,12 @@ public class ForgeXplatImpl implements IXplatAbstractions {
return maybeCap.orElse(null);
}
@Override
public @Nullable ADMediaHolder findMediaHolder(ServerPlayer player) {
var maybeCap = player.getCapability(HexCapabilities.MEDIA).resolve();
return maybeCap.orElse(null);
}
@Override
public @Nullable
ADIotaHolder findDataHolder(ItemStack stack) {