merge in talia's fix? hopefully?
This commit is contained in:
commit
954f8918ae
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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)
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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?,
|
||||
)
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(", ");
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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) =
|
|
@ -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) =
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -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 =
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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();
|
||||
|
|
65
Common/src/main/java/at/petrak/hexcasting/common/casting/env/PackagedItemCastEnv.java
vendored
Normal file
65
Common/src/main/java/at/petrak/hexcasting/common/casting/env/PackagedItemCastEnv.java
vendored
Normal 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;
|
||||
}
|
||||
}
|
187
Common/src/main/java/at/petrak/hexcasting/common/casting/env/PlayerBasedCastEnv.java
vendored
Normal file
187
Common/src/main/java/at/petrak/hexcasting/common/casting/env/PlayerBasedCastEnv.java
vendored
Normal 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);
|
||||
}
|
||||
}
|
59
Common/src/main/java/at/petrak/hexcasting/common/casting/env/PlayerBasedMishapEnv.java
vendored
Normal file
59
Common/src/main/java/at/petrak/hexcasting/common/casting/env/PlayerBasedMishapEnv.java
vendored
Normal 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));
|
||||
}
|
||||
}
|
97
Common/src/main/java/at/petrak/hexcasting/common/casting/env/StaffCastEnv.java
vendored
Normal file
97
Common/src/main/java/at/petrak/hexcasting/common/casting/env/StaffCastEnv.java
vendored
Normal 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));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue