Merge branch 'main' into fix173+175

This commit is contained in:
petrak@ 2022-08-28 22:11:54 -05:00 committed by GitHub
commit d327d4d943
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
166 changed files with 4030 additions and 1283 deletions

View file

@ -38,6 +38,9 @@ dependencies {
compileOnly "at.petra-k.paucal:paucal-common-$minecraftVersion:$paucalVersion"
compileOnly "vazkii.patchouli:Patchouli-xplat:$minecraftVersion-$patchouliVersion"
compileOnly "org.jetbrains:annotations:$jetbrainsAnnotationsVersion"
testCompileOnly "org.jetbrains:annotations:$jetbrainsAnnotationsVersion"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.1'
}

View file

@ -337,5 +337,5 @@ b6593ea802a692c29b5032292df31beb84878ad8 data/hexcasting/advancements/aaa_wastef
4f4c94021adfb296e3ef3dce1acc46f724f38f92 data/hexcasting/advancements/aab_big_cast.json
2fe3543a209fca031b1eace7ea217c76142609cc data/hexcasting/advancements/enlightenment.json
eb6393ffc79966e4b5983a68157742b78cd12414 data/hexcasting/advancements/opened_eyes.json
ed0e62cb81783d8eb6323dd70609067219f163ec data/hexcasting/advancements/root.json
a66a85cef9a3195e4388ecc16786e2a9f6ed3b56 data/hexcasting/advancements/root.json
739cbdf7f204132f2acfab4df8d21c6197aa1456 data/hexcasting/advancements/y_u_no_cast_angy.json

View file

@ -21,9 +21,7 @@
"conditions": {
"items": [
{
"items": [
"hexcasting:charged_amethyst"
]
"tag": "hexcasting:grants_root_advancement"
}
]
}

View file

@ -5,6 +5,7 @@ import at.petrak.hexcasting.api.spell.math.EulerPathFinder
import at.petrak.hexcasting.api.spell.math.HexDir
import at.petrak.hexcasting.api.spell.math.HexPattern
import at.petrak.hexcasting.api.spell.mishaps.MishapInvalidPattern
import at.petrak.hexcasting.api.utils.getSafe
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
@ -26,6 +27,7 @@ import java.util.concurrent.ConcurrentMap
*/
object PatternRegistry {
private val operatorLookup = ConcurrentHashMap<ResourceLocation, Operator>()
private val keyLookup = ConcurrentHashMap<Operator, ResourceLocation>()
private val specialHandlers: ConcurrentLinkedDeque<SpecialHandlerEntry> = ConcurrentLinkedDeque()
// Map signatures to the "preferred" direction they start in and their operator ID.
@ -47,6 +49,7 @@ object PatternRegistry {
}
this.operatorLookup[id] = operator
this.keyLookup[operator] = id
if (isPerWorld) {
this.perWorldPatternLookup[id] = PerWorldEntry(pattern, id)
} else {
@ -81,14 +84,6 @@ object PatternRegistry {
*/
@JvmStatic
fun matchPatternAndID(pat: HexPattern, overworld: ServerLevel): Pair<Operator, ResourceLocation> {
// Pipeline:
// patterns are registered here every time the game boots
// when we try to look
for (handler in specialHandlers) {
val op = handler.handler.handlePattern(pat)
if (op != null) return op to handler.id
}
// Is it global?
val sig = pat.anglesSignature()
this.regularPatternLookup[sig]?.let {
@ -105,6 +100,14 @@ object PatternRegistry {
return op to it.first
}
// Lookup a special handler
// Do this last to prevent conflicts with great spells; this has happened a few times with
// create phial hahaha
for (handler in specialHandlers) {
val op = handler.handler.handlePattern(pat)
if (op != null) return op to handler.id
}
throw MishapInvalidPattern()
}
@ -119,6 +122,12 @@ object PatternRegistry {
return perWorldPatterns.lookup
}
/**
* Internal use only.
*/
@JvmStatic
fun lookupPattern(op: Operator): ResourceLocation? = this.keyLookup[op]
/**
* Internal use only.
*/
@ -175,7 +184,7 @@ object PatternRegistry {
val (id, startDir) = rhs
val entry = CompoundTag()
entry.putString(TAG_OP_ID, id.toString())
entry.putInt(TAG_START_DIR, startDir.ordinal)
entry.putByte(TAG_START_DIR, startDir.ordinal.toByte())
tag.put(sig, entry)
}
return tag
@ -186,8 +195,8 @@ object PatternRegistry {
val map = HashMap<String, Pair<ResourceLocation, HexDir>>()
for (sig in tag.allKeys) {
val entry = tag.getCompound(sig)
val opId = ResourceLocation.tryParse(entry.getString(TAG_OP_ID))!!
val startDir = HexDir.values()[entry.getInt(TAG_START_DIR)]
val opId = ResourceLocation.tryParse(entry.getString(TAG_OP_ID)) ?: continue
val startDir = HexDir.values().getSafe(entry.getByte(TAG_START_DIR))
map[sig] = opId to startDir
}
return Save(map)

View file

@ -1,25 +1,69 @@
package at.petrak.hexcasting.api.addldata;
import org.jetbrains.annotations.ApiStatus;
public interface ManaHolder {
/**
* Use {@code withdrawMana(-1, true)}
*
* @see ManaHolder#withdrawMana(int, boolean)
*/
@ApiStatus.OverrideOnly
int getMana();
/**
* Use {@code withdrawMana(-1, true) + insertMana(-1, true)} where possible
*
* @see ManaHolder#insertMana(int, boolean)
* @see ManaHolder#withdrawMana(int, boolean)
*/
@ApiStatus.OverrideOnly
int getMaxMana();
/**
* Use {@code insertMana(mana - withdrawMana(-1, true), false)} where possible
*
* @see ManaHolder#insertMana(int, boolean)
* @see ManaHolder#withdrawMana(int, boolean)
*/
@ApiStatus.OverrideOnly
void setMana(int mana);
/**
* Whether this mana holder can have mana inserted into it.
*/
boolean canRecharge();
/**
* Whether this mana holder can be extracted from.
*/
boolean canProvide();
/**
* The priority for this mana holder to be selected when casting a hex. Higher priorities are taken first.
*
* By default,
* * Charged Amethyst has priority 1
* * Amethyst Shards have priority 2
* * Amethyst Dust has priority 3
* * Items which hold mana have priority 40
*/
int getConsumptionPriority();
/**
* Whether the mana inside this mana holder may be used to construct a battery.
*/
boolean canConstructBattery();
/**
* Withdraws mana from the holder. Returns the amount of mana extracted, which may be less or more than the cost.
*
* Even if {@link ManaHolder#canProvide} is false, you can still withdraw mana this way.
*
* Withdrawing a negative amount will act as though you attempted to withdraw as much mana as the holder contains.
*/
default int withdrawMana(int cost, boolean simulate) {
if (!canProvide()) {
return 0;
}
var manaHere = getMana();
if (cost < 0) {
cost = manaHere;
@ -30,4 +74,30 @@ public interface ManaHolder {
}
return Math.min(cost, manaHere);
}
/**
* Inserts mana into the holder. Returns the amount of mana inserted, which may be less than the requested amount.
*
* Even if {@link ManaHolder#canRecharge} is false, you can still insert mana this way.
*
* Inserting a negative amount will act as though you attempted to insert exactly as much mana as the holder was missing.
*/
default int insertMana(int amount, boolean simulate) {
var manaHere = getMana();
int emptySpace = getMaxMana() - manaHere;
if (emptySpace <= 0) {
return 0;
}
if (amount < 0) {
amount = emptySpace;
}
int inserting = Math.min(amount, emptySpace);
if (!simulate) {
var newMana = manaHere + inserting;
setMana(newMana);
}
return inserting;
}
}

View file

@ -35,7 +35,7 @@ public class OvercastTrigger extends SimpleCriterionTrigger<OvercastTrigger.Inst
super.trigger(player, inst -> {
var manaToHealth = HexConfig.common().manaToHealthRate();
var healthUsed = manaGenerated / manaToHealth;
return inst.test(manaGenerated, healthUsed / player.getMaxHealth(), player.getHealth() - (float) healthUsed);
return inst.test(manaGenerated, healthUsed / player.getMaxHealth(), player.getHealth());
});
}

View file

@ -15,8 +15,6 @@ import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.lib.HexSounds;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
@ -36,6 +34,7 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
@ -44,6 +43,7 @@ import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import java.text.DecimalFormat;
import java.util.*;
public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implements WorldlyContainer {
@ -56,6 +56,8 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen
TAG_MANA = "mana",
TAG_LAST_MISHAP = "last_mishap";
private static final DecimalFormat DUST_AMOUNT = new DecimalFormat("###,###.##");
@Nullable
private UUID activator = null;
@Nullable
@ -114,16 +116,15 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen
}
public void applyScryingLensOverlay(List<Pair<ItemStack, Component>> lines,
BlockState state, BlockPos pos,
LocalPlayer observer, ClientLevel world,
Direction hitFace) {
BlockState state, BlockPos pos,
Player observer, Level world,
Direction hitFace) {
if (world.getBlockEntity(pos) instanceof BlockEntityAbstractImpetus beai) {
if (beai.getMana() < 0) {
lines.add(new Pair<>(new ItemStack(HexItems.AMETHYST_DUST), ItemCreativeUnlocker.infiniteMedia(world)));
} else {
var dustCount = (float) beai.getMana() / (float) ManaConstants.DUST_UNIT;
var dustCmp = new TranslatableComponent("hexcasting.tooltip.lens.impetus.mana",
String.format("%.2f", dustCount));
var dustCmp = new TranslatableComponent("hexcasting.tooltip.mana",
DUST_AMOUNT.format(beai.getMana() / (float) ManaConstants.DUST_UNIT));
lines.add(new Pair<>(new ItemStack(HexItems.AMETHYST_DUST), dustCmp));
}
@ -540,6 +541,12 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen
@Override
public boolean canPlaceItem(int index, ItemStack stack) {
if (remainingManaCapacity() == 0)
return false;
if (stack.is(HexItems.CREATIVE_UNLOCKER))
return true;
var manamount = extractManaFromItem(stack, true);
return manamount > 0;
}
@ -557,7 +564,7 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen
} else {
var manamount = extractManaFromItem(stack, false);
if (manamount > 0) {
this.mana += manamount;
this.mana = Math.min(manamount + mana, MAX_CAPACITY);
this.sync();
}
}
@ -571,6 +578,6 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen
public int remainingManaCapacity() {
if (this.mana < 0)
return 0;
return MAX_CAPACITY - this.mana;
return Math.max(0, MAX_CAPACITY - this.mana);
}
}

View file

@ -4,13 +4,14 @@ import at.petrak.hexcasting.xplat.IXplatAbstractions;
import com.google.common.collect.Lists;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.player.Player;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.BeehiveBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
@ -36,15 +37,22 @@ public final class ScryingLensOverlayRegistry {
private static final List<Pair<OverlayPredicate, OverlayBuilder>> PREDICATE_LOOKUP = new Vector<>();
// implemented as a map to allow for weak dereferencing
private static final Map<LocalPlayer, Pair<BlockPos, Integer>> comparatorData = new WeakHashMap<>();
private static final Map<Player, Pair<BlockPos, Integer>> comparatorData = new WeakHashMap<>();
private static final Map<Player, Pair<BlockPos, Integer>> beeData = new WeakHashMap<>();
public static void receiveComparatorValue(BlockPos pos, int value) {
LocalPlayer player = Minecraft.getInstance().player;
public static void receiveComparatorAndBeeValue(BlockPos pos, int comparator, int bee) {
Player player = Minecraft.getInstance().player;
if (player != null) {
if (pos == null || value == -1) {
if (pos == null || comparator == -1) {
comparatorData.remove(player);
} else {
comparatorData.put(player, new Pair<>(pos, value));
comparatorData.put(player, new Pair<>(pos, comparator));
}
if (pos == null || bee == -1) {
beeData.remove(player);
} else {
beeData.put(player, new Pair<>(pos, bee));
}
}
}
@ -78,6 +86,34 @@ public final class ScryingLensOverlayRegistry {
return comparatorValue.getSecond();
}
public static int getBeeValue() {
var mc = Minecraft.getInstance();
var player = mc.player;
var level = mc.level;
var result = mc.hitResult;
if (player == null || level == null || result == null || result.getType() != HitResult.Type.BLOCK) {
return -1;
}
var beeValue = beeData.get(player);
if (beeValue == null) {
return -1;
}
var pos = ((BlockHitResult) result).getBlockPos();
if (!pos.equals(beeValue.getFirst())) {
return -1;
}
var state = mc.level.getBlockState(pos);
if (!(state.getBlock() instanceof BeehiveBlock)) {
return -1;
}
return beeValue.getSecond();
}
/**
* Add the block to display things when the player is holding a lens and looking at it.
*
@ -113,7 +149,7 @@ public final class ScryingLensOverlayRegistry {
* Internal use only.
*/
public static @NotNull List<Pair<ItemStack, Component>> getLines(BlockState state, BlockPos pos,
LocalPlayer observer, ClientLevel world,
Player observer, Level world,
Direction hitFace) {
List<Pair<ItemStack, Component>> lines = Lists.newArrayList();
var idLookedup = ID_LOOKUP.get(IXplatAbstractions.INSTANCE.getID(state.getBlock()));
@ -138,8 +174,8 @@ public final class ScryingLensOverlayRegistry {
@FunctionalInterface
public interface OverlayBuilder {
void addLines(List<Pair<ItemStack, Component>> lines,
BlockState state, BlockPos pos, LocalPlayer observer,
ClientLevel world,
BlockState state, BlockPos pos, Player observer,
Level world,
Direction hitFace);
}
@ -148,8 +184,8 @@ public final class ScryingLensOverlayRegistry {
*/
@FunctionalInterface
public interface OverlayPredicate {
boolean test(BlockState state, BlockPos pos, LocalPlayer observer,
ClientLevel world,
boolean test(BlockState state, BlockPos pos, Player observer,
Level world,
Direction hitFace);
}
}

View file

@ -2,9 +2,11 @@ package at.petrak.hexcasting.api.item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.ApiStatus;
import java.util.UUID;
@ApiStatus.OverrideOnly
public interface ColorizerItem {
int color(ItemStack stack, UUID owner, float time, Vec3 position);
}

View file

@ -11,10 +11,12 @@ import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ApiStatus.OverrideOnly
public interface DataHolderItem {
String TAG_OVERRIDE_VISUALLY = "VisualOverride";

View file

@ -3,10 +3,12 @@ package at.petrak.hexcasting.api.item;
import at.petrak.hexcasting.api.spell.SpellDatum;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ApiStatus.OverrideOnly
public interface HexHolderItem extends ManaHolderItem {
boolean canDrawManaFromInventory(ItemStack stack);

View file

@ -1,7 +1,12 @@
package at.petrak.hexcasting.api.item;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
/**
* This interface should not be accessed direc
*/
@ApiStatus.OverrideOnly
public interface ManaHolderItem {
int getMana(ItemStack stack);
@ -32,4 +37,23 @@ public interface ManaHolderItem {
}
return Math.min(cost, manaHere);
}
default int insertMana(ItemStack stack, int amount, boolean simulate) {
var manaHere = getMana(stack);
int emptySpace = getMaxMana(stack) - manaHere;
if (emptySpace <= 0) {
return 0;
}
if (amount < 0) {
amount = emptySpace;
}
int inserting = Math.min(amount, emptySpace);
if (!simulate) {
var newMana = manaHere + inserting;
setMana(stack, newMana);
}
return inserting;
}
}

View file

@ -0,0 +1,83 @@
package at.petrak.hexcasting.api.misc;
import at.petrak.hexcasting.api.addldata.ManaHolder;
import at.petrak.hexcasting.api.spell.casting.CastingContext;
import at.petrak.hexcasting.api.spell.casting.CastingHarness;
import com.google.common.collect.Lists;
import net.minecraft.util.ToFloatFunction;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
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<ManaHolder>>> MANA_HOLDER_DISCOVERY = new ArrayList<>();
private static final List<ToFloatFunction<Player>> GRID_SCALE_MODIFIERS = new ArrayList<>();
private static final List<Function<CastingContext, List<ItemStack>>> ITEM_SLOT_DISCOVERER = new ArrayList<>();
private static final List<Function<CastingContext, List<ItemStack>>> OPERATIVE_SLOT_DISCOVERER = new ArrayList<>();
public static boolean hasLens(Player player) {
for (var predicate : HAS_LENS_PREDICATE) {
if (predicate.test(player)) {
return true;
}
}
return false;
}
public static List<ManaHolder> collectManaHolders(CastingHarness harness) {
List<ManaHolder> holders = Lists.newArrayList();
for (var discoverer : MANA_HOLDER_DISCOVERY) {
holders.addAll(discoverer.apply(harness));
}
return holders;
}
public static float gridScaleModifier(Player player) {
float mod = 1;
for (var modifier : GRID_SCALE_MODIFIERS) {
mod *= modifier.apply(player);
}
return mod;
}
public static List<ItemStack> collectItemSlots(CastingContext ctx) {
List<ItemStack> stacks = Lists.newArrayList();
for (var discoverer : ITEM_SLOT_DISCOVERER) {
stacks.addAll(discoverer.apply(ctx));
}
return stacks;
}
public static List<ItemStack> collectOperableSlots(CastingContext ctx) {
List<ItemStack> stacks = Lists.newArrayList();
for (var discoverer : OPERATIVE_SLOT_DISCOVERER) {
stacks.addAll(discoverer.apply(ctx));
}
return stacks;
}
public static void addLensPredicate(Predicate<Player> predicate) {
HAS_LENS_PREDICATE.add(predicate);
}
public static void addManaHolderDiscoverer(Function<CastingHarness, List<ManaHolder>> discoverer) {
MANA_HOLDER_DISCOVERY.add(discoverer);
}
public static void addGridScaleModifier(ToFloatFunction<Player> modifier) {
GRID_SCALE_MODIFIERS.add(modifier);
}
public static void addItemSlotDiscoverer(Function<CastingContext, List<ItemStack>> discoverer) {
ITEM_SLOT_DISCOVERER.add(discoverer);
}
public static void addOperativeSlotDiscoverer(Function<CastingContext, List<ItemStack>> discoverer) {
OPERATIVE_SLOT_DISCOVERER.add(discoverer);
}
}

View file

@ -12,6 +12,7 @@ public class HexItemTags {
public static final TagKey<Item> AKASHIC_PLANKS = create("akashic_planks");
public static final TagKey<Item> WANDS = create("wands");
public static final TagKey<Item> PHIAL_BASE = create("phial_base");
public static final TagKey<Item> GRANTS_ROOT_ADVANCEMENT = create("grants_root_advancement");
public static TagKey<Item> create(String name) {
return create(modLoc(name));

View file

@ -1,8 +1,14 @@
package at.petrak.hexcasting.api.spell
import at.petrak.hexcasting.api.PatternRegistry
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.utils.asTranslatedComponent
import at.petrak.hexcasting.api.utils.lightPurple
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.phys.Vec3
import java.text.DecimalFormat
/**
* Manipulates the stack in some way, usually by popping some number of values off the stack
@ -40,6 +46,11 @@ interface Operator {
*/
val causesBlindDiversion: Boolean get() = this is SpellOperator
/**
* The component for displaying this pattern's name. Override for dynamic patterns.
*/
val displayName: Component get() = "hexcasting.spell.${PatternRegistry.lookupPattern(this)}".asTranslatedComponent.lightPurple
companion object {
// I see why vzakii did this: you can't raycast out to infinity!
const val MAX_DISTANCE: Double = 32.0
@ -57,6 +68,20 @@ interface Operator {
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> =
listOf(x)
}
private val DOUBLE_FORMATTER = DecimalFormat("####.####")
@JvmStatic
fun makeConstantOp(x: Double, key: ResourceLocation): Operator = object : ConstManaOperator {
override val argc: Int
get() = 0
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> =
x.asSpellResult
override val displayName: Component
get() = "hexcasting.spell.$key".asTranslatedComponent(DOUBLE_FORMATTER.format(x)).lightPurple
}
}
}

View file

@ -225,7 +225,7 @@ class SpellDatum<T : Any> private constructor(val payload: T) {
TAG_PATTERN -> {
val pat = HexPattern.fromNBT(nbt.getCompound(TAG_PATTERN))
var angleDesc = pat.anglesSignature()
if (angleDesc.isNotBlank()) angleDesc = " $angleDesc";
if (angleDesc.isNotBlank()) angleDesc = " $angleDesc"
out += "HexPattern(".gold
out += "${pat.startDir}$angleDesc".white
out += ")".gold

View file

@ -1,7 +1,7 @@
package at.petrak.hexcasting.api.spell
import at.petrak.hexcasting.api.spell.casting.CastingContext
import java.util.*
import at.petrak.hexcasting.api.utils.getSafe
/**
* Miscellaneous spell datums used as markers, etc.
@ -22,8 +22,7 @@ enum class Widget : ConstManaOperator {
companion object {
@JvmStatic
fun fromString(key: String): Widget {
val lowercaseKey = key.lowercase(Locale.ROOT)
return values().firstOrNull { it.name.lowercase(Locale.ROOT) == lowercaseKey } ?: GARBAGE
return values().getSafe(key, GARBAGE)
}
}
}

View file

@ -1,6 +1,7 @@
package at.petrak.hexcasting.api.spell.casting
import at.petrak.hexcasting.api.HexAPI.modLoc
import at.petrak.hexcasting.api.misc.DiscoveryHandlers
import at.petrak.hexcasting.api.mod.HexConfig
import at.petrak.hexcasting.api.spell.Operator
import at.petrak.hexcasting.api.spell.mishaps.MishapEntityTooFarAway
@ -14,8 +15,8 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.GameType
import net.minecraft.world.phys.Vec3
import java.util.function.Predicate
import kotlin.math.min
@ -39,10 +40,14 @@ data class CastingContext(
private val entitiesGivenMotion = mutableSetOf<Entity>()
inline fun getHeldItemToOperateOn(acceptItemIf: (ItemStack) -> Boolean): Pair<ItemStack, InteractionHand> {
val handItem = caster.getItemInHand(castingHand)
if (!acceptItemIf(handItem))
return caster.getItemInHand(otherHand) to otherHand
return handItem to castingHand
val handItem = caster.getItemInHand(otherHand)
if (!acceptItemIf(handItem)) {
val castingItem = caster.getItemInHand(castingHand)
if (acceptItemIf(castingItem)) {
return castingItem to castingHand
}
}
return handItem to otherHand
}
/**
@ -83,7 +88,8 @@ data class CastingContext(
return entitiesGivenMotion.contains(target)
}
fun isVecInWorld(vec: Vec3) = world.isInWorldBounds(BlockPos(vec)) && world.worldBorder.isWithinBounds(vec.x, vec.z, 0.5)
fun isVecInWorld(vec: Vec3) =
world.isInWorldBounds(BlockPos(vec)) && world.worldBorder.isWithinBounds(vec.x, vec.z, 0.5)
fun isVecInRange(vec: Vec3): Boolean {
val sentinel = IXplatAbstractions.INSTANCE.getSentinel(caster)
@ -117,6 +123,12 @@ data class CastingContext(
return isVecInRange(entity.position())
}
fun canEditBlockAt(pos: BlockPos): Boolean {
return this.isVecInRange(Vec3.atCenterOf(pos))
&& this.caster.gameMode.gameModeForPlayer != GameType.ADVENTURE
&& this.world.mayInteract(this.caster, pos)
}
/**
* Return the slot from which to take blocks and items.
*/
@ -125,25 +137,12 @@ data class CastingContext(
// for what purpose i cannot imagine
// http://redditpublic.com/images/b/b2/Items_slot_number.png looks right
// and offhand is 150 Inventory.java:464
fun getOperativeSlot(stackOK: Predicate<ItemStack>): Int? {
val otherHandStack = this.caster.getItemInHand(this.otherHand)
if (stackOK.test(otherHandStack)) {
return when (this.otherHand) {
InteractionHand.MAIN_HAND -> this.caster.inventory.selected
InteractionHand.OFF_HAND -> 150
}
}
val anchorSlot = when (this.castingHand) {
// slot to the right of the wand
InteractionHand.MAIN_HAND -> (this.caster.inventory.selected + 1) % 9
// first hotbar slot
InteractionHand.OFF_HAND -> 0
}
for (delta in 0 until 9) {
val slot = (anchorSlot + delta) % 9
val stack = this.caster.inventory.getItem(slot)
fun getOperativeSlot(stackOK: Predicate<ItemStack>): ItemStack? {
val operable = DiscoveryHandlers.collectOperableSlots(this)
for (stack in operable) {
if (stackOK.test(stack)) {
return slot
return stack
}
}
return null
@ -154,17 +153,14 @@ data class CastingContext(
* Return whether the withdrawal was successful.
*/
// https://github.com/VazkiiMods/Psi/blob/master/src/main/java/vazkii/psi/common/spell/trick/block/PieceTrickPlaceBlock.java#L143
fun withdrawItem(item: Item, count: Int, actuallyRemove: Boolean): Boolean {
fun withdrawItem(item: ItemStack, count: Int, actuallyRemove: Boolean): Boolean {
if (this.caster.isCreative) return true
val inv = this.caster.inventory
// TODO: withdraw from ender chest given a specific ender charm?
val stacksToExamine = inv.items.toMutableList().apply { removeAt(inv.selected) }.asReversed().toMutableList()
stacksToExamine.addAll(inv.offhand)
stacksToExamine.add(inv.getSelected())
val stacksToExamine = DiscoveryHandlers.collectItemSlots(this)
fun matches(stack: ItemStack): Boolean =
!stack.isEmpty && stack.`is`(item)
!stack.isEmpty && ItemStack.isSameItemSameTags(item, stack)
val presentCount = stacksToExamine.fold(0) { acc, stack ->
acc + if (matches(stack)) stack.count else 0
@ -206,4 +202,28 @@ data class CastingContext(
val advs = this.caster.advancements
return advs.getOrStartProgress(adv!!).isDone
}
companion object {
init {
DiscoveryHandlers.addItemSlotDiscoverer {
val inv = it.caster.inventory
inv.items.toMutableList().apply { removeAt(inv.selected) }.asReversed().toMutableList().apply {
addAll(inv.offhand)
add(inv.getSelected())
}
}
DiscoveryHandlers.addOperativeSlotDiscoverer {
val slots = mutableListOf<ItemStack>()
val anchorSlot = if (it.castingHand == InteractionHand.MAIN_HAND) (it.caster.inventory.selected + 1) % 9 else 0
slots.add(it.caster.getItemInHand(it.otherHand))
for (delta in 0 until 9) {
val slot = (anchorSlot + delta) % 9
slots.add(it.caster.inventory.getItem(slot))
}
slots
}
}
}
}

View file

@ -3,6 +3,7 @@ package at.petrak.hexcasting.api.spell.casting
import at.petrak.hexcasting.api.PatternRegistry
import at.petrak.hexcasting.api.advancements.HexAdvancementTriggers
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus
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
@ -13,7 +14,6 @@ import at.petrak.hexcasting.api.spell.math.HexDir
import at.petrak.hexcasting.api.spell.math.HexPattern
import at.petrak.hexcasting.api.spell.mishaps.*
import at.petrak.hexcasting.api.utils.*
import at.petrak.hexcasting.common.items.magic.ItemCreativeUnlocker
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
@ -47,6 +47,24 @@ class CastingHarness private constructor(
*/
fun executeIota(iota: SpellDatum<*>, world: ServerLevel): ControllerInfo = executeIotas(listOf(iota), world)
private fun getOperatorForPattern(iota: SpellDatum<*>, world: ServerLevel): Operator? {
if (iota.getType() == DatumType.PATTERN)
return PatternRegistry.matchPattern(iota.payload as HexPattern, world)
return null
}
private fun getPatternForFrame(frame: ContinuationFrame): HexPattern? {
if (frame !is ContinuationFrame.Evaluate) return null
return frame.list.car.payload as? HexPattern
}
private fun getOperatorForFrame(frame: ContinuationFrame, world: ServerLevel): Operator? {
if (frame !is ContinuationFrame.Evaluate) return null
return getOperatorForPattern(frame.list.car, world)
}
/**
* Given a list of iotas, execute them in sequence.
*/
@ -60,7 +78,23 @@ class CastingHarness private constructor(
// Take the top of the continuation stack...
val next = continuation.frame
// ...and execute it.
val result = next.evaluate(continuation.next, world, this)
val result = try {
next.evaluate(continuation.next, world, this)
} catch (mishap: Mishap) {
val pattern = getPatternForFrame(next)
val operator = getOperatorForFrame(next, world)
CastResult(
continuation,
null,
mishap.resolutionType(ctx),
listOf(
OperatorSideEffect.DoMishap(
mishap,
Mishap.Context(pattern ?: HexPattern(HexDir.WEST), operator)
)
)
)
}
// Then write all pertinent data back to the harness for the next iteration.
if (result.newData != null) {
this.applyFunctionalData(result.newData)
@ -112,7 +146,7 @@ class CastingHarness private constructor(
listOf(
OperatorSideEffect.DoMishap(
mishap,
Mishap.Context(iota.payload as? HexPattern ?: HexPattern(HexDir.WEST), null)
Mishap.Context(iota.payload as? HexPattern ?: HexPattern(HexDir.WEST), getOperatorForPattern(iota, world))
)
),
)
@ -125,7 +159,7 @@ class CastingHarness private constructor(
listOf(
OperatorSideEffect.DoMishap(
MishapError(exception),
Mishap.Context(iota.payload as? HexPattern ?: HexPattern(HexDir.WEST), null)
Mishap.Context(iota.payload as? HexPattern ?: HexPattern(HexDir.WEST), getOperatorForPattern(iota, world))
)
)
)
@ -204,7 +238,7 @@ class CastingHarness private constructor(
continuation,
null,
mishap.resolutionType(ctx),
listOf(OperatorSideEffect.DoMishap(mishap, Mishap.Context(newPat, operatorIdPair?.second))),
listOf(OperatorSideEffect.DoMishap(mishap, Mishap.Context(newPat, operatorIdPair?.first))),
)
} catch (exception: Exception) {
exception.printStackTrace()
@ -215,7 +249,7 @@ class CastingHarness private constructor(
listOf(
OperatorSideEffect.DoMishap(
MishapError(exception),
Mishap.Context(newPat, operatorIdPair?.second)
Mishap.Context(newPat, operatorIdPair?.first)
)
)
)
@ -381,21 +415,21 @@ class CastingHarness private constructor(
val casterHexHolder = IXplatAbstractions.INSTANCE.findHexHolder(casterStack)
val hexHolderDrawsFromInventory = if (casterHexHolder != null) {
if (casterManaHolder != null) {
val manaAvailable = casterManaHolder.mana
val manaAvailable = casterManaHolder.withdrawMana(-1, true)
val manaToTake = min(costLeft, manaAvailable)
if (!fake) casterManaHolder.mana = manaAvailable - manaToTake
if (!fake) casterManaHolder.withdrawMana(manaToTake, false)
costLeft -= manaToTake
}
casterHexHolder.canDrawManaFromInventory()
} else {
false
}
if (casterStack.`is`(HexItemTags.WANDS) || hexHolderDrawsFromInventory) {
val manableItems = this.ctx.caster.inventory.items
.filter(::isManaItem)
val manaSources = DiscoveryHandlers.collectManaHolders(this)
.sortedWith(Comparator(::compareManaItem).reversed())
for (stack in manableItems) {
costLeft -= extractMana(stack, costLeft, simulate = fake && !ItemCreativeUnlocker.isDebug(stack))
for (source in manaSources) {
costLeft -= extractMana(source, costLeft, simulate = fake)
if (costLeft <= 0)
break
}
@ -407,13 +441,17 @@ class CastingHarness private constructor(
val manaAbleToCastFromHP = this.ctx.caster.health * manaToHealth
val manaToActuallyPayFor = min(manaAbleToCastFromHP.toInt(), costLeft)
if (!fake) {
HexAdvancementTriggers.OVERCAST_TRIGGER.trigger(this.ctx.caster, manaToActuallyPayFor)
this.ctx.caster.awardStat(HexStatistics.MANA_OVERCASTED, manaCost - costLeft)
costLeft -= if (!fake) {
Mishap.trulyHurt(this.ctx.caster, HexDamageSources.OVERCAST, healthtoRemove.toFloat())
val actuallyTaken = (manaAbleToCastFromHP - (this.ctx.caster.health * manaToHealth)).toInt()
HexAdvancementTriggers.OVERCAST_TRIGGER.trigger(this.ctx.caster, actuallyTaken)
this.ctx.caster.awardStat(HexStatistics.MANA_OVERCASTED, manaCost - costLeft)
actuallyTaken
} else {
manaToActuallyPayFor
}
costLeft -= manaToActuallyPayFor
}
}
}
@ -461,6 +499,24 @@ class CastingHarness private constructor(
const val TAG_ESCAPE_NEXT = "escape_next"
const val TAG_PREPACKAGED_COLORIZER = "prepackaged_colorizer"
init {
DiscoveryHandlers.addManaHolderDiscoverer {
it.ctx.caster.inventory.items
.filter(::isManaItem)
.mapNotNull(IXplatAbstractions.INSTANCE::findManaHolder)
}
DiscoveryHandlers.addManaHolderDiscoverer {
it.ctx.caster.inventory.armor
.filter(::isManaItem)
.mapNotNull(IXplatAbstractions.INSTANCE::findManaHolder)
}
DiscoveryHandlers.addManaHolderDiscoverer {
it.ctx.caster.inventory.offhand
.filter(::isManaItem)
.mapNotNull(IXplatAbstractions.INSTANCE::findManaHolder)
}
}
@JvmStatic
fun fromNBT(nbt: CompoundTag, ctx: CastingContext): CastingHarness {
return try {

View file

@ -1,6 +1,6 @@
package at.petrak.hexcasting.api.spell.casting
import java.util.*
import at.petrak.hexcasting.api.utils.getSafe
enum class ResolvedPatternType(val color: Int, val fadeColor: Int, val success: Boolean) {
UNRESOLVED(0x7f7f7f, 0xcccccc, false),
@ -12,8 +12,7 @@ enum class ResolvedPatternType(val color: Int, val fadeColor: Int, val success:
companion object {
@JvmStatic
fun fromString(key: String): ResolvedPatternType {
val lowercaseKey = key.lowercase(Locale.ROOT)
return values().firstOrNull { it.name.lowercase(Locale.ROOT) == lowercaseKey } ?: UNRESOLVED
return values().getSafe(key)
}
}
}

View file

@ -1,6 +1,6 @@
package at.petrak.hexcasting.api.spell.math
import java.util.*
import at.petrak.hexcasting.api.utils.getSafe
enum class HexDir {
NORTH_EAST, EAST, SOUTH_EAST, SOUTH_WEST, WEST, NORTH_WEST;
@ -28,8 +28,7 @@ enum class HexDir {
companion object {
@JvmStatic
fun fromString(key: String): HexDir {
val lowercaseKey = key.lowercase(Locale.ROOT)
return values().firstOrNull { it.name.lowercase(Locale.ROOT) == lowercaseKey } ?: WEST
return values().getSafe(key, WEST)
}
}
}

View file

@ -3,6 +3,7 @@ package at.petrak.hexcasting.api.spell.math
import at.petrak.hexcasting.api.utils.NBTBuilder
import at.petrak.hexcasting.api.utils.coordToPx
import at.petrak.hexcasting.api.utils.findCenter
import at.petrak.hexcasting.api.utils.getSafe
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.world.phys.Vec2
@ -134,8 +135,8 @@ data class HexPattern(public val startDir: HexDir, public val angles: MutableLis
@JvmStatic
fun fromNBT(tag: CompoundTag): HexPattern {
val startDir = HexDir.values()[tag.getByte(TAG_START_DIR).toInt()]
val angles = tag.getByteArray(TAG_ANGLES).map { HexAngle.values()[it.toInt()] }
val startDir = HexDir.values().getSafe(tag.getByte(TAG_START_DIR))
val angles = tag.getByteArray(TAG_ANGLES).map(HexAngle.values()::getSafe)
return HexPattern(startDir, angles.toMutableList())
}

View file

@ -2,6 +2,7 @@ package at.petrak.hexcasting.api.spell.mishaps
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.mod.HexItemTags
import at.petrak.hexcasting.api.spell.Operator
import at.petrak.hexcasting.api.spell.ParticleSpray
import at.petrak.hexcasting.api.spell.SpellDatum
import at.petrak.hexcasting.api.spell.casting.CastingContext
@ -10,11 +11,11 @@ import at.petrak.hexcasting.api.spell.math.HexPattern
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.lastHurt
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.resources.ResourceLocation
import net.minecraft.world.InteractionHand
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.LivingEntity
@ -53,8 +54,8 @@ sealed class Mishap : Throwable() {
protected fun error(stub: String, vararg args: Any): Component =
"hexcasting.mishap.$stub".asTranslatedComponent(*args)
protected fun actionName(action: ResourceLocation?): Component =
"hexcasting.spell.${action ?: "unknown"}".asTranslatedComponent.lightPurple
protected fun actionName(action: Operator?): Component =
action?.displayName ?: "hexcasting.spell.null".asTranslatedComponent.lightPurple
protected fun yeetHeldItemsTowards(ctx: CastingContext, targetPos: Vec3) {
// Knock the player's items out of their hands
@ -75,6 +76,8 @@ sealed class Mishap : Throwable() {
protected fun yeetHeldItem(ctx: CastingContext, 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)
@ -98,10 +101,11 @@ sealed class Mishap : Throwable() {
return ctx.world.getBlockState(pos).block.name
}
data class Context(val pattern: HexPattern, val action: ResourceLocation?)
data class Context(val pattern: HexPattern, val action: Operator?)
companion object {
fun trulyHurt(entity: LivingEntity, source: DamageSource, amount: Float) {
val targetHealth = entity.health - amount
if (entity.invulnerableTime > 10) {
val lastHurt = entity.lastHurt
if (lastHurt < amount)
@ -109,7 +113,25 @@ sealed class Mishap : Throwable() {
else
entity.lastHurt -= amount
}
entity.hurt(source, amount)
if (!entity.hurt(source, amount)) {
// Ok, if you REALLY don't want to play nice...
entity.health = targetHealth
entity.markHurt()
if (entity.isDeadOrDying) {
if (!entity.checkTotemDeathProtection(source)) {
val sound = entity.deathSoundAccessor
if (sound != null) {
entity.playSound(sound, entity.soundVolumeAccessor, entity.voicePitch)
}
entity.die(source)
}
} else {
entity.playHurtSound(source)
}
entity.setHurtWithStamp(source, entity.level.gameTime)
}
}
}
}

View file

@ -89,6 +89,18 @@ fun pxToCoord(px: Vec2, size: Float, offset: Vec2): HexCoord {
HexCoord(q, r + (rf + 0.5 * qf).roundToInt())
}
@JvmOverloads
fun <T : Enum<T>> Array<T>.getSafe(key: String, default: T = this[0]): T {
val lowercaseKey = key.lowercase(Locale.ROOT)
return firstOrNull { it.name.lowercase(Locale.ROOT) == lowercaseKey } ?: default
}
@JvmOverloads
fun <T : Enum<T>> Array<T>.getSafe(index: Byte, default: T = this[0]) = getSafe(index.toInt(), default)
@JvmOverloads
fun <T : Enum<T>> Array<T>.getSafe(index: Int, default: T = this[0]) = if (index in indices) this[index] else default
fun String.withStyle(op: (Style) -> Style): MutableComponent = asTextComponent.withStyle(op)
fun String.withStyle(style: Style): MutableComponent = asTextComponent.withStyle(style)
fun String.withStyle(formatting: ChatFormatting): MutableComponent = asTextComponent.withStyle(formatting)

View file

@ -1,6 +1,7 @@
@file:JvmName("ManaHelper")
package at.petrak.hexcasting.api.utils
import at.petrak.hexcasting.api.addldata.ManaHolder
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.util.Mth
import net.minecraft.world.item.ItemStack
@ -30,26 +31,38 @@ fun extractMana(
): Int {
val manaHolder = IXplatAbstractions.INSTANCE.findManaHolder(stack) ?: return 0
if (drainForBatteries && !manaHolder.canConstructBattery())
return extractMana(manaHolder, cost, drainForBatteries, simulate)
}
/**
* Extract [cost] mana from [holder]. If [cost] is less than zero, extract all mana instead.
* This may mutate the stack underlying [holder] (and may consume it) unless [simulate] is set.
*
* If [drainForBatteries] is false, this will only consider forms of mana that can be used to make new batteries.
*
* Return the amount of mana extracted. This may be over [cost] if mana is wasted.
*/
fun extractMana(
holder: ManaHolder,
cost: Int = -1,
drainForBatteries: Boolean = false,
simulate: Boolean = false
): Int {
if (drainForBatteries && !holder.canConstructBattery())
return 0
return manaHolder.withdrawMana(cost, simulate)
return holder.withdrawMana(cost, simulate)
}
/**
* Sorted from least important to most important
*/
fun compareManaItem(astack: ItemStack, bstack: ItemStack): Int {
val aMana = IXplatAbstractions.INSTANCE.findManaHolder(astack)
val bMana = IXplatAbstractions.INSTANCE.findManaHolder(bstack)
fun compareManaItem(aMana: ManaHolder, bMana: ManaHolder): Int {
val priority = aMana.consumptionPriority - bMana.consumptionPriority
if (priority != 0)
return priority
return if (astack.item != bstack.item) {
(aMana?.consumptionPriority ?: 0) - (bMana?.consumptionPriority ?: 0)
} else if (aMana != null && bMana != null) {
aMana.mana - bMana.mana
} else {
astack.count - bstack.count
}
return aMana.withdrawMana(-1, true) - bMana.withdrawMana(-1, true)
}
fun manaBarColor(mana: Int, maxMana: Int): Int {

View file

@ -1,8 +1,8 @@
package at.petrak.hexcasting.client;
import at.petrak.hexcasting.api.client.ScryingLensOverlayRegistry;
import at.petrak.hexcasting.api.misc.DiscoveryHandlers;
import at.petrak.hexcasting.api.player.Sentinel;
import at.petrak.hexcasting.common.items.ItemLens;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import com.google.common.collect.Lists;
import com.mojang.blaze3d.platform.GlStateManager;
@ -155,7 +155,7 @@ public class HexAdditionalRenderers {
return;
}
if (!ItemLens.hasLensHUD(player))
if (!DiscoveryHandlers.hasLens(player))
return;
var hitRes = mc.hitResult;

View file

@ -31,6 +31,7 @@ import com.mojang.datafixers.util.Pair;
import net.minecraft.ChatFormatting;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.TextColor;
@ -40,11 +41,14 @@ import net.minecraft.util.Mth;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.ComparatorMode;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import net.minecraft.world.level.block.state.properties.RailShape;
import net.minecraft.world.level.material.MaterialColor;
import org.jetbrains.annotations.NotNull;
@ -184,12 +188,32 @@ public class RegisterClientStuff {
.withStyle(redstoneColor(compare ? 0 : 15))));
});
ScryingLensOverlayRegistry.addDisplayer(Blocks.POWERED_RAIL,
(lines, state, pos, observer, world, direction) -> {
int power = getPoweredRailStrength(world, pos, state);
lines.add(new Pair<>(
new ItemStack(Items.POWERED_RAIL),
new TextComponent(String.valueOf(power))
.withStyle(redstoneColor(power, 9))));
});
ScryingLensOverlayRegistry.addDisplayer(Blocks.REPEATER,
(lines, state, pos, observer, world, direction) -> lines.add(new Pair<>(
new ItemStack(Items.CLOCK),
new TextComponent(String.valueOf(state.getValue(RepeaterBlock.DELAY)))
.withStyle(ChatFormatting.YELLOW))));
ScryingLensOverlayRegistry.addPredicateDisplayer(
(state, pos, observer, world, direction) -> state.getBlock() instanceof BeehiveBlock,
(lines, state, pos, observer, world, direction) -> {
int count = ScryingLensOverlayRegistry.getBeeValue();
lines.add(new Pair<>(new ItemStack(Items.BEE_NEST), count == -1 ? new TextComponent("") :
new TranslatableComponent(
"hexcasting.tooltip.lens.bee" + (count == 1 ? ".single" : ""),
count
)));
});
ScryingLensOverlayRegistry.addPredicateDisplayer(
(state, pos, observer, world, direction) -> state.isSignalSource() && !state.is(
Blocks.COMPARATOR),
@ -226,7 +250,11 @@ public class RegisterClientStuff {
}
private static UnaryOperator<Style> redstoneColor(int power) {
return color(RedStoneWireBlock.getColorForPower(Mth.clamp(power, 0, 15)));
return redstoneColor(power, 15);
}
private static UnaryOperator<Style> redstoneColor(int power, int max) {
return color(RedStoneWireBlock.getColorForPower(Mth.clamp((power * max) / 15, 0, 15)));
}
private static int instrumentColor(NoteBlockInstrument instrument) {
@ -248,6 +276,114 @@ public class RegisterClientStuff {
};
}
private static int getPoweredRailStrength(Level level, BlockPos pos, BlockState state) {
if (level.hasNeighborSignal(pos))
return 9;
int positiveValue = findPoweredRailSignal(level, pos, state, true, 0);
int negativeValue = findPoweredRailSignal(level, pos, state, false, 0);
return Math.max(positiveValue, negativeValue);
}
// Copypasta from PoweredRailBlock.class
private static int findPoweredRailSignal(Level level, BlockPos pos, BlockState state, boolean travelPositive, int depth) {
if (depth >= 8) {
return 0;
} else {
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
boolean descending = true;
RailShape shape = state.getValue(PoweredRailBlock.SHAPE);
switch(shape) {
case NORTH_SOUTH:
if (travelPositive) {
++z;
} else {
--z;
}
break;
case EAST_WEST:
if (travelPositive) {
--x;
} else {
++x;
}
break;
case ASCENDING_EAST:
if (travelPositive) {
--x;
} else {
++x;
++y;
descending = false;
}
shape = RailShape.EAST_WEST;
break;
case ASCENDING_WEST:
if (travelPositive) {
--x;
++y;
descending = false;
} else {
++x;
}
shape = RailShape.EAST_WEST;
break;
case ASCENDING_NORTH:
if (travelPositive) {
++z;
} else {
--z;
++y;
descending = false;
}
shape = RailShape.NORTH_SOUTH;
break;
case ASCENDING_SOUTH:
if (travelPositive) {
++z;
++y;
descending = false;
} else {
--z;
}
shape = RailShape.NORTH_SOUTH;
}
int power = getPowerFromRail(level, new BlockPos(x, y, z), travelPositive, depth, shape);
if (power > 0) {
return power;
} else if (descending) {
return getPowerFromRail(level, new BlockPos(x, y - 1, z), travelPositive, depth, shape);
} else {
return 0;
}
}
}
private static int getPowerFromRail(Level level, BlockPos pos, boolean travelPositive, int depth, RailShape shape) {
BlockState otherState = level.getBlockState(pos);
if (!otherState.is(Blocks.POWERED_RAIL)) {
return 0;
} else {
RailShape otherShape = otherState.getValue(PoweredRailBlock.SHAPE);
if (shape == RailShape.EAST_WEST && (otherShape == RailShape.NORTH_SOUTH || otherShape == RailShape.ASCENDING_NORTH || otherShape == RailShape.ASCENDING_SOUTH)) {
return 0;
} else if (shape == RailShape.NORTH_SOUTH && (otherShape == RailShape.EAST_WEST || otherShape == RailShape.ASCENDING_EAST || otherShape == RailShape.ASCENDING_WEST)) {
return 0;
} else if (otherState.getValue(PoweredRailBlock.POWERED)) {
return level.hasNeighborSignal(pos) ? 8 - depth : findPoweredRailSignal(level, pos, otherState, travelPositive, depth + 1);
} else {
return 0;
}
}
}
private static void registerScollOverrides(ItemScroll scroll) {
IClientXplatAbstractions.INSTANCE.registerItemProperty(scroll, ItemScroll.ANCIENT_PREDICATE,
(stack, level, holder, holderID) -> NBTHelper.hasString(stack, ItemScroll.TAG_OP_ID) ? 1f : 0f);

View file

@ -125,7 +125,7 @@ public class WallScrollRenderer extends EntityRenderer<EntityWallScroll> {
if (wallScroll.getShowsStrokeOrder()) {
var spotFrac = 0.8f * wallScroll.blockSize;
var animTime = wallScroll.tickCount;
var animTime = wallScroll.tickCount + partialTicks;
var pointCircuit =
(animTime * HexConfig.client().patternPointSpeedMultiplier()) % (points.size() + 10);
if (pointCircuit < points.size() - 1) {

View file

@ -1,5 +1,6 @@
package at.petrak.hexcasting.client.gui
import at.petrak.hexcasting.api.misc.DiscoveryHandlers
import at.petrak.hexcasting.api.mod.HexConfig
import at.petrak.hexcasting.api.mod.HexItemTags
import at.petrak.hexcasting.api.spell.casting.ControllerInfo
@ -10,13 +11,11 @@ import at.petrak.hexcasting.api.spell.math.HexCoord
import at.petrak.hexcasting.api.spell.math.HexDir
import at.petrak.hexcasting.api.spell.math.HexPattern
import at.petrak.hexcasting.api.utils.asTranslatedComponent
import at.petrak.hexcasting.api.utils.otherHand
import at.petrak.hexcasting.client.ShiftScrollListener
import at.petrak.hexcasting.client.drawPatternFromPoints
import at.petrak.hexcasting.client.drawSpot
import at.petrak.hexcasting.client.ktxt.accumulatedScroll
import at.petrak.hexcasting.client.sound.GridSoundInstance
import at.petrak.hexcasting.common.lib.HexItems
import at.petrak.hexcasting.common.lib.HexSounds
import at.petrak.hexcasting.common.network.MsgNewSpellPatternSyn
import at.petrak.hexcasting.xplat.IClientXplatAbstractions
@ -208,10 +207,6 @@ class GuiSpellcasting(
is PatternDrawState.JustStarted -> {
// Well, we never managed to get anything on the stack this go-around.
this.drawState = PatternDrawState.BetweenPatterns
if (this.patterns.isEmpty()) {
Minecraft.getInstance().setScreen(null)
Minecraft.getInstance().soundManager.stop(HexSounds.CASTING_AMBIANCE.location, null)
}
}
is PatternDrawState.Drawing -> {
val (start, _, pat) = this.drawState as PatternDrawState.Drawing
@ -352,13 +347,12 @@ class GuiSpellcasting(
/** Distance between adjacent hex centers */
fun hexSize(): Float {
val hasLens = Minecraft.getInstance().player!!
.getItemInHand(otherHand(this.handOpenedWith)).`is`(HexItems.SCRYING_LENS)
val scaleModifier = DiscoveryHandlers.gridScaleModifier(Minecraft.getInstance().player)
// Originally, we allowed 32 dots across. Assuming a 1920x1080 screen this allowed like 500-odd area.
// Let's be generous and give them 512.
val baseScale = sqrt(this.width.toDouble() * this.height / 512.0)
return baseScale.toFloat() * if (hasLens) 0.75f else 1f
return baseScale.toFloat() * scaleModifier
}
fun coordsOffset(): Vec2 = Vec2(this.width.toFloat() * 0.5f, this.height.toFloat() * 0.5f)

View file

@ -37,10 +37,9 @@ public class BlockAkashicFloodfiller extends Block {
return true;
}
public static @Nullable
BlockPos floodFillFor(BlockPos start, Level world,
TriPredicate<BlockPos, BlockState, Level> isValid, TriPredicate<BlockPos, BlockState, Level> isTarget) {
@Nullable
public static BlockPos floodFillFor(BlockPos start, Level world,
TriPredicate<BlockPos, BlockState, Level> isValid, TriPredicate<BlockPos, BlockState, Level> isTarget, int maxRange) {
var seenBlocks = new HashSet<BlockPos>();
var todo = new ArrayDeque<BlockPos>();
todo.add(start);
@ -50,6 +49,10 @@ public class BlockAkashicFloodfiller extends Block {
for (var dir : Direction.values()) {
var neighbor = here.relative(dir);
if (neighbor.distSqr(start) > maxRange * maxRange)
continue;
if (seenBlocks.add(neighbor)) {
var bs = world.getBlockState(neighbor);
if (isTarget.test(neighbor, bs, world)) {
@ -64,10 +67,10 @@ public class BlockAkashicFloodfiller extends Block {
return null;
}
public static @Nullable
BlockPos floodFillFor(BlockPos start, Level world,
@Nullable
public static BlockPos floodFillFor(BlockPos start, Level world,
TriPredicate<BlockPos, BlockState, Level> isTarget) {
return floodFillFor(start, world, BlockAkashicFloodfiller::canItBeFloodedThrough, isTarget);
return floodFillFor(start, world, BlockAkashicFloodfiller::canItBeFloodedThrough, isTarget, 32);
}
public static boolean canItBeFloodedThrough(BlockPos pos, BlockState state, Level world) {

View file

@ -5,8 +5,6 @@ import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.common.lib.HexBlockEntities;
import com.mojang.authlib.GameProfile;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
@ -17,6 +15,7 @@ import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
@ -79,9 +78,9 @@ public class BlockEntityStoredPlayerImpetus extends BlockEntityAbstractImpetus {
}
public void applyScryingLensOverlay(List<Pair<ItemStack, Component>> lines,
BlockState state, BlockPos pos, LocalPlayer observer,
ClientLevel world,
Direction hitFace) {
BlockState state, BlockPos pos, Player observer,
Level world,
Direction hitFace) {
super.applyScryingLensOverlay(lines, state, pos, observer, world, hitFace);
var name = this.getPlayerName();

View file

@ -38,6 +38,10 @@ import at.petrak.hexcasting.common.casting.operators.stack.*;
import at.petrak.hexcasting.common.lib.HexItems;
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LayeredCauldronBlock;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.Vec3;
import static at.petrak.hexcasting.api.HexAPI.modLoc;
@ -201,10 +205,13 @@ public class RegisterPatterns {
modLoc("colorize"),
OpColorize.INSTANCE);
PatternRegistry.mapPattern(HexPattern.fromAngles("aqawqadaq", HexDir.SOUTH_EAST), modLoc("create_water"),
OpCreateWater.INSTANCE);
new OpCreateFluid(false, ManaConstants.DUST_UNIT,
Items.WATER_BUCKET,
Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, LayeredCauldronBlock.MAX_FILL_LEVEL),
Fluids.WATER));
PatternRegistry.mapPattern(HexPattern.fromAngles("dedwedade", HexDir.SOUTH_WEST),
modLoc("destroy_water"),
OpDestroyWater.INSTANCE);
OpDestroyFluid.INSTANCE);
PatternRegistry.mapPattern(HexPattern.fromAngles("aaqawawa", HexDir.SOUTH_EAST), modLoc("ignite"),
OpIgnite.INSTANCE);
PatternRegistry.mapPattern(HexPattern.fromAngles("ddedwdwd", HexDir.SOUTH_WEST), modLoc("extinguish"),
@ -292,7 +299,10 @@ public class RegisterPatterns {
PatternRegistry.mapPattern(HexPattern.fromAngles("eawwaeawawaa", HexDir.NORTH_WEST),
modLoc("flight"), OpFlight.INSTANCE, true);
PatternRegistry.mapPattern(HexPattern.fromAngles("eaqawqadaqd", HexDir.EAST),
modLoc("create_lava"), OpCreateLava.INSTANCE, true);
modLoc("create_lava"), new OpCreateFluid(true, ManaConstants.CRYSTAL_UNIT,
Items.LAVA_BUCKET,
Blocks.LAVA_CAULDRON.defaultBlockState(),
Fluids.LAVA), true);
PatternRegistry.mapPattern(
HexPattern.fromAngles("wwwqqqwwwqqeqqwwwqqwqqdqqqqqdqq", HexDir.EAST),
modLoc("teleport"), OpTeleport.INSTANCE, true);
@ -490,7 +500,7 @@ public class RegisterPatterns {
if (negate) {
accumulator = -accumulator;
}
return Operator.makeConstantOp(SpellDatum.make(accumulator));
return Operator.makeConstantOp(accumulator, modLoc("number"));
} else {
return null;
}
@ -526,7 +536,7 @@ public class RegisterPatterns {
return null;
}
return new OpMask(mask);
return new OpMask(mask, modLoc("mask"));
});
}
}

View file

@ -1,5 +1,6 @@
package at.petrak.hexcasting.common.casting.operators
import at.petrak.hexcasting.api.addldata.DataHolder
import at.petrak.hexcasting.api.spell.ParticleSpray
import at.petrak.hexcasting.api.spell.RenderedSpell
import at.petrak.hexcasting.api.spell.SpellDatum
@ -35,22 +36,15 @@ object OpWrite : SpellOperator {
throw MishapOthersName(trueName)
return Triple(
Spell(datum),
Spell(datum, datumHolder),
0,
listOf()
)
}
private data class Spell(val datum: SpellDatum<*>) : RenderedSpell {
private data class Spell(val datum: SpellDatum<*>, val datumHolder: DataHolder) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val (handStack) = ctx.getHeldItemToOperateOn {
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(it)
datumHolder != null && datumHolder.writeDatum(datum, true)
}
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(handStack)
datumHolder?.writeDatum(datum, false)
datumHolder.writeDatum(datum, false)
}
}
}

View file

@ -11,7 +11,7 @@ object OpRemove : ConstManaOperator {
val list = args.getChecked<SpellList>(0, argc).toMutableList()
val index = args.getChecked<Double>(1, argc).toInt()
if (index < 0 || index >= list.size)
return list
return list.asSpellResult
list.removeAt(index)
return list.asSpellResult
}

View file

@ -1,10 +1,10 @@
package at.petrak.hexcasting.common.casting.operators.math
import at.petrak.hexcasting.api.spell.ConstManaOperator
import at.petrak.hexcasting.api.spell.numOrVec
import at.petrak.hexcasting.api.spell.SpellDatum
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.mishaps.MishapDivideByZero
import at.petrak.hexcasting.api.spell.numOrVec
import at.petrak.hexcasting.api.spell.spellListOf
import net.minecraft.world.phys.Vec3
import kotlin.math.pow
@ -40,7 +40,7 @@ object OpPowProj : ConstManaOperator {
{ rvec ->
if (lvec == Vec3.ZERO)
throw MishapDivideByZero.of(lvec, rvec, "project")
rvec.scale(rvec.dot(lvec) / lvec.dot(lvec))
lvec.scale(rvec.dot(lvec) / lvec.dot(lvec))
}
)
})

View file

@ -5,9 +5,7 @@ import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.mishaps.MishapImmuneEntity
import at.petrak.hexcasting.api.spell.mishaps.MishapLocationTooFarAway
import at.petrak.hexcasting.common.network.MsgBlinkAck
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.server.level.ServerPlayer
import at.petrak.hexcasting.common.casting.operators.spells.great.OpTeleport
import net.minecraft.world.entity.Entity
import kotlin.math.max
import kotlin.math.roundToInt
@ -47,12 +45,8 @@ object OpBlink : SpellOperator {
private data class Spell(val target: Entity, val delta: Double) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val dvec = target.lookAngle.scale(delta)
target.setPos(target.position().add(dvec))
if (target is ServerPlayer) {
target.connection.resetPosition()
IXplatAbstractions.INSTANCE.sendPacketToPlayer(target, MsgBlinkAck(dvec))
}
val delta = target.lookAngle.scale(delta)
OpTeleport.teleportRespectSticky(target, delta)
}
}
}

View file

@ -15,23 +15,22 @@ object OpBreakBlock : SpellOperator {
override fun execute(
args: List<SpellDatum<*>>,
ctx: CastingContext
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
): Triple<RenderedSpell, Int, List<ParticleSpray>>? {
val pos = args.getChecked<Vec3>(0, argc)
ctx.assertVecInRange(pos)
val centered = Vec3.atCenterOf(BlockPos(pos))
val bpos = BlockPos(pos)
val centered = Vec3.atCenterOf(bpos)
return Triple(
Spell(pos),
Spell(bpos),
(ManaConstants.DUST_UNIT * 1.125).toInt(),
listOf(ParticleSpray.burst(centered, 1.0))
)
}
private data class Spell(val v: Vec3) : RenderedSpell {
private data class Spell(val pos: BlockPos) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val pos = BlockPos(v)
if (!ctx.world.mayInteract(ctx.caster, pos))
if (!ctx.canEditBlockAt(pos))
return
val blockstate = ctx.world.getBlockState(pos)

View file

@ -9,6 +9,7 @@ import at.petrak.hexcasting.api.spell.SpellOperator
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.mishaps.MishapBadOffhandItem
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.item.ItemStack
object OpColorize : SpellOperator {
override val argc = 0
@ -26,22 +27,19 @@ object OpColorize : SpellOperator {
)
}
return Triple(
Spell,
Spell(handStack),
ManaConstants.DUST_UNIT,
listOf()
)
}
private object Spell : RenderedSpell {
private data class Spell(val stack: ItemStack) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val handStack = ctx.getHeldItemToOperateOn(IXplatAbstractions.INSTANCE::isColorizer).first.copy()
if (IXplatAbstractions.INSTANCE.isColorizer(handStack)) {
if (ctx.withdrawItem(handStack.item, 1, true)) {
IXplatAbstractions.INSTANCE.setColorizer(
ctx.caster,
FrozenColorizer(handStack, ctx.caster.uuid)
)
}
if (ctx.withdrawItem(stack, 1, true)) {
IXplatAbstractions.INSTANCE.setColorizer(
ctx.caster,
FrozenColorizer(stack, ctx.caster.uuid)
)
}
}
}

View file

@ -44,7 +44,7 @@ class OpConjure(val light: Boolean) : SpellOperator {
override fun cast(ctx: CastingContext) {
val pos = BlockPos(target)
if (!ctx.world.mayInteract(ctx.caster, pos))
if (!ctx.canEditBlockAt(pos))
return
val placeContext = DirectionalPlaceContext(ctx.world, pos, Direction.DOWN, ItemStack.EMPTY, Direction.UP)

View file

@ -0,0 +1,59 @@
package at.petrak.hexcasting.common.casting.operators.spells
import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
import net.minecraft.world.item.BucketItem
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.material.Fluid
import net.minecraft.world.phys.Vec3
class OpCreateFluid(override val isGreat: Boolean, val cost: Int, val bucket: Item, val cauldron: BlockState, val fluid: Fluid) : SpellOperator {
override val argc = 1
override fun execute(
args: List<SpellDatum<*>>,
ctx: CastingContext
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
val target = args.getChecked<Vec3>(0, argc)
ctx.assertVecInRange(target)
return Triple(
Spell(target, bucket, cauldron, fluid),
cost,
listOf(ParticleSpray.burst(Vec3.atCenterOf(BlockPos(target)), 1.0))
)
}
private data class Spell(val target: Vec3, val bucket: Item, val cauldron: BlockState, val fluid: Fluid) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val pos = BlockPos(target)
if (!ctx.canEditBlockAt(pos) || !IXplatAbstractions.INSTANCE.isPlacingAllowed(
ctx.world,
pos,
ItemStack(bucket),
ctx.caster
)
)
return
val state = ctx.world.getBlockState(pos)
if (state.block == Blocks.CAULDRON)
ctx.world.setBlock(pos, cauldron, 3)
else if (!IXplatAbstractions.INSTANCE.tryPlaceFluid(
ctx.world,
ctx.castingHand,
pos,
fluid
) && bucket is BucketItem) {
// make the player null so we don't give them a usage statistic for example
bucket.emptyContents(null, ctx.world, pos, null)
}
}
}
}

View file

@ -1,67 +0,0 @@
package at.petrak.hexcasting.common.casting.operators.spells
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.misc.ManaConstants
import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
import net.minecraft.world.item.BucketItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.AbstractCauldronBlock
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.LayeredCauldronBlock
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.Vec3
object OpCreateWater : SpellOperator {
override val argc = 1
override fun execute(
args: List<SpellDatum<*>>,
ctx: CastingContext
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
val target = args.getChecked<Vec3>(0, argc)
ctx.assertVecInRange(target)
return Triple(
Spell(target),
ManaConstants.DUST_UNIT,
listOf(ParticleSpray.burst(Vec3.atCenterOf(BlockPos(target)), 1.0))
)
}
private data class Spell(val target: Vec3) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val pos = BlockPos(target)
if (!ctx.world.mayInteract(ctx.caster, pos)|| !IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, ItemStack(Items.WATER_BUCKET), ctx.caster))
return
val state = ctx.world.getBlockState(pos)
if (state.block is AbstractCauldronBlock)
ctx.world.setBlock(
pos,
Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3),
3
)
else if (!IXplatAbstractions.INSTANCE.tryPlaceFluid(
ctx.world,
ctx.castingHand,
pos,
ItemStack(Items.WATER_BUCKET),
Fluids.WATER
)
) {
// Just steal bucket code lmao
val charlie = Items.WATER_BUCKET
if (charlie is BucketItem) {
// make the player null so we don't give them a usage statistic for example
charlie.emptyContents(null, ctx.world, pos, null)
} else {
HexAPI.LOGGER.warn("Items.WATER_BUCKET wasn't a BucketItem?")
}
}
}
}
}

View file

@ -9,16 +9,13 @@ import net.minecraft.core.Direction
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.BucketPickup
import net.minecraft.world.level.block.LiquidBlock
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.material.Material
import net.minecraft.world.phys.Vec3
object OpDestroyWater : SpellOperator {
object OpDestroyFluid : SpellOperator {
override val argc = 1
override fun execute(
args: List<SpellDatum<*>>,
@ -39,10 +36,24 @@ object OpDestroyWater : SpellOperator {
private data class Spell(val target: Vec3) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val basePos = BlockPos(target)
// Try draining from fluid handlers first, and if so, don't do the normal behavior
if (ctx.canEditBlockAt(basePos)) {
if (IXplatAbstractions.INSTANCE.drainAllFluid(ctx.world, basePos)) {
return
} else {
val state = ctx.world.getBlockState(basePos)
if (state.block is AbstractCauldronBlock && state.block != Blocks.CAULDRON) {
ctx.world.setBlock(basePos, Blocks.CAULDRON.defaultBlockState(), 3)
return
}
}
}
// SpongeBlock.java
val todo = ArrayDeque<BlockPos>()
val seen = HashSet<BlockPos>()
val basePos = BlockPos(target)
// a little extra range on the initial cast to make it feel more intuitive
for (xShift in -2..2) for (yShift in -2..2) for (zShift in -2..2) {
@ -52,9 +63,7 @@ object OpDestroyWater : SpellOperator {
var successes = 0
while (todo.isNotEmpty() && successes <= MAX_DESTROY_COUNT) {
val here = todo.removeFirst()
val distFromFocus =
ctx.caster.position().distanceToSqr(Vec3.atCenterOf(here))
if (distFromFocus < Operator.MAX_DISTANCE * Operator.MAX_DISTANCE && seen.add(here) && ctx.world.mayInteract(ctx.caster, here)) {
if (ctx.canEditBlockAt(here) && seen.add(here)) {
// never seen this pos in my life
val fluid = ctx.world.getFluidState(here)
if (fluid != Fluids.EMPTY.defaultFluidState()) {
@ -88,25 +97,25 @@ object OpDestroyWater : SpellOperator {
false
}
if (success) {
ctx.world.sendParticles(
ParticleTypes.SMOKE,
here.x + 0.5 + Math.random() * 0.4 - 0.2,
here.y + 0.5 + Math.random() * 0.4 - 0.2,
here.z + 0.5 + Math.random() * 0.4 - 0.2,
2,
0.0,
0.05,
0.0,
0.0
)
successes++
for (dir in Direction.values()) {
todo.add(here.relative(dir))
if (success) {
ctx.world.sendParticles(
ParticleTypes.SMOKE,
here.x + 0.5 + Math.random() * 0.4 - 0.2,
here.y + 0.5 + Math.random() * 0.4 - 0.2,
here.z + 0.5 + Math.random() * 0.4 - 0.2,
2,
0.0,
0.05,
0.0,
0.0
)
successes++
for (dir in Direction.values()) {
todo.add(here.relative(dir))
}
}
}
}
}
}
}

View file

@ -35,8 +35,9 @@ object OpEdifySapling : SpellOperator {
private data class Spell(val pos: BlockPos) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val blockstate = ctx.world.getBlockState(pos)
if (!ctx.world.mayInteract(ctx.caster, pos) ||
!IXplatAbstractions.INSTANCE.isBreakingAllowed(ctx.world, pos, blockstate, ctx.caster))
if (!ctx.canEditBlockAt(pos) ||
!IXplatAbstractions.INSTANCE.isBreakingAllowed(ctx.world, pos, blockstate, ctx.caster)
)
return
val bs = ctx.world.getBlockState(pos)

View file

@ -8,6 +8,7 @@ import at.petrak.hexcasting.api.spell.SpellOperator
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.mishaps.MishapBadOffhandItem
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.item.ItemStack
class OpErase : SpellOperator {
override val argc = 0
@ -33,22 +34,15 @@ class OpErase : SpellOperator {
}
return Triple(
Spell,
Spell(handStack),
ManaConstants.DUST_UNIT, listOf()
)
}
private object Spell : RenderedSpell {
private data class Spell(val stack: ItemStack) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val (handStack) = ctx.getHeldItemToOperateOn {
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(it)
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(it)
(hexHolder?.hasHex() == true) ||
(datumHolder?.writeDatum(null, true) == true)
}
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(handStack)
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(handStack)
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(stack)
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(stack)
if (hexHolder?.hasHex() == true)
hexHolder.clearHex()

View file

@ -20,7 +20,7 @@ class OpExplode(val fire: Boolean) : SpellOperator {
val strength = args.getChecked<Double>(1, argc)
ctx.assertVecInRange(pos)
val clampedStrength = Mth.clamp(strength, 0.0, 10.0)
val cost = ManaConstants.DUST_UNIT * (3 * clampedStrength + if (fire) 0.125 else 1.0)
val cost = ManaConstants.DUST_UNIT * (3 * clampedStrength + if (fire) 1.0 else 0.125)
return Triple(
Spell(pos, clampedStrength, this.fire),
cost.toInt(),
@ -30,7 +30,8 @@ class OpExplode(val fire: Boolean) : SpellOperator {
private data class Spell(val pos: Vec3, val strength: Double, val fire: Boolean) : RenderedSpell {
override fun cast(ctx: CastingContext) {
if (!ctx.world.mayInteract(ctx.caster, BlockPos(pos)))
// TODO: you can use this to explode things *outside* of the worldborder?
if (!ctx.canEditBlockAt(BlockPos(pos)))
return
ctx.world.explode(

View file

@ -50,11 +50,7 @@ object OpExtinguish : SpellOperator {
ctx.caster.position().distanceToSqr(Vec3.atCenterOf(here))
val distFromTarget =
target.distanceTo(Vec3.atCenterOf(here)) // max distance to prevent runaway shenanigans
if (distFromFocus < Operator.MAX_DISTANCE * Operator.MAX_DISTANCE && seen.add(here) && distFromTarget < 10 && ctx.world.mayInteract(
ctx.caster,
here
)
) {
if (ctx.canEditBlockAt(here) && distFromTarget < 10 && seen.add(here)) {
// never seen this pos in my life
val blockstate = ctx.world.getBlockState(here)
if (IXplatAbstractions.INSTANCE.isBreakingAllowed(ctx.world, here, blockstate, ctx.caster)) {

View file

@ -1,6 +1,5 @@
package at.petrak.hexcasting.common.casting.operators.spells
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.misc.ManaConstants
import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
@ -9,7 +8,7 @@ import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.InteractionHand
import net.minecraft.world.item.FireChargeItem
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.phys.BlockHitResult
@ -35,26 +34,26 @@ object OpIgnite : SpellOperator {
override fun cast(ctx: CastingContext) {
val pos = BlockPos(target)
// steal petra code that steals bucket code
val maxwell = Items.FIRE_CHARGE
if (!ctx.world.mayInteract(ctx.caster, pos) || !IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, ItemStack(maxwell), ctx.caster))
if (!ctx.canEditBlockAt(pos))
return
if (maxwell is FireChargeItem) {
// help
maxwell.useOn(
UseOnContext(
ctx.world,
null,
InteractionHand.MAIN_HAND,
ItemStack(maxwell),
BlockHitResult(target, Direction.UP, pos, false)
)
)
} else {
HexAPI.LOGGER.warn("Items.FIRE_CHARGE wasn't a FireChargeItem?")
// help
if (!tryToClick(ctx, pos, Items.FIRE_CHARGE)) {
tryToClick(ctx, pos, Items.FLINT_AND_STEEL)
}
}
fun tryToClick(ctx: CastingContext, pos: BlockPos, item: Item): Boolean {
return IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, ItemStack(item), ctx.caster) &&
item.useOn(
UseOnContext(
ctx.world,
null,
InteractionHand.MAIN_HAND,
ItemStack(item),
BlockHitResult(target, Direction.UP, pos, false)
)
).consumesAction()
}
}
}

View file

@ -2,11 +2,7 @@ package at.petrak.hexcasting.common.casting.operators.spells
import at.petrak.hexcasting.api.misc.ManaConstants
import at.petrak.hexcasting.api.mod.HexItemTags
import at.petrak.hexcasting.api.spell.getChecked
import at.petrak.hexcasting.api.spell.ParticleSpray
import at.petrak.hexcasting.api.spell.RenderedSpell
import at.petrak.hexcasting.api.spell.SpellDatum
import at.petrak.hexcasting.api.spell.SpellOperator
import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.mishaps.MishapBadItem
import at.petrak.hexcasting.api.spell.mishaps.MishapBadOffhandItem
@ -14,6 +10,7 @@ import at.petrak.hexcasting.api.utils.extractMana
import at.petrak.hexcasting.api.utils.isManaItem
import at.petrak.hexcasting.common.items.magic.ItemManaHolder
import at.petrak.hexcasting.common.lib.HexItems
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
@ -59,14 +56,13 @@ object OpMakeBattery : SpellOperator {
)
}
return Triple(Spell(entity),
return Triple(Spell(entity, hand),
ManaConstants.CRYSTAL_UNIT, listOf(ParticleSpray.burst(entity.position(), 0.5)))
}
private data class Spell(val itemEntity: ItemEntity) : RenderedSpell {
private data class Spell(val itemEntity: ItemEntity, val hand: InteractionHand) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val (handStack, hand) = ctx.getHeldItemToOperateOn { it.`is`(HexItemTags.PHIAL_BASE) }
if (handStack.`is`(HexItemTags.PHIAL_BASE) && itemEntity.isAlive) {
if (itemEntity.isAlive) {
val entityStack = itemEntity.item.copy()
val manaAmt = extractMana(entityStack, drainForBatteries = true)
if (manaAmt > 0) {

View file

@ -1,11 +1,6 @@
package at.petrak.hexcasting.common.casting.operators.spells
import at.petrak.hexcasting.api.spell.getChecked
import at.petrak.hexcasting.api.spell.ParticleSpray
import at.petrak.hexcasting.api.spell.RenderedSpell
import at.petrak.hexcasting.api.spell.SpellDatum
import at.petrak.hexcasting.api.spell.SpellList
import at.petrak.hexcasting.api.spell.SpellOperator
import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.mishaps.MishapBadItem
import at.petrak.hexcasting.api.spell.mishaps.MishapBadOffhandItem
@ -15,6 +10,7 @@ import at.petrak.hexcasting.api.utils.isManaItem
import at.petrak.hexcasting.common.items.magic.ItemPackagedHex
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
class OpMakePackagedSpell<T : ItemPackagedHex>(val itemType: T, val cost: Int) : SpellOperator {
override val argc = 2
@ -53,13 +49,12 @@ class OpMakePackagedSpell<T : ItemPackagedHex>(val itemType: T, val cost: Int) :
if (trueName != null)
throw MishapOthersName(trueName)
return Triple(Spell(entity, patterns), cost, listOf(ParticleSpray.burst(entity.position(), 0.5)))
return Triple(Spell(entity, patterns, handStack), cost, listOf(ParticleSpray.burst(entity.position(), 0.5)))
}
private inner class Spell(val itemEntity: ItemEntity, val patterns: List<SpellDatum<*>>) : RenderedSpell {
private inner class Spell(val itemEntity: ItemEntity, val patterns: List<SpellDatum<*>>, val stack: ItemStack) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val (handStack) = ctx.getHeldItemToOperateOn { it.`is`(itemType) }
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(handStack)
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(stack)
if (hexHolder != null
&& !hexHolder.hasHex()
&& itemEntity.isAlive

View file

@ -29,12 +29,8 @@ object OpPlaceBlock : SpellOperator {
val pos = BlockPos(target)
if (!ctx.world.mayInteract(ctx.caster, pos))
return null
val blockHit = BlockHitResult(
Vec3.ZERO, ctx.caster.direction, pos, false
target, ctx.caster.direction, pos, false
)
val itemUseCtx = UseOnContext(ctx.caster, ctx.castingHand, blockHit)
val placeContext = BlockPlaceContext(itemUseCtx)
@ -54,17 +50,16 @@ object OpPlaceBlock : SpellOperator {
override fun cast(ctx: CastingContext) {
val pos = BlockPos(vec)
if (!ctx.world.mayInteract(ctx.caster, pos))
if (!ctx.canEditBlockAt(pos))
return
val blockHit = BlockHitResult(
Vec3.ZERO, ctx.caster.direction, pos, false
vec, ctx.caster.direction, pos, false
)
val bstate = ctx.world.getBlockState(pos)
val placeeSlot = ctx.getOperativeSlot { it.item is BlockItem }
if (placeeSlot != null) {
val placeeStack = ctx.caster.inventory.getItem(placeeSlot).copy()
val placeeStack = ctx.getOperativeSlot { it.item is BlockItem }?.copy()
if (placeeStack != null) {
if (!IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, placeeStack, ctx.caster))
return
@ -80,13 +75,12 @@ object OpPlaceBlock : SpellOperator {
val itemUseCtx = UseOnContext(ctx.caster, ctx.castingHand, blockHit)
val placeContext = BlockPlaceContext(itemUseCtx)
if (bstate.canBeReplaced(placeContext)) {
val placee = placeeStack.item as BlockItem
if (ctx.withdrawItem(placee, 1, false)) {
if (ctx.withdrawItem(placeeStack, 1, false)) {
val res = spoofedStack.useOn(placeContext)
ctx.caster.setItemInHand(ctx.castingHand, oldStack)
if (res != InteractionResult.FAIL) {
ctx.withdrawItem(placee, 1, true)
ctx.withdrawItem(placeeStack, 1, true)
ctx.world.playSound(
ctx.caster,

View file

@ -26,11 +26,15 @@ class OpPotionEffect(
val target = args.getChecked<LivingEntity>(0, argc)
if (target is ArmorStand)
throw MishapInvalidIota.ofClass(SpellDatum.make(target), 0, LivingEntity::class.java)
val duration = max(args.getChecked(1, argc), 0.0)
ctx.assertEntityInRange(target)
val potency = if (this.allowPotency)
args.getChecked<Double>(2, argc).coerceIn(1.0, 128.0)
else 1.0
else
1.0
ctx.assertEntityInRange(target)
val cost = this.baseCost * duration * if (potencyCubic) {
potency * potency * potency
@ -47,8 +51,10 @@ class OpPotionEffect(
private class Spell(val effect: MobEffect, val target: LivingEntity, val duration: Double, val potency: Double) :
RenderedSpell {
override fun cast(ctx: CastingContext) {
val effectInst = MobEffectInstance(effect, (duration * 20).toInt(), potency.toInt() - 1)
target.addEffect(effectInst)
if (duration > 1.0 / 20.0) {
val effectInst = MobEffectInstance(effect, (duration * 20).toInt(), potency.toInt() - 1)
target.addEffect(effectInst)
}
}
}
}

View file

@ -9,6 +9,7 @@ import at.petrak.hexcasting.api.utils.extractMana
import at.petrak.hexcasting.api.utils.isManaItem
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
object OpRecharge : SpellOperator {
override val argc = 1
@ -18,7 +19,7 @@ object OpRecharge : SpellOperator {
): Triple<RenderedSpell, Int, List<ParticleSpray>>? {
val (handStack, hand) = ctx.getHeldItemToOperateOn {
val mana = IXplatAbstractions.INSTANCE.findManaHolder(it)
mana != null && mana.canRecharge() && mana.mana /* doo doo da do doo */ < mana.maxMana
mana != null && mana.canRecharge() && mana.insertMana(-1, true) != 0
}
val mana = IXplatAbstractions.INSTANCE.findManaHolder(handStack)
@ -40,33 +41,28 @@ object OpRecharge : SpellOperator {
)
}
if (mana.mana >= mana.maxMana)
if (mana.insertMana(-1, true) == 0)
return null
return Triple(
Spell(entity),
Spell(entity, handStack),
ManaConstants.SHARD_UNIT,
listOf(ParticleSpray.burst(entity.position(), 0.5))
)
}
private data class Spell(val itemEntity: ItemEntity) : RenderedSpell {
private data class Spell(val itemEntity: ItemEntity, val stack: ItemStack) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val (handStack) = ctx.getHeldItemToOperateOn {
val mana = IXplatAbstractions.INSTANCE.findManaHolder(it)
mana != null && mana.canRecharge() && mana.mana < mana.maxMana
}
val mana = IXplatAbstractions.INSTANCE.findManaHolder(handStack)
val mana = IXplatAbstractions.INSTANCE.findManaHolder(stack)
if (mana != null && itemEntity.isAlive) {
val entityStack = itemEntity.item.copy()
val maxMana = mana.maxMana
val existingMana = mana.mana
val emptySpace = mana.insertMana(-1, true)
val manaAmt = extractMana(entityStack, maxMana - existingMana)
val manaAmt = extractMana(entityStack, emptySpace)
mana.mana = manaAmt + existingMana
mana.insertMana(manaAmt, false)
itemEntity.item = entityStack
if (entityStack.isEmpty)

View file

@ -25,7 +25,7 @@ object OpBrainsweep : SpellOperator {
override fun execute(
args: List<SpellDatum<*>>,
ctx: CastingContext
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
): Triple<RenderedSpell, Int, List<ParticleSpray>>? {
val sacrifice = args.getChecked<Villager>(0, argc)
val pos = args.getChecked<Vec3>(1, argc)
ctx.assertVecInRange(pos)
@ -35,6 +35,10 @@ object OpBrainsweep : SpellOperator {
throw MishapAlreadyBrainswept(sacrifice)
val bpos = BlockPos(pos)
if (!ctx.canEditBlockAt(bpos))
return null
val state = ctx.world.getBlockState(bpos)
val recman = ctx.world.recipeManager
@ -49,9 +53,15 @@ object OpBrainsweep : SpellOperator {
)
}
private data class Spell(val pos: BlockPos, val state: BlockState, val sacrifice: Villager, val recipe: BrainsweepRecipe) : RenderedSpell {
private data class Spell(
val pos: BlockPos,
val state: BlockState,
val sacrifice: Villager,
val recipe: BrainsweepRecipe
) : RenderedSpell {
override fun cast(ctx: CastingContext) {
ctx.world.setBlockAndUpdate(pos, BrainsweepRecipe.copyProperties(state, recipe.result))
Brainsweeping.brainsweep(sacrifice)
if (HexConfig.server().doVillagersTakeOffenseAtMindMurder()) {
sacrifice.tellWitnessesThatIWasMurdered(ctx.caster)

View file

@ -1,64 +0,0 @@
package at.petrak.hexcasting.common.casting.operators.spells.great
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.misc.ManaConstants
import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
import net.minecraft.world.item.BucketItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.AbstractCauldronBlock
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.Vec3
object OpCreateLava : SpellOperator {
override val argc = 1
override val isGreat = true
override fun execute(
args: List<SpellDatum<*>>,
ctx: CastingContext
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
val target = args.getChecked<Vec3>(0, argc)
ctx.assertVecInRange(target)
return Triple(
Spell(target),
ManaConstants.CRYSTAL_UNIT,
listOf(ParticleSpray.burst(Vec3.atCenterOf(BlockPos(target)), 1.0)),
)
}
private data class Spell(val target: Vec3) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val pos = BlockPos(target)
if (!ctx.world.mayInteract(ctx.caster, pos)|| !IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, ItemStack(Items.LAVA_BUCKET), ctx.caster))
return
val state = ctx.world.getBlockState(pos)
if (state.block is AbstractCauldronBlock)
ctx.world.setBlock(pos, Blocks.LAVA_CAULDRON.defaultBlockState(), 3)
else if (!IXplatAbstractions.INSTANCE.tryPlaceFluid(
ctx.world,
ctx.castingHand,
pos,
ItemStack(Items.LAVA_BUCKET),
Fluids.LAVA
)
) {
// Just steal bucket code lmao
val charlie = Items.LAVA_BUCKET
if (charlie is BucketItem) {
// make the player null so we don't give them a usage statistic for example
charlie.emptyContents(null, ctx.world, pos, null)
} else {
HexAPI.LOGGER.warn("Items.LAVA_BUCKET wasn't a BucketItem?")
}
}
}
}
}

View file

@ -5,10 +5,12 @@ import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.mishaps.MishapImmuneEntity
import at.petrak.hexcasting.api.spell.mishaps.MishapLocationTooFarAway
import at.petrak.hexcasting.common.lib.HexEntityTags
import at.petrak.hexcasting.common.network.MsgBlinkAck
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.phys.Vec3
object OpTeleport : SpellOperator {
@ -43,12 +45,8 @@ object OpTeleport : SpellOperator {
private data class Spell(val teleportee: Entity, val delta: Vec3) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val distance = delta.length()
if (distance < 32768.0) {
teleportee.setPos(teleportee.position().add(delta))
if (teleportee is ServerPlayer) {
IXplatAbstractions.INSTANCE.sendPacketToPlayer(teleportee, MsgBlinkAck(delta))
}
teleportRespectSticky(teleportee, delta)
}
if (teleportee is ServerPlayer && teleportee == ctx.caster) {
@ -61,6 +59,9 @@ object OpTeleport : SpellOperator {
// having to rearrange those. Also it makes sense for LORE REASONS probably, since the caster is more
// aware of items they use often.
for (armorItem in teleportee.inventory.armor) {
if (EnchantmentHelper.hasBindingCurse(armorItem))
continue
if (Math.random() < baseDropChance * 0.25) {
teleportee.drop(armorItem.copy(), true, false)
armorItem.shrink(armorItem.count)
@ -79,6 +80,34 @@ object OpTeleport : SpellOperator {
// we also don't drop the offhand just to be nice
}
}
}
fun teleportRespectSticky(teleportee: Entity, delta: Vec3) {
val base = teleportee.rootVehicle
val playersToUpdate = mutableListOf<ServerPlayer>()
if (base.indirectPassengers.any { it.type.`is`(HexEntityTags.STICKY_TELEPORTERS) }) {
// this handles teleporting the passengers
val target = base.position().add(delta)
base.teleportTo(target.x, target.y, target.z)
base.indirectPassengers
.filterIsInstance<ServerPlayer>()
.forEach(playersToUpdate::add)
} else {
// Break it into two stacks
teleportee.stopRiding()
teleportee.passengers.forEach(Entity::stopRiding)
teleportee.setPos(teleportee.position().add(delta))
if (teleportee is ServerPlayer) {
playersToUpdate.add(teleportee)
}
}
for (player in playersToUpdate) {
player.connection.resetPosition()
IXplatAbstractions.INSTANCE.sendPacketToPlayer(player, MsgBlinkAck(delta))
}
}
}

View file

@ -30,6 +30,12 @@ class OpWeather(val rain: Boolean) : SpellOperator {
val w = ctx.world
if (w.isRaining != rain) {
w.levelData.isRaining = rain // i hex the rains down in minecraftia
if (rain) {
w.setWeatherParameters(0, 6000, true, w.random.nextDouble() < 0.05)
} else {
w.setWeatherParameters(6000, 0, false, false)
}
}
}
}

View file

@ -25,7 +25,7 @@ class OpCreateSentinel(val extendsRange: Boolean) : SpellOperator {
return Triple(
Spell(target, this.extendsRange),
ManaConstants.DUST_UNIT,
ManaConstants.DUST_UNIT * if (extendsRange) 2 else 1,
listOf(ParticleSpray.burst(target, 2.0))
)
}

View file

@ -1,11 +1,9 @@
package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.misc.ManaConstants
import at.petrak.hexcasting.api.spell.OperationResult
import at.petrak.hexcasting.api.spell.Operator
import at.petrak.hexcasting.api.spell.SpellDatum
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.OperatorSideEffect
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.spell.getChecked
import at.petrak.hexcasting.api.spell.mishaps.MishapInvalidIota
@ -13,7 +11,6 @@ import at.petrak.hexcasting.api.spell.mishaps.MishapNotEnoughArgs
import at.petrak.hexcasting.api.utils.asTranslatedComponent
import it.unimi.dsi.fastutil.ints.IntArrayList
import kotlin.math.abs
import kotlin.math.ln
import kotlin.math.roundToInt
// "lehmer code"
@ -58,13 +55,13 @@ object OpAlwinfyHasAscendedToABeingOfPureMath : Operator {
editTarget = editTarget.subList(1, editTarget.size)
}
val cost = (ln((strides.lastOrNull() ?: 0).toFloat()) * ManaConstants.DUST_UNIT).toInt()
// val cost = (ln((strides.lastOrNull() ?: 0).toFloat()) * ManaConstants.DUST_UNIT).toInt()
return OperationResult(
continuation,
stack,
local,
listOf(OperatorSideEffect.ConsumeMana(cost))
listOf() // OperatorSideEffect.ConsumeMana(cost)
)
}

View file

@ -3,9 +3,13 @@ package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.spell.ConstManaOperator
import at.petrak.hexcasting.api.spell.SpellDatum
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.utils.asTranslatedComponent
import at.petrak.hexcasting.api.utils.lightPurple
import it.unimi.dsi.fastutil.booleans.BooleanList
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
class OpMask(val mask: BooleanList) : ConstManaOperator {
class OpMask(val mask: BooleanList, val key: ResourceLocation) : ConstManaOperator {
override val argc: Int
get() = mask.size
@ -17,4 +21,7 @@ class OpMask(val mask: BooleanList) : ConstManaOperator {
}
return out
}
override val displayName: Component
get() = "hexcasting.spell.$key".asTranslatedComponent(mask.map { if (it) '-' else 'v' }.joinToString("")).lightPurple
}

View file

@ -1,6 +1,7 @@
package at.petrak.hexcasting.common.items;
import at.petrak.hexcasting.annotations.SoftImplement;
import at.petrak.hexcasting.api.misc.DiscoveryHandlers;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.network.MsgUpdateComparatorVisualsAck;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
@ -14,45 +15,32 @@ import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Wearable;
import net.minecraft.world.level.block.BeehiveBlock;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.DispenserBlock;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.Predicate;
public class ItemLens extends Item implements Wearable {
private static final List<Predicate<Player>> HAS_HUD_PREDICATE = new ArrayList<>();
static {
addLensHUDPredicate(player -> player.getItemBySlot(EquipmentSlot.MAINHAND).is(HexItems.SCRYING_LENS));
addLensHUDPredicate(player -> player.getItemBySlot(EquipmentSlot.OFFHAND).is(HexItems.SCRYING_LENS));
addLensHUDPredicate(player -> player.getItemBySlot(EquipmentSlot.HEAD).is(HexItems.SCRYING_LENS));
}
DiscoveryHandlers.addLensPredicate(player -> player.getItemBySlot(EquipmentSlot.MAINHAND).is(HexItems.SCRYING_LENS));
DiscoveryHandlers.addLensPredicate(player -> player.getItemBySlot(EquipmentSlot.OFFHAND).is(HexItems.SCRYING_LENS));
DiscoveryHandlers.addLensPredicate(player -> player.getItemBySlot(EquipmentSlot.HEAD).is(HexItems.SCRYING_LENS));
public static boolean hasLensHUD(Player player) {
for (Predicate<Player> predicate : HAS_HUD_PREDICATE) {
if (predicate.test(player)) {
return true;
}
}
return false;
}
public static void addLensHUDPredicate(Predicate<Player> predicate) {
HAS_HUD_PREDICATE.add(predicate);
DiscoveryHandlers.addGridScaleModifier(player -> player.getItemBySlot(EquipmentSlot.MAINHAND).is(HexItems.SCRYING_LENS) ? 0.75f : 1);
DiscoveryHandlers.addGridScaleModifier(player -> player.getItemBySlot(EquipmentSlot.OFFHAND).is(HexItems.SCRYING_LENS) ? 0.75f : 1);
}
public ItemLens(Properties pProperties) {
@ -80,12 +68,13 @@ public class ItemLens extends Item implements Wearable {
}
public static void tickLens(Entity pEntity) {
if (!pEntity.getLevel().isClientSide() && pEntity instanceof ServerPlayer player && hasLensHUD(player)) {
if (!pEntity.getLevel().isClientSide() && pEntity instanceof ServerPlayer player && DiscoveryHandlers.hasLens(player)) {
sendComparatorDataToClient(player);
}
}
private static final Map<ServerPlayer, Pair<BlockPos, Integer>> comparatorDataMap = new WeakHashMap<>();
private static final Map<ServerPlayer, Pair<BlockPos, Integer>> beeDataMap = new WeakHashMap<>();
private static void sendComparatorDataToClient(ServerPlayer player) {
double reachAttribute = IXplatAbstractions.INSTANCE.getReachDistance(player);
@ -94,29 +83,40 @@ public class ItemLens extends Item implements Wearable {
if (hitResult.getType() == HitResult.Type.BLOCK) {
var pos = ((BlockHitResult) hitResult).getBlockPos();
var state = player.level.getBlockState(pos);
int bee = -1;
if (state.getBlock() instanceof BeehiveBlock && player.level.getBlockEntity(pos) instanceof BeehiveBlockEntity bees) {
bee = bees.getOccupantCount();
}
if (state.is(Blocks.COMPARATOR)) {
syncComparatorValue(player, pos,
state.getDirectSignal(player.level, pos, state.getValue(BlockStateProperties.HORIZONTAL_FACING)));
state.getDirectSignal(player.level, pos, state.getValue(BlockStateProperties.HORIZONTAL_FACING)), bee);
} else if (state.hasAnalogOutputSignal()) {
syncComparatorValue(player, pos, state.getAnalogOutputSignal(player.level, pos));
syncComparatorValue(player, pos, state.getAnalogOutputSignal(player.level, pos), bee);
} else {
syncComparatorValue(player, null, -1);
syncComparatorValue(player, null, -1, bee);
}
} else {
syncComparatorValue(player, null, -1);
syncComparatorValue(player, null, -1, -1);
}
}
private static void syncComparatorValue(ServerPlayer player, BlockPos pos, int value) {
var previous = comparatorDataMap.get(player);
if (value == -1) {
if (previous != null) {
private static void syncComparatorValue(ServerPlayer player, BlockPos pos, int comparator, int bee) {
var previousComparator = comparatorDataMap.get(player);
var previousBee = beeDataMap.get(player);
if (comparator == -1 && bee == -1) {
if (previousComparator != null || previousBee != null) {
comparatorDataMap.remove(player);
IXplatAbstractions.INSTANCE.sendPacketToPlayer(player, new MsgUpdateComparatorVisualsAck(null, -1));
beeDataMap.remove(player);
IXplatAbstractions.INSTANCE.sendPacketToPlayer(player, new MsgUpdateComparatorVisualsAck(null, -1, -1));
}
} else if (previous == null || (!pos.equals(previous.getFirst()) || value != previous.getSecond())) {
comparatorDataMap.put(player, new Pair<>(pos, value));
IXplatAbstractions.INSTANCE.sendPacketToPlayer(player, new MsgUpdateComparatorVisualsAck(pos, value));
} else if (previousComparator == null || !pos.equals(previousComparator.getFirst()) || comparator != previousComparator.getSecond() ||
previousBee == null || !pos.equals(previousBee.getFirst()) || bee != previousBee.getSecond()) {
comparatorDataMap.put(player, new Pair<>(pos, comparator));
beeDataMap.put(player, new Pair<>(pos, bee));
IXplatAbstractions.INSTANCE.sendPacketToPlayer(player, new MsgUpdateComparatorVisualsAck(pos, comparator, bee));
}
}

View file

@ -0,0 +1,55 @@
package at.petrak.hexcasting.common.items.magic;
import at.petrak.hexcasting.api.addldata.ManaHolder;
import net.minecraft.world.item.ItemStack;
public record DebugUnlockerHolder(ItemStack creativeUnlocker) implements ManaHolder {
@Override
public int getMana() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxMana() {
return Integer.MAX_VALUE - 1;
}
@Override
public void setMana(int mana) {
// NO-OP
}
@Override
public boolean canRecharge() {
return true;
}
@Override
public boolean canProvide() {
return true;
}
@Override
public int getConsumptionPriority() {
return 1000;
}
@Override
public boolean canConstructBattery() {
return false;
}
@Override
public int withdrawMana(int cost, boolean simulate) {
ItemCreativeUnlocker.addToIntArray(creativeUnlocker, ItemCreativeUnlocker.TAG_EXTRACTIONS, cost);
return cost < 0 ? getMana() : cost;
}
@Override
public int insertMana(int amount, boolean simulate) {
ItemCreativeUnlocker.addToIntArray(creativeUnlocker, ItemCreativeUnlocker.TAG_INSERTIONS, amount);
return amount;
}
}

View file

@ -1,9 +1,12 @@
package at.petrak.hexcasting.common.items.magic;
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.item.ManaHolderItem;
import at.petrak.hexcasting.api.misc.DiscoveryHandlers;
import at.petrak.hexcasting.api.misc.ManaConstants;
import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.lib.HexSounds;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.advancements.Advancement;
@ -11,13 +14,17 @@ import net.minecraft.locale.Language;
import net.minecraft.network.chat.*;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
@ -29,6 +36,36 @@ import static at.petrak.hexcasting.api.HexAPI.modLoc;
public class ItemCreativeUnlocker extends Item implements ManaHolderItem {
static {
DiscoveryHandlers.addManaHolderDiscoverer(harness -> {
var player = harness.getCtx().getCaster();
if (!player.isCreative())
return List.of();
for (ItemStack item : player.getInventory().items) {
if (isDebug(item)) {
return List.of(new DebugUnlockerHolder(item));
}
}
// Technically possible with commands!
for (ItemStack item : player.getInventory().armor) {
if (isDebug(item)) {
return List.of(new DebugUnlockerHolder(item));
}
}
for (ItemStack item : player.getInventory().offhand) {
if (isDebug(item)) {
return List.of(new DebugUnlockerHolder(item));
}
}
return List.of();
});
}
public static boolean isDebug(ItemStack stack) {
return stack.is(HexItems.CREATIVE_UNLOCKER)
&& stack.hasCustomHoverName()
@ -47,7 +84,8 @@ public class ItemCreativeUnlocker extends Item implements ManaHolderItem {
return emphasized;
}
private static final String TAG_EXTRACTIONS = "extractions";
public static final String TAG_EXTRACTIONS = "extractions";
public static final String TAG_INSERTIONS = "insertions";
public ItemCreativeUnlocker(Properties properties) {
super(properties);
@ -75,22 +113,37 @@ public class ItemCreativeUnlocker extends Item implements ManaHolderItem {
@Override
public boolean canRecharge(ItemStack stack) {
return false;
return true;
}
public static void addToIntArray(ItemStack stack, String tag, int n) {
int[] arr = NBTHelper.getIntArray(stack, tag);
if (arr == null) {
arr = new int[0];
}
int[] newArr = Arrays.copyOf(arr, arr.length + 1);
newArr[newArr.length - 1] = n;
NBTHelper.putIntArray(stack, tag, newArr);
}
@Override
public int withdrawMana(ItemStack stack, int cost, boolean simulate) {
// In case it's withdrawn through other means
if (!simulate && isDebug(stack)) {
int[] arr = NBTHelper.getIntArray(stack, TAG_EXTRACTIONS);
if (arr == null) {
arr = new int[0];
}
int[] newArr = Arrays.copyOf(arr, arr.length + 1);
newArr[newArr.length - 1] = cost;
NBTHelper.putIntArray(stack, TAG_EXTRACTIONS, newArr);
addToIntArray(stack, TAG_EXTRACTIONS, cost);
}
return cost < 0 ? 1 : cost;
return cost < 0 ? getMana(stack) : cost;
}
@Override
public int insertMana(ItemStack stack, int amount, boolean simulate) {
// In case it's inserted through other means
if (!simulate && isDebug(stack)) {
addToIntArray(stack, TAG_INSERTIONS, amount);
}
return amount < 0 ? getMaxMana(stack) : amount;
}
@Override
@ -101,28 +154,44 @@ public class ItemCreativeUnlocker extends Item implements ManaHolderItem {
@Override
public void inventoryTick(ItemStack stack, Level level, Entity entity, int slot, boolean selected) {
if (isDebug(stack) && !level.isClientSide) {
int[] arr = NBTHelper.getIntArray(stack, TAG_EXTRACTIONS);
if (arr != null) {
NBTHelper.remove(stack, TAG_EXTRACTIONS);
for (int i : arr) {
if (i < 0) {
entity.sendMessage(new TranslatableComponent("hexcasting.debug.mana_withdrawn",
stack.getDisplayName(),
new TranslatableComponent("hexcasting.debug.all_mana").withStyle(ChatFormatting.GRAY))
.withStyle(ChatFormatting.LIGHT_PURPLE), Util.NIL_UUID);
} else {
entity.sendMessage(new TranslatableComponent("hexcasting.debug.mana_withdrawn.with_dust",
stack.getDisplayName(),
new TextComponent("" + i).withStyle(ChatFormatting.WHITE),
new TextComponent(String.format("%.2f", i * 1.0 / ManaConstants.DUST_UNIT)).withStyle(
ChatFormatting.WHITE))
.withStyle(ChatFormatting.LIGHT_PURPLE), Util.NIL_UUID);
}
debugDisplay(stack, TAG_EXTRACTIONS, "withdrawn", "all_mana", entity);
debugDisplay(stack, TAG_INSERTIONS, "inserted", "infinite_mana", entity);
}
}
private void debugDisplay(ItemStack stack, String tag, String langKey, String allKey, Entity entity) {
int[] arr = NBTHelper.getIntArray(stack, tag);
if (arr != null) {
NBTHelper.remove(stack, tag);
for (int i : arr) {
if (i < 0) {
entity.sendMessage(new TranslatableComponent("hexcasting.debug.mana_" + langKey,
stack.getDisplayName(),
new TranslatableComponent("hexcasting.debug." + allKey).withStyle(ChatFormatting.GRAY))
.withStyle(ChatFormatting.LIGHT_PURPLE), Util.NIL_UUID);
} else {
entity.sendMessage(new TranslatableComponent("hexcasting.debug.mana_" + langKey + ".with_dust",
stack.getDisplayName(),
new TextComponent("" + i).withStyle(ChatFormatting.WHITE),
new TextComponent(String.format("%.2f", i * 1.0 / ManaConstants.DUST_UNIT)).withStyle(
ChatFormatting.WHITE))
.withStyle(ChatFormatting.LIGHT_PURPLE), Util.NIL_UUID);
}
}
}
}
@Override
public InteractionResult useOn(UseOnContext context) {
BlockEntity be = context.getLevel().getBlockEntity(context.getClickedPos());
if (be instanceof BlockEntityAbstractImpetus impetus) {
impetus.setInfiniteMana();
context.getLevel().playSound(null, context.getClickedPos(), HexSounds.SPELL_CIRCLE_FIND_BLOCK, SoundSource.PLAYERS, 1f, 1f);
return InteractionResult.sidedSuccess(context.getLevel().isClientSide());
}
return InteractionResult.PASS;
}
@Override
public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity consumer) {
if (level instanceof ServerLevel slevel && consumer instanceof ServerPlayer player) {
@ -150,8 +219,6 @@ public class ItemCreativeUnlocker extends Item implements ManaHolderItem {
return copy;
}
private static final TextColor HEX_COLOR = TextColor.fromRgb(0xb38ef3);
private static MutableComponent rainbow(MutableComponent component, int shift, Level level) {
if (level == null) {
return component.withStyle(ChatFormatting.WHITE);
@ -164,16 +231,14 @@ public class ItemCreativeUnlocker extends Item implements ManaHolderItem {
@Override
public void appendHoverText(ItemStack stack, @Nullable Level level, List<Component> tooltipComponents,
TooltipFlag isAdvanced) {
String prefix = "item.hexcasting.creative_unlocker.";
Component emphasized = infiniteMedia(level);
MutableComponent modName = new TranslatableComponent(prefix + "mod_name").withStyle(
(s) -> s.withColor(HEX_COLOR));
MutableComponent modName = new TranslatableComponent("item.hexcasting.creative_unlocker.mod_name").withStyle(
(s) -> s.withColor(ItemManaHolder.HEX_COLOR));
tooltipComponents.add(
new TranslatableComponent(prefix + "tooltip.0", emphasized).withStyle(ChatFormatting.GRAY));
tooltipComponents.add(new TranslatableComponent(prefix + "tooltip.1", modName).withStyle(ChatFormatting.GRAY));
new TranslatableComponent("hexcasting.spelldata.onitem", emphasized).withStyle(ChatFormatting.GRAY));
tooltipComponents.add(new TranslatableComponent("item.hexcasting.creative_unlocker.tooltip", modName).withStyle(ChatFormatting.GRAY));
}
private static void addChildren(Advancement root, List<Advancement> out) {

View file

@ -1,10 +1,12 @@
package at.petrak.hexcasting.common.items.magic;
import at.petrak.hexcasting.api.item.ManaHolderItem;
import at.petrak.hexcasting.api.misc.ManaConstants;
import at.petrak.hexcasting.api.utils.ManaHelper;
import at.petrak.hexcasting.api.utils.NBTHelper;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextColor;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.util.Mth;
import net.minecraft.world.item.Item;
@ -13,12 +15,24 @@ import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.List;
public abstract class ItemManaHolder extends Item implements ManaHolderItem {
public static final String TAG_MANA = "hexcasting:mana";
public static final String TAG_MAX_MANA = "hexcasting:start_mana";
public static final TextColor HEX_COLOR = TextColor.fromRgb(0xb38ef3);
private static final DecimalFormat PERCENTAGE = new DecimalFormat("####");
static {
PERCENTAGE.setRoundingMode(RoundingMode.DOWN);
}
private static final DecimalFormat DUST_AMOUNT = new DecimalFormat("###,###.##");
public ItemManaHolder(Properties pProperties) {
super(pProperties);
}
@ -75,12 +89,24 @@ public abstract class ItemManaHolder extends Item implements ManaHolderItem {
@Override
public void appendHoverText(ItemStack pStack, @Nullable Level pLevel, List<Component> pTooltipComponents,
TooltipFlag pIsAdvanced) {
if (pIsAdvanced.isAdvanced() && getMaxMana(pStack) > 0) {
var maxMana = getMaxMana(pStack);
if (maxMana > 0) {
var mana = getMana(pStack);
var fullness = getManaFullness(pStack);
var color = TextColor.fromRgb(ManaHelper.manaBarColor(mana, maxMana));
var manaAmount = new TextComponent(DUST_AMOUNT.format(mana / (float) ManaConstants.DUST_UNIT));
var percentFull = new TextComponent(PERCENTAGE.format(100f * fullness) + "%");
var maxCapacity = new TranslatableComponent("hexcasting.tooltip.mana", DUST_AMOUNT.format(maxMana / (float) ManaConstants.DUST_UNIT));
manaAmount.withStyle(style -> style.withColor(HEX_COLOR));
maxCapacity.withStyle(style -> style.withColor(HEX_COLOR));
percentFull.withStyle(style -> style.withColor(color));
pTooltipComponents.add(
new TranslatableComponent("item.hexcasting.manaholder.amount",
String.format("%,d", getMana(pStack)),
String.format("%,d", getMaxMana(pStack)),
100f * getManaFullness(pStack)).withStyle(ChatFormatting.GRAY));
new TranslatableComponent("hexcasting.tooltip.mana_amount.advanced",
manaAmount, maxCapacity, percentFull));
}
super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced);

View file

@ -0,0 +1,15 @@
package at.petrak.hexcasting.common.lib;
import net.minecraft.core.Registry;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EntityType;
import static at.petrak.hexcasting.api.HexAPI.modLoc;
public class HexEntityTags {
public static final TagKey<EntityType<?>> STICKY_TELEPORTERS = create("sticky_teleporters");
public static TagKey<EntityType<?>> create(String name) {
return TagKey.create(Registry.ENTITY_TYPE_REGISTRY, modLoc(name));
}
}

View file

@ -65,7 +65,7 @@ public class HexItems {
public static final ItemSlate SLATE = make("slate", new ItemSlate(HexBlocks.SLATE, props()));
public static final ItemManaBattery BATTERY = make("battery",
new ItemManaBattery(new Item.Properties().stacksTo(1)));
new ItemManaBattery(unstackable()));
public static final EnumMap<DyeColor, ItemDyeColorizer> DYE_COLORIZERS = Util.make(() -> {
var out = new EnumMap<DyeColor, ItemDyeColorizer>(DyeColor.class);

View file

@ -1,5 +1,6 @@
package at.petrak.hexcasting.common.network;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.common.entities.EntityWallScroll;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
@ -34,7 +35,7 @@ public record MsgNewWallScrollAck(ClientboundAddEntityPacket inner, BlockPos pos
public static MsgNewWallScrollAck deserialize(FriendlyByteBuf buf) {
var inner = new ClientboundAddEntityPacket(buf);
var pos = buf.readBlockPos();
var dir = Direction.values()[buf.readByte()];
var dir = HexUtils.getSafe(Direction.values(), buf.readByte());
var scroll = buf.readItem();
var strokeOrder = buf.readBoolean();
var blockSize = buf.readVarInt();

View file

@ -12,7 +12,7 @@ import static at.petrak.hexcasting.api.HexAPI.modLoc;
/**
* Sent server->client when a player is looking at a block through a lens whose comparator value is not the same as what they last saw.
*/
public record MsgUpdateComparatorVisualsAck(BlockPos pos, int value) implements IMessage {
public record MsgUpdateComparatorVisualsAck(BlockPos pos, int comparator, int bee) implements IMessage {
public static final ResourceLocation ID = modLoc("cmp");
@Override
@ -23,15 +23,17 @@ public record MsgUpdateComparatorVisualsAck(BlockPos pos, int value) implements
public static MsgUpdateComparatorVisualsAck deserialize(ByteBuf buffer) {
var buf = new FriendlyByteBuf(buffer);
int value = buf.readInt();
BlockPos pos = value == -1 ? null : buf.readBlockPos();
int comparator = buf.readInt();
int bee = buf.readInt();
BlockPos pos = comparator == -1 && bee == -1 ? null : buf.readBlockPos();
return new MsgUpdateComparatorVisualsAck(pos, value);
return new MsgUpdateComparatorVisualsAck(pos, comparator, bee);
}
public void serialize(FriendlyByteBuf buf) {
buf.writeInt(this.value);
if (this.value != -1) {
buf.writeInt(this.comparator);
buf.writeInt(this.bee);
if (this.comparator != -1 || this.bee != -1) {
buf.writeBlockPos(this.pos);
}
}
@ -40,7 +42,7 @@ public record MsgUpdateComparatorVisualsAck(BlockPos pos, int value) implements
Minecraft.getInstance().execute(new Runnable() {
@Override
public void run() {
ScryingLensOverlayRegistry.receiveComparatorValue(msg.pos(), msg.value());
ScryingLensOverlayRegistry.receiveComparatorAndBeeValue(msg.pos(), msg.comparator(), msg.bee());
}
});
}

View file

@ -5,6 +5,7 @@ import at.petrak.hexcasting.api.advancements.FailToCastGreatSpellTrigger;
import at.petrak.hexcasting.api.advancements.OvercastTrigger;
import at.petrak.hexcasting.api.advancements.SpendManaTrigger;
import at.petrak.hexcasting.api.misc.ManaConstants;
import at.petrak.hexcasting.api.mod.HexItemTags;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.paucal.api.datagen.PaucalAdvancementProvider;
import net.minecraft.advancements.Advancement;
@ -12,6 +13,7 @@ import net.minecraft.advancements.DisplayInfo;
import net.minecraft.advancements.FrameType;
import net.minecraft.advancements.critereon.EntityPredicate;
import net.minecraft.advancements.critereon.InventoryChangeTrigger;
import net.minecraft.advancements.critereon.ItemPredicate;
import net.minecraft.advancements.critereon.MinMaxBounds;
import net.minecraft.data.DataGenerator;
import net.minecraft.network.chat.TranslatableComponent;
@ -37,8 +39,8 @@ public class HexAdvancements extends PaucalAdvancementProvider {
new ResourceLocation("minecraft", "textures/block/calcite.png"),
FrameType.TASK, true, true, true))
// the only thing making this vaguely tolerable is the knowledge the json files are worse somehow
.addCriterion("has_charged_amethyst",
InventoryChangeTrigger.TriggerInstance.hasItems(HexItems.CHARGED_AMETHYST))
.addCriterion("has_charged_amethyst", InventoryChangeTrigger.TriggerInstance.hasItems(
ItemPredicate.Builder.item().of(HexItemTags.GRANTS_ROOT_ADVANCEMENT).build()))
.save(consumer, prefix("root")); // how the hell does one even read this
// weird names so we have alphabetical parity

View file

@ -26,10 +26,12 @@ public class HexItemTagProvider extends PaucalItemTagProvider {
tag(xtags.amethystDust()).add(HexItems.AMETHYST_DUST);
tag(HexItemTags.WANDS).add(HexItems.WAND_AKASHIC,
HexItems.WAND_OAK, HexItems.WAND_SPRUCE, HexItems.WAND_BIRCH,
HexItems.WAND_JUNGLE, HexItems.WAND_ACACIA, HexItems.WAND_DARK_OAK,
HexItems.WAND_CRIMSON, HexItems.WAND_WARPED);
HexItems.WAND_OAK, HexItems.WAND_SPRUCE, HexItems.WAND_BIRCH,
HexItems.WAND_JUNGLE, HexItems.WAND_ACACIA, HexItems.WAND_DARK_OAK,
HexItems.WAND_CRIMSON, HexItems.WAND_WARPED);
tag(HexItemTags.PHIAL_BASE).add(Items.GLASS_BOTTLE);
tag(HexItemTags.GRANTS_ROOT_ADVANCEMENT).add(HexItems.AMETHYST_DUST, Items.AMETHYST_SHARD,
HexItems.CHARGED_AMETHYST);
this.copy(HexBlockTags.AKASHIC_LOGS, HexItemTags.AKASHIC_LOGS);
this.copy(HexBlockTags.AKASHIC_PLANKS, HexItemTags.AKASHIC_PLANKS);

View file

@ -0,0 +1,9 @@
package at.petrak.hexcasting.datagen;
import net.minecraft.data.recipes.RecipeBuilder;
public interface IXplatConditionsBuilder extends RecipeBuilder {
IXplatConditionsBuilder whenModLoaded(String modid);
IXplatConditionsBuilder whenModMissing(String modid);
}

View file

@ -1,5 +1,6 @@
package at.petrak.hexcasting.datagen;
import at.petrak.hexcasting.datagen.recipe.builders.ToolIngredient;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.crafting.Ingredient;
@ -23,4 +24,10 @@ public interface IXplatIngredients {
EnumMap<DyeColor, Ingredient> dyes();
Ingredient stick();
Ingredient whenModIngredient(Ingredient defaultIngredient, String modid, Ingredient modIngredient);
ToolIngredient axeStrip();
ToolIngredient axeDig();
}

View file

@ -1,69 +1,66 @@
package at.petrak.hexcasting.datagen.recipe;
package at.petrak.hexcasting.datagen.recipe
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.advancements.OvercastTrigger;
import at.petrak.hexcasting.api.mod.HexItemTags;
import at.petrak.hexcasting.common.items.ItemWand;
import at.petrak.hexcasting.common.items.colorizer.ItemPrideColorizer;
import at.petrak.hexcasting.common.lib.HexBlocks;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.recipe.SealFocusRecipe;
import at.petrak.hexcasting.common.recipe.SealSpellbookRecipe;
import at.petrak.hexcasting.common.recipe.ingredient.StateIngredientHelper;
import at.petrak.hexcasting.common.recipe.ingredient.VillagerIngredient;
import at.petrak.hexcasting.datagen.IXplatIngredients;
import at.petrak.hexcasting.datagen.recipe.builders.BrainsweepRecipeBuilder;
import at.petrak.hexcasting.datagen.recipe.builders.CreateCrushingRecipeBuilder;
import at.petrak.paucal.api.datagen.PaucalRecipeProvider;
import net.minecraft.advancements.critereon.EntityPredicate;
import net.minecraft.advancements.critereon.MinMaxBounds;
import net.minecraft.core.Registry;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.recipes.*;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.ItemTags;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.DyeItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.SimpleRecipeSerializer;
import net.minecraft.world.level.block.Blocks;
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.advancements.OvercastTrigger
import at.petrak.hexcasting.api.mod.HexItemTags
import at.petrak.hexcasting.common.items.ItemWand
import at.petrak.hexcasting.common.items.colorizer.ItemPrideColorizer
import at.petrak.hexcasting.common.lib.HexBlocks
import at.petrak.hexcasting.common.lib.HexItems
import at.petrak.hexcasting.common.recipe.SealFocusRecipe
import at.petrak.hexcasting.common.recipe.SealSpellbookRecipe
import at.petrak.hexcasting.common.recipe.ingredient.StateIngredientHelper
import at.petrak.hexcasting.common.recipe.ingredient.VillagerIngredient
import at.petrak.hexcasting.datagen.IXplatConditionsBuilder
import at.petrak.hexcasting.datagen.IXplatIngredients
import at.petrak.hexcasting.datagen.recipe.builders.BrainsweepRecipeBuilder
import at.petrak.hexcasting.datagen.recipe.builders.CompatIngredientValue
import at.petrak.hexcasting.datagen.recipe.builders.CreateCrushingRecipeBuilder
import at.petrak.hexcasting.datagen.recipe.builders.FarmersDelightCuttingRecipeBuilder
import at.petrak.paucal.api.datagen.PaucalRecipeProvider
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.advancements.critereon.MinMaxBounds
import net.minecraft.core.Registry
import net.minecraft.data.DataGenerator
import net.minecraft.data.recipes.*
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvents
import net.minecraft.tags.ItemTags
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.DyeItem
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.item.crafting.SimpleRecipeSerializer
import net.minecraft.world.level.block.Blocks
import java.util.function.Consumer
import java.util.function.Consumer;
import java.util.function.Supplier;
class HexplatRecipes(
val generator: DataGenerator,
val ingredients: IXplatIngredients,
val conditions: (RecipeBuilder) -> IXplatConditionsBuilder
) : PaucalRecipeProvider(generator, HexAPI.MOD_ID) {
override fun makeRecipes(recipes: Consumer<FinishedRecipe>) {
specialRecipe(recipes, SealFocusRecipe.SERIALIZER)
specialRecipe(recipes, SealSpellbookRecipe.SERIALIZER)
public class HexplatRecipes extends PaucalRecipeProvider {
public DataGenerator generator;
public IXplatIngredients ingredients;
public Supplier<CreateCrushingRecipeBuilder> createCrushing;
wandRecipe(recipes, HexItems.WAND_OAK, Items.OAK_PLANKS)
wandRecipe(recipes, HexItems.WAND_BIRCH, Items.BIRCH_PLANKS)
wandRecipe(recipes, HexItems.WAND_SPRUCE, Items.SPRUCE_PLANKS)
wandRecipe(recipes, HexItems.WAND_JUNGLE, Items.JUNGLE_PLANKS)
wandRecipe(recipes, HexItems.WAND_DARK_OAK, Items.DARK_OAK_PLANKS)
wandRecipe(recipes, HexItems.WAND_ACACIA, Items.ACACIA_PLANKS)
wandRecipe(recipes, HexItems.WAND_CRIMSON, Items.CRIMSON_PLANKS)
wandRecipe(recipes, HexItems.WAND_WARPED, Items.WARPED_PLANKS)
wandRecipe(recipes, HexItems.WAND_AKASHIC, HexBlocks.AKASHIC_PLANKS.asItem())
public HexplatRecipes(DataGenerator pGenerator, IXplatIngredients ingredients, Supplier<CreateCrushingRecipeBuilder> createCrushing) {
super(pGenerator, HexAPI.MOD_ID);
this.generator = pGenerator;
this.ingredients = ingredients;
this.createCrushing = createCrushing;
}
protected void makeRecipes(Consumer<FinishedRecipe> recipes) {
specialRecipe(recipes, SealFocusRecipe.SERIALIZER);
specialRecipe(recipes, SealSpellbookRecipe.SERIALIZER);
wandRecipe(recipes, HexItems.WAND_OAK, Items.OAK_PLANKS);
wandRecipe(recipes, HexItems.WAND_BIRCH, Items.BIRCH_PLANKS);
wandRecipe(recipes, HexItems.WAND_SPRUCE, Items.SPRUCE_PLANKS);
wandRecipe(recipes, HexItems.WAND_JUNGLE, Items.JUNGLE_PLANKS);
wandRecipe(recipes, HexItems.WAND_DARK_OAK, Items.DARK_OAK_PLANKS);
wandRecipe(recipes, HexItems.WAND_ACACIA, Items.ACACIA_PLANKS);
wandRecipe(recipes, HexItems.WAND_CRIMSON, Items.CRIMSON_PLANKS);
wandRecipe(recipes, HexItems.WAND_WARPED, Items.WARPED_PLANKS);
wandRecipe(recipes, HexItems.WAND_AKASHIC, HexBlocks.AKASHIC_PLANKS.asItem());
ringCornered(HexItems.FOCUS, 1, ingredients.glowstoneDust(),
ingredients.leather(), Ingredient.of(HexItems.CHARGED_AMETHYST))
ringCornered(HexItems.FOCUS, 1,
ingredients.glowstoneDust(),
ingredients.leather(),
Ingredient.of(HexItems.CHARGED_AMETHYST))
.unlockedBy("has_item", hasItem(HexItemTags.WANDS))
.save(recipes);
.save(recipes)
ShapedRecipeBuilder.shaped(HexItems.SPELLBOOK)
.define('N', ingredients.goldNugget())
@ -75,17 +72,19 @@ public class HexplatRecipes extends PaucalRecipeProvider {
.pattern("NFA")
.pattern("NBA")
.unlockedBy("has_focus", hasItem(HexItems.FOCUS))
.unlockedBy("has_chorus", hasItem(Items.CHORUS_FRUIT)).save(recipes);
.unlockedBy("has_chorus", hasItem(Items.CHORUS_FRUIT)).save(recipes)
ringCornerless(HexItems.CYPHER, 1,
ringCornerless(
HexItems.CYPHER, 1,
ingredients.copperIngot(),
Ingredient.of(HexItems.AMETHYST_DUST))
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes)
ringCornerless(HexItems.TRINKET, 1,
ringCornerless(
HexItems.TRINKET, 1,
ingredients.ironIngot(),
Ingredient.of(Items.AMETHYST_SHARD))
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.ARTIFACT)
.define('F', ingredients.goldIngot())
@ -95,10 +94,10 @@ public class HexplatRecipes extends PaucalRecipeProvider {
.pattern(" F ")
.pattern("FAF")
.pattern(" D ")
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes)
ringCornerless(HexItems.SCRYING_LENS, 1, Items.GLASS, HexItems.AMETHYST_DUST)
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.ABACUS)
.define('S', Items.STICK)
@ -107,7 +106,7 @@ public class HexplatRecipes extends PaucalRecipeProvider {
.pattern("WAW")
.pattern("SAS")
.pattern("WAW")
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes)
// Why am I like this
ShapedRecipeBuilder.shaped(HexItems.SUBMARINE_SANDWICH)
@ -118,35 +117,39 @@ public class HexplatRecipes extends PaucalRecipeProvider {
.pattern(" SA")
.pattern(" C ")
.pattern(" B ")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes);
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes)
for (var dye : DyeColor.values()) {
var item = HexItems.DYE_COLORIZERS.get(dye);
for (dye in DyeColor.values()) {
val item = HexItems.DYE_COLORIZERS[dye]!!
ShapedRecipeBuilder.shaped(item)
.define('D', HexItems.AMETHYST_DUST)
.define('C', DyeItem.byColor(dye))
.pattern(" D ")
.pattern("DCD")
.pattern(" D ")
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes);
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes)
}
gayRecipe(recipes, ItemPrideColorizer.Type.AGENDER, Items.GLASS);
gayRecipe(recipes, ItemPrideColorizer.Type.AROACE, Items.WHEAT_SEEDS);
gayRecipe(recipes, ItemPrideColorizer.Type.AROMANTIC, Items.ARROW);
gayRecipe(recipes, ItemPrideColorizer.Type.ASEXUAL, Items.BREAD);
gayRecipe(recipes, ItemPrideColorizer.Type.BISEXUAL, Items.WHEAT);
gayRecipe(recipes, ItemPrideColorizer.Type.DEMIBOY, Items.RAW_IRON);
gayRecipe(recipes, ItemPrideColorizer.Type.DEMIGIRL, Items.RAW_COPPER);
gayRecipe(recipes, ItemPrideColorizer.Type.GAY, Items.STONE_BRICK_WALL);
gayRecipe(recipes, ItemPrideColorizer.Type.GENDERFLUID, Items.WATER_BUCKET);
gayRecipe(recipes, ItemPrideColorizer.Type.GENDERQUEER, Items.GLASS_BOTTLE);
gayRecipe(recipes, ItemPrideColorizer.Type.INTERSEX, Items.AZALEA);
gayRecipe(recipes, ItemPrideColorizer.Type.LESBIAN, Items.HONEYCOMB);
gayRecipe(recipes, ItemPrideColorizer.Type.NONBINARY, Items.MOSS_BLOCK);
gayRecipe(recipes, ItemPrideColorizer.Type.PANSEXUAL, Items.CARROT);
gayRecipe(recipes, ItemPrideColorizer.Type.PLURAL, Items.REPEATER);
gayRecipe(recipes, ItemPrideColorizer.Type.TRANSGENDER, Items.EGG);
gayRecipe(recipes, ItemPrideColorizer.Type.AGENDER, Ingredient.of(Items.GLASS))
gayRecipe(recipes, ItemPrideColorizer.Type.AROACE, Ingredient.of(Items.WHEAT_SEEDS))
gayRecipe(recipes, ItemPrideColorizer.Type.AROMANTIC, Ingredient.of(Items.ARROW))
gayRecipe(recipes, ItemPrideColorizer.Type.ASEXUAL, Ingredient.of(Items.BREAD))
gayRecipe(recipes, ItemPrideColorizer.Type.BISEXUAL, Ingredient.of(Items.WHEAT))
gayRecipe(recipes, ItemPrideColorizer.Type.DEMIBOY, Ingredient.of(Items.RAW_IRON))
gayRecipe(recipes, ItemPrideColorizer.Type.DEMIGIRL, Ingredient.of(Items.RAW_COPPER))
gayRecipe(recipes, ItemPrideColorizer.Type.GAY, Ingredient.of(Items.STONE_BRICK_WALL))
gayRecipe(recipes, ItemPrideColorizer.Type.GENDERFLUID, Ingredient.of(Items.WATER_BUCKET))
gayRecipe(recipes, ItemPrideColorizer.Type.GENDERQUEER, Ingredient.of(Items.GLASS_BOTTLE))
gayRecipe(recipes, ItemPrideColorizer.Type.INTERSEX, Ingredient.of(Items.AZALEA))
gayRecipe(recipes, ItemPrideColorizer.Type.LESBIAN, Ingredient.of(Items.HONEYCOMB))
gayRecipe(recipes, ItemPrideColorizer.Type.NONBINARY, Ingredient.of(Items.MOSS_BLOCK))
gayRecipe(recipes, ItemPrideColorizer.Type.PANSEXUAL, ingredients.whenModIngredient(
Ingredient.of(Items.CARROT),
"farmersdelight",
CompatIngredientValue.of("farmersdelight:skillet")
))
gayRecipe(recipes, ItemPrideColorizer.Type.PLURAL, Ingredient.of(Items.REPEATER))
gayRecipe(recipes, ItemPrideColorizer.Type.TRANSGENDER, Ingredient.of(Items.EGG))
ShapedRecipeBuilder.shaped(HexItems.UUID_COLORIZER)
.define('B', Items.BOWL)
@ -155,35 +158,37 @@ public class HexplatRecipes extends PaucalRecipeProvider {
.pattern(" C ")
.pattern(" D ")
.pattern(" B ")
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes);
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.SCROLL_SMOL)
.define('P', Items.PAPER)
.define('A', Items.AMETHYST_SHARD)
.pattern(" A")
.pattern("P ")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes);
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.SCROLL_MEDIUM)
.define('P', Items.PAPER)
.define('A', Items.AMETHYST_SHARD)
.pattern(" A")
.pattern("PP ")
.pattern("PP ")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes);
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.SCROLL_LARGE)
.define('P', Items.PAPER)
.define('A', Items.AMETHYST_SHARD)
.pattern("PPA")
.pattern("PPP")
.pattern("PPP")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes);
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.SLATE, 6)
.define('S', Items.DEEPSLATE)
.define('A', HexItems.AMETHYST_DUST)
.pattern(" A ")
.pattern("SSS")
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes);
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.JEWELER_HAMMER)
.define('I', ingredients.ironIngot())
@ -193,103 +198,120 @@ public class HexplatRecipes extends PaucalRecipeProvider {
.pattern("IAN")
.pattern(" S ")
.pattern(" S ")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes);
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.SLATE_BLOCK)
.define('S', HexItems.SLATE)
.pattern("S")
.pattern("S")
.unlockedBy("has_item", hasItem(HexItems.SLATE))
.save(recipes, modLoc("slate_block_from_slates"));
.save(recipes, modLoc("slate_block_from_slates"))
ringAll(HexBlocks.SLATE_BLOCK, 8, Blocks.DEEPSLATE, HexItems.AMETHYST_DUST)
.unlockedBy("has_item", hasItem(HexItems.SLATE)).save(recipes);
.unlockedBy("has_item", hasItem(HexItems.SLATE)).save(recipes)
packing(HexItems.AMETHYST_DUST, HexBlocks.AMETHYST_DUST_BLOCK.asItem(), "amethyst_dust",
false, recipes);
false, recipes)
ringAll(HexBlocks.AMETHYST_TILES, 8, Blocks.AMETHYST_BLOCK, HexItems.AMETHYST_DUST)
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes);
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes)
SingleItemRecipeBuilder.stonecutting(Ingredient.of(Blocks.AMETHYST_BLOCK), HexBlocks.AMETHYST_TILES)
.unlockedBy("has_item", hasItem(Blocks.AMETHYST_BLOCK))
.save(recipes, modLoc("stonecutting/amethyst_tiles"));
.save(recipes, modLoc("stonecutting/amethyst_tiles"))
ringAll(HexBlocks.SCROLL_PAPER, 8, Items.PAPER, Items.AMETHYST_SHARD)
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes);
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes)
ShapelessRecipeBuilder.shapeless(HexBlocks.ANCIENT_SCROLL_PAPER, 8)
.requires(ingredients.dyes().get(DyeColor.BROWN))
.requires(ingredients.dyes()[DyeColor.BROWN]!!)
.requires(HexBlocks.SCROLL_PAPER, 8)
.unlockedBy("has_item", hasItem(HexBlocks.SCROLL_PAPER)).save(recipes);
.unlockedBy("has_item", hasItem(HexBlocks.SCROLL_PAPER)).save(recipes)
stack(HexBlocks.SCROLL_PAPER_LANTERN, 1, HexBlocks.SCROLL_PAPER, Items.TORCH)
.unlockedBy("has_item", hasItem(HexBlocks.SCROLL_PAPER)).save(recipes);
.unlockedBy("has_item", hasItem(HexBlocks.SCROLL_PAPER)).save(recipes)
stack(HexBlocks.ANCIENT_SCROLL_PAPER_LANTERN, 1, HexBlocks.ANCIENT_SCROLL_PAPER, Items.TORCH)
.unlockedBy("has_item", hasItem(HexBlocks.ANCIENT_SCROLL_PAPER)).save(recipes);
.unlockedBy("has_item", hasItem(HexBlocks.ANCIENT_SCROLL_PAPER)).save(recipes)
ShapelessRecipeBuilder.shapeless(HexBlocks.ANCIENT_SCROLL_PAPER_LANTERN, 8)
.requires(ingredients.dyes().get(DyeColor.BROWN))
.requires(ingredients.dyes()[DyeColor.BROWN]!!)
.requires(HexBlocks.SCROLL_PAPER_LANTERN, 8)
.unlockedBy("has_item", hasItem(HexBlocks.SCROLL_PAPER_LANTERN))
.save(recipes, modLoc("ageing_scroll_paper_lantern"));
.save(recipes, modLoc("ageing_scroll_paper_lantern"))
stack(HexBlocks.SCONCE, 4, Ingredient.of(HexItems.CHARGED_AMETHYST),
stack(HexBlocks.SCONCE, 4,
Ingredient.of(HexItems.CHARGED_AMETHYST),
ingredients.copperIngot())
.unlockedBy("has_item", hasItem(HexItems.CHARGED_AMETHYST)).save(recipes);
.unlockedBy("has_item", hasItem(HexItems.CHARGED_AMETHYST)).save(recipes)
ShapelessRecipeBuilder.shapeless(HexBlocks.AKASHIC_PLANKS, 4)
.requires(HexItemTags.AKASHIC_LOGS)
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_LOGS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_LOGS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_WOOD, 3)
.define('W', HexBlocks.AKASHIC_LOG)
.pattern("WW")
.pattern("WW")
.unlockedBy("has_item", hasItem(HexBlocks.AKASHIC_LOG)).save(recipes);
.unlockedBy("has_item", hasItem(HexBlocks.AKASHIC_LOG)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_WOOD_STRIPPED, 3)
.define('W', HexBlocks.AKASHIC_LOG_STRIPPED)
.pattern("WW")
.pattern("WW")
.unlockedBy("has_item", hasItem(HexBlocks.AKASHIC_LOG_STRIPPED)).save(recipes);
ring(HexBlocks.AKASHIC_PANEL, 8, HexItemTags.AKASHIC_PLANKS, null)
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes);
.unlockedBy("has_item", hasItem(HexBlocks.AKASHIC_LOG_STRIPPED)).save(recipes)
ring(HexBlocks.AKASHIC_PANEL, 8,
HexItemTags.AKASHIC_PLANKS, null)
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_TILE, 6)
.define('W', HexItemTags.AKASHIC_PLANKS)
.pattern("WW ")
.pattern("W W")
.pattern(" WW")
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_DOOR, 3)
.define('W', HexItemTags.AKASHIC_PLANKS)
.pattern("WW")
.pattern("WW")
.pattern("WW")
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_TRAPDOOR, 2)
.define('W', HexItemTags.AKASHIC_PLANKS)
.pattern("WWW")
.pattern("WWW")
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_STAIRS, 4)
.define('W', HexItemTags.AKASHIC_PLANKS)
.pattern("W ")
.pattern("WW ")
.pattern("WWW")
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_SLAB, 6)
.define('W', HexItemTags.AKASHIC_PLANKS)
.pattern("WWW")
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_PRESSURE_PLATE, 1)
.define('W', HexItemTags.AKASHIC_PLANKS)
.pattern("WW")
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapelessRecipeBuilder.shapeless(HexBlocks.AKASHIC_BUTTON)
.requires(HexItemTags.AKASHIC_PLANKS)
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes);
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
var enlightenment = new OvercastTrigger.Instance(EntityPredicate.Composite.ANY,
MinMaxBounds.Ints.ANY,
// add a little bit of slop here
val enlightenment = OvercastTrigger.Instance(
EntityPredicate.Composite.ANY,
MinMaxBounds.Ints.ANY, // add a little bit of slop here
MinMaxBounds.Doubles.atLeast(0.8),
MinMaxBounds.Doubles.between(0.1, 2.05));
MinMaxBounds.Doubles.between(0.1, 2.05)
)
ShapedRecipeBuilder.shaped(HexBlocks.EMPTY_IMPETUS)
.define('B', Items.IRON_BARS)
.define('A', HexItems.CHARGED_AMETHYST)
@ -298,7 +320,7 @@ public class HexplatRecipes extends PaucalRecipeProvider {
.pattern("PSS")
.pattern("BAB")
.pattern("SSP")
.unlockedBy("enlightenment", enlightenment).save(recipes);
.unlockedBy("enlightenment", enlightenment).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.EMPTY_DIRECTRIX)
.define('C', Items.COMPARATOR)
@ -308,16 +330,17 @@ public class HexplatRecipes extends PaucalRecipeProvider {
.pattern("CSS")
.pattern("OAO")
.pattern("SSC")
.unlockedBy("enlightenment", enlightenment).save(recipes);
.unlockedBy("enlightenment", enlightenment).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_BOOKSHELF)
.define('L', HexItemTags.AKASHIC_LOGS)
.define('P', HexItemTags.AKASHIC_PLANKS)
.define('C', Items.BOOK)
/*this is the*/.pattern("LPL") // and what i have for you today is
/*this is the*/ .pattern("LPL") // and what i have for you today is
.pattern("CCC")
.pattern("LPL")
.unlockedBy("enlightenment", enlightenment).save(recipes);
.unlockedBy("enlightenment", enlightenment).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_CONNECTOR)
.define('L', HexItemTags.AKASHIC_LOGS)
.define('P', HexItemTags.AKASHIC_PLANKS)
@ -325,68 +348,112 @@ public class HexplatRecipes extends PaucalRecipeProvider {
.pattern("LPL")
.pattern("CCC")
.pattern("LPL")
.unlockedBy("enlightenment", enlightenment).save(recipes);
.unlockedBy("enlightenment", enlightenment).save(recipes)
new BrainsweepRecipeBuilder(StateIngredientHelper.of(Blocks.AMETHYST_BLOCK),
new VillagerIngredient(null, null, 3),
BrainsweepRecipeBuilder(StateIngredientHelper.of(Blocks.AMETHYST_BLOCK),
VillagerIngredient(null, null, 3),
Blocks.BUDDING_AMETHYST.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/budding_amethyst"));
.save(recipes, modLoc("brainsweep/budding_amethyst"))
new BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_IMPETUS),
new VillagerIngredient(new ResourceLocation("toolsmith"), null, 2),
BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_IMPETUS),
VillagerIngredient(ResourceLocation("toolsmith"), null, 2),
HexBlocks.IMPETUS_RIGHTCLICK.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/impetus_rightclick"));
new BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_IMPETUS),
new VillagerIngredient(new ResourceLocation("fletcher"), null, 2),
.save(recipes, modLoc("brainsweep/impetus_rightclick"))
BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_IMPETUS),
VillagerIngredient(ResourceLocation("fletcher"), null, 2),
HexBlocks.IMPETUS_LOOK.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/impetus_look"));
new BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_IMPETUS),
new VillagerIngredient(new ResourceLocation("cleric"), null, 2),
.save(recipes, modLoc("brainsweep/impetus_look"))
BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_IMPETUS),
VillagerIngredient(ResourceLocation("cleric"), null, 2),
HexBlocks.IMPETUS_STOREDPLAYER.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/impetus_storedplayer"));
.save(recipes, modLoc("brainsweep/impetus_storedplayer"))
new BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_DIRECTRIX),
new VillagerIngredient(new ResourceLocation("mason"), null, 1),
BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_DIRECTRIX),
VillagerIngredient(ResourceLocation("mason"), null, 1),
HexBlocks.DIRECTRIX_REDSTONE.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/directrix_redstone"));
.save(recipes, modLoc("brainsweep/directrix_redstone"))
new BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.AKASHIC_CONNECTOR),
new VillagerIngredient(new ResourceLocation("librarian"), null, 5),
BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.AKASHIC_CONNECTOR),
VillagerIngredient(ResourceLocation("librarian"), null, 5),
HexBlocks.AKASHIC_RECORD.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/akashic_record"));
.save(recipes, modLoc("brainsweep/akashic_record"))
// Create compat
createCrushing.get()
CreateCrushingRecipeBuilder()
.withInput(Blocks.AMETHYST_CLUSTER)
.duration(150)
.withOutput(Items.AMETHYST_SHARD, 7)
.withOutput(HexItems.AMETHYST_DUST, 5)
.withOutput(0.25f, HexItems.CHARGED_AMETHYST)
.save(recipes, new ResourceLocation("create", "crushing/amethyst_cluster"));
.withConditions()
.whenModLoaded("create")
.save(recipes, ResourceLocation("create", "crushing/amethyst_cluster"))
createCrushing.get()
CreateCrushingRecipeBuilder()
.withInput(Blocks.AMETHYST_BLOCK)
.duration(150)
.withOutput(Items.AMETHYST_SHARD, 3)
.withOutput(0.5f, HexItems.AMETHYST_DUST, 4)
.save(recipes, new ResourceLocation("create", "crushing/amethyst_block"));
.withConditions()
.whenModLoaded("create")
.save(recipes, ResourceLocation("create", "crushing/amethyst_block"))
createCrushing.get()
CreateCrushingRecipeBuilder()
.withInput(Items.AMETHYST_SHARD)
.duration(150)
.withOutput(HexItems.AMETHYST_DUST, 4)
.withOutput(0.5f, HexItems.AMETHYST_DUST)
.save(recipes, modLoc("compat/create/crushing/amethyst_shard"));
.withConditions()
.whenModLoaded("create")
.save(recipes, modLoc("compat/create/crushing/amethyst_shard"))
// FD compat
FarmersDelightCuttingRecipeBuilder()
.withInput(HexBlocks.AKASHIC_LOG)
.withTool(ingredients.axeStrip())
.withOutput(HexBlocks.AKASHIC_LOG_STRIPPED)
.withOutput("farmersdelight:tree_bark")
.withSound(SoundEvents.AXE_STRIP)
.withConditions()
.whenModLoaded("farmersdelight")
.save(recipes, modLoc("compat/farmersdelight/cutting/akashic_log"))
FarmersDelightCuttingRecipeBuilder()
.withInput(HexBlocks.AKASHIC_WOOD)
.withTool(ingredients.axeStrip())
.withOutput(HexBlocks.AKASHIC_WOOD_STRIPPED)
.withOutput("farmersdelight:tree_bark")
.withSound(SoundEvents.AXE_STRIP)
.withConditions()
.whenModLoaded("farmersdelight")
.save(recipes, modLoc("compat/farmersdelight/cutting/akashic_wood"))
FarmersDelightCuttingRecipeBuilder()
.withInput(HexBlocks.AKASHIC_TRAPDOOR)
.withTool(ingredients.axeDig())
.withOutput(HexBlocks.AKASHIC_PLANKS)
.withConditions()
.whenModLoaded("farmersdelight")
.save(recipes, modLoc("compat/farmersdelight/cutting/akashic_trapdoor"))
FarmersDelightCuttingRecipeBuilder()
.withInput(HexBlocks.AKASHIC_DOOR)
.withTool(ingredients.axeDig())
.withOutput(HexBlocks.AKASHIC_PLANKS)
.withConditions()
.whenModLoaded("farmersdelight")
.save(recipes, modLoc("compat/farmersdelight/cutting/akashic_door"))
}
private void wandRecipe(Consumer<FinishedRecipe> recipes, ItemWand wand, Item plank) {
private fun wandRecipe(recipes: Consumer<FinishedRecipe>, wand: ItemWand, plank: Item) {
ShapedRecipeBuilder.shaped(wand)
.define('W', plank)
.define('S', Items.STICK)
@ -395,22 +462,25 @@ public class HexplatRecipes extends PaucalRecipeProvider {
.pattern(" WS")
.pattern("S ")
.unlockedBy("has_item", hasItem(HexItems.CHARGED_AMETHYST))
.save(recipes);
.save(recipes)
}
private void gayRecipe(Consumer<FinishedRecipe> recipes, ItemPrideColorizer.Type type, Item material) {
var colorizer = HexItems.PRIDE_COLORIZERS.get(type);
private fun gayRecipe(recipes: Consumer<FinishedRecipe>, type: ItemPrideColorizer.Type, material: Ingredient) {
val colorizer = HexItems.PRIDE_COLORIZERS[type]!!
ShapedRecipeBuilder.shaped(colorizer)
.define('D', HexItems.AMETHYST_DUST)
.define('C', material)
.pattern(" D ")
.pattern("DCD")
.pattern(" D ")
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes);
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST))
.save(recipes)
}
protected void specialRecipe(Consumer<FinishedRecipe> consumer, SimpleRecipeSerializer<?> serializer) {
var name = Registry.RECIPE_SERIALIZER.getKey(serializer);
SpecialRecipeBuilder.special(serializer).save(consumer, HexAPI.MOD_ID + ":dynamic/" + name.getPath());
private fun specialRecipe(consumer: Consumer<FinishedRecipe>, serializer: SimpleRecipeSerializer<*>) {
val name = Registry.RECIPE_SERIALIZER.getKey(serializer)
SpecialRecipeBuilder.special(serializer).save(consumer, HexAPI.MOD_ID + ":dynamic/" + name!!.path)
}
private fun RecipeBuilder.withConditions(): IXplatConditionsBuilder = conditions(this)
}

View file

@ -0,0 +1,33 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.gson.JsonObject;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Stream;
public class CompatIngredientValue implements Ingredient.Value {
public final String item;
public CompatIngredientValue(String name) {
this.item = name;
}
public @NotNull Collection<ItemStack> getItems() {
return Collections.emptyList();
}
public @NotNull JsonObject serialize() {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("item", item);
return jsonObject;
}
public static Ingredient of(String itemName) {
return new Ingredient(Stream.of(new CompatIngredientValue(itemName)));
}
}

View file

@ -0,0 +1,18 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.gson.JsonObject;
public record CompatProcessingOutput(String name, int count, float chance) implements ProcessingOutput {
@Override
public JsonObject serialize() {
JsonObject json = new JsonObject();
json.addProperty("item", name);
if (count != 1) {
json.addProperty("count", count);
}
if (chance != 1) {
json.addProperty("chance", chance);
}
return json;
}
}

View file

@ -2,9 +2,7 @@ package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.minecraft.advancements.CriterionTriggerInstance;
import net.minecraft.core.Registry;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.data.recipes.RecipeBuilder;
import net.minecraft.resources.ResourceLocation;
@ -26,7 +24,7 @@ import java.util.function.Consumer;
// https://github.com/Creators-of-Create/Create/blob/82be76d8934af03b4e52cad6a9f74a4175ba7b05/src/main/java/com/simibubi/create/foundation/data/recipe/ProcessingRecipeGen.java
// https://github.com/Creators-of-Create/Create/blob/82be76d8934af03b4e52cad6a9f74a4175ba7b05/src/main/java/com/simibubi/create/content/contraptions/processing/ProcessingRecipeBuilder.java
// https://github.com/Creators-of-Create/Create/blob/82be76d8934af03b4e52cad6a9f74a4175ba7b05/src/main/java/com/simibubi/create/content/contraptions/processing/ProcessingRecipeSerializer.java
public abstract class CreateCrushingRecipeBuilder implements RecipeBuilder {
public class CreateCrushingRecipeBuilder implements RecipeBuilder {
private String group = "";
private Ingredient input;
private final List<ProcessingOutput> results = new ArrayList<>();
@ -78,7 +76,24 @@ public abstract class CreateCrushingRecipeBuilder implements RecipeBuilder {
}
public CreateCrushingRecipeBuilder withOutput(ItemStack output, float chance) {
this.results.add(new ProcessingOutput(output, chance));
this.results.add(new ItemProcessingOutput(output, chance));
return this;
}
public CreateCrushingRecipeBuilder withOutput(String name) {
return withOutput(1f, name, 1);
}
public CreateCrushingRecipeBuilder withOutput(String name, int count) {
return withOutput(1f, name, count);
}
public CreateCrushingRecipeBuilder withOutput(float chance, String name) {
return withOutput(chance, name, 1);
}
public CreateCrushingRecipeBuilder withOutput(float chance, String name, int count) {
this.results.add(new CompatProcessingOutput(name, count, chance));
return this;
}
@ -92,12 +107,6 @@ public abstract class CreateCrushingRecipeBuilder implements RecipeBuilder {
consumer.accept(new CrushingRecipe(resourceLocation));
}
public abstract void serializeConditions(JsonObject object);
public abstract CreateCrushingRecipeBuilder whenModLoaded(String modid);
public abstract CreateCrushingRecipeBuilder whenModMissing(String modid);
public class CrushingRecipe implements FinishedRecipe {
private final ResourceLocation id;
@ -128,8 +137,6 @@ public abstract class CreateCrushingRecipeBuilder implements RecipeBuilder {
if (processingDuration > 0) {
json.addProperty("processingTime", processingDuration);
}
serializeConditions(json);
}
@Override
@ -153,22 +160,4 @@ public abstract class CreateCrushingRecipeBuilder implements RecipeBuilder {
}
}
private record ProcessingOutput(ItemStack stack, float chance) {
private JsonObject serialize() {
JsonObject json = new JsonObject();
ResourceLocation resourceLocation = Registry.ITEM.getKey(stack.getItem());
json.addProperty("item", resourceLocation.toString());
int count = stack.getCount();
if (count != 1) {
json.addProperty("count", count);
}
if (stack.hasTag()) {
json.add("nbt", JsonParser.parseString(stack.getTag().toString()));
}
if (chance != 1) {
json.addProperty("chance", chance);
}
return json;
}
}
}

View file

@ -0,0 +1,165 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.common.collect.Lists;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.minecraft.advancements.CriterionTriggerInstance;
import net.minecraft.core.Registry;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.data.recipes.RecipeBuilder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.ItemLike;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.function.Consumer;
public class FarmersDelightCuttingRecipeBuilder implements RecipeBuilder {
private String group = "";
private final List<ProcessingOutput> outputs = Lists.newArrayList();
private Ingredient input;
private ToolIngredient toolAction;
private SoundEvent sound;
@Override
public FarmersDelightCuttingRecipeBuilder unlockedBy(String s, CriterionTriggerInstance criterionTriggerInstance) {
return this;
}
@Override
public @NotNull FarmersDelightCuttingRecipeBuilder group(@Nullable String name) {
group = name;
return this;
}
@Override
public Item getResult() {
return Items.AIR; // Irrelevant, we implement serialization ourselves
}
public FarmersDelightCuttingRecipeBuilder withInput(ItemLike item) {
return withInput(Ingredient.of(item));
}
public FarmersDelightCuttingRecipeBuilder withInput(ItemStack stack) {
return withInput(Ingredient.of(stack));
}
public FarmersDelightCuttingRecipeBuilder withInput(Ingredient ingredient) {
this.input = ingredient;
return this;
}
public FarmersDelightCuttingRecipeBuilder withOutput(ItemLike output) {
return withOutput(1f, output, 1);
}
public FarmersDelightCuttingRecipeBuilder withOutput(float chance, ItemLike output) {
return withOutput(chance, output, 1);
}
public FarmersDelightCuttingRecipeBuilder withOutput(ItemLike output, int count) {
return withOutput(1f, output, count);
}
public FarmersDelightCuttingRecipeBuilder withOutput(float chance, ItemLike output, int count) {
return withOutput(new ItemStack(output, count), chance);
}
public FarmersDelightCuttingRecipeBuilder withOutput(ItemStack output, float chance) {
this.outputs.add(new ItemProcessingOutput(output, chance));
return this;
}
public FarmersDelightCuttingRecipeBuilder withOutput(String name) {
return withOutput(1f, name, 1);
}
public FarmersDelightCuttingRecipeBuilder withOutput(String name, int count) {
return withOutput(1f, name, count);
}
public FarmersDelightCuttingRecipeBuilder withOutput(float chance, String name) {
return withOutput(chance, name, 1);
}
public FarmersDelightCuttingRecipeBuilder withOutput(float chance, String name, int count) {
this.outputs.add(new CompatProcessingOutput(name, count, chance));
return this;
}
public FarmersDelightCuttingRecipeBuilder withTool(ToolIngredient ingredient) {
this.toolAction = ingredient;
return this;
}
public FarmersDelightCuttingRecipeBuilder withSound(SoundEvent sound) {
this.sound = sound;
return this;
}
@Override
public void save(Consumer<FinishedRecipe> consumer, ResourceLocation resourceLocation) {
consumer.accept(new CuttingRecipe(resourceLocation));
}
public class CuttingRecipe implements FinishedRecipe {
private final ResourceLocation id;
public CuttingRecipe(ResourceLocation id) {
this.id = id;
}
@Override
public void serializeRecipeData(@NotNull JsonObject json) {
json.addProperty("type", "farmersdelight:cutting");
if (!group.isEmpty()) {
json.addProperty("group", group);
}
JsonArray jsonIngredients = new JsonArray();
JsonArray jsonOutputs = new JsonArray();
jsonIngredients.add(input.toJson());
outputs.forEach(o -> jsonOutputs.add(o.serialize()));
json.add("ingredients", jsonIngredients);
json.add("tool", toolAction.serialize());
json.add("result", jsonOutputs);
if (sound != null) {
json.addProperty("sound", Registry.SOUND_EVENT.getKey(sound).toString());
}
}
@Override
public @NotNull ResourceLocation getId() {
return id;
}
@Override
public @NotNull RecipeSerializer<?> getType() {
return RecipeSerializer.SHAPELESS_RECIPE; // Irrelevant, we implement serialization ourselves
}
@Override
public JsonObject serializeAdvancement() {
return null;
}
@Override
public ResourceLocation getAdvancementId() {
return null;
}
}
}

View file

@ -0,0 +1,27 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
public record ItemProcessingOutput(ItemStack stack, float chance) implements ProcessingOutput {
@Override
public JsonObject serialize() {
JsonObject json = new JsonObject();
ResourceLocation resourceLocation = Registry.ITEM.getKey(stack.getItem());
json.addProperty("item", resourceLocation.toString());
int count = stack.getCount();
if (count != 1) {
json.addProperty("count", count);
}
if (stack.hasTag()) {
json.add("nbt", JsonParser.parseString(stack.getTag().toString()));
}
if (chance != 1) {
json.addProperty("chance", chance);
}
return json;
}
}

View file

@ -0,0 +1,7 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.gson.JsonObject;
public interface ProcessingOutput {
JsonObject serialize();
}

View file

@ -0,0 +1,7 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.gson.JsonObject;
public interface ToolIngredient {
JsonObject serialize();
}

View file

@ -0,0 +1,33 @@
package at.petrak.hexcasting.interop.patchouli;
import net.minecraft.client.resources.language.I18n;
import vazkii.patchouli.api.IComponentProcessor;
import vazkii.patchouli.api.IVariable;
import vazkii.patchouli.api.IVariableProvider;
public class PatternProcessor implements IComponentProcessor {
private String translationKey;
@Override
public void setup(IVariableProvider vars) {
if (vars.has("header"))
translationKey = vars.get("header").asString();
else {
IVariable key = vars.get("op_id");
String opName = key.asString();
String prefix = "hexcasting.spell.";
boolean hasOverride = I18n.exists(prefix + "book." + opName);
translationKey = prefix + (hasOverride ? "book." : "") + opName;
}
}
@Override
public IVariable process(String key) {
if (key.equals("translation_key")) {
return IVariable.wrap(translationKey);
}
return null;
}
}

View file

@ -0,0 +1,55 @@
package at.petrak.hexcasting.interop.utils;
import at.petrak.hexcasting.api.mod.HexConfig;
import at.petrak.hexcasting.common.items.magic.ItemManaBattery;
import at.petrak.hexcasting.common.lib.HexItems;
import com.google.common.collect.Lists;
import com.mojang.datafixers.util.Pair;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import java.util.List;
public class PhialRecipeStackBuilder {
private static ItemStack makeBattery(int unit, int size) {
return ItemManaBattery.withMana(new ItemStack(HexItems.BATTERY), unit * size, unit * size);
}
public static Pair<List<ItemStack>, List<ItemStack>> createStacks() {
List<ItemStack> inputItems = Lists.newArrayList();
List<ItemStack> outputItems = Lists.newArrayList();
int dust = HexConfig.common().dustManaAmount();
int shard = HexConfig.common().shardManaAmount();
int charged = HexConfig.common().chargedCrystalManaAmount();
if (dust > 0) {
inputItems.add(new ItemStack(HexItems.AMETHYST_DUST, 1));
outputItems.add(makeBattery(dust, 1));
inputItems.add(new ItemStack(HexItems.AMETHYST_DUST, 64));
outputItems.add(makeBattery(dust, 64));
}
if (shard > 0) {
inputItems.add(new ItemStack(Items.AMETHYST_SHARD, 1));
outputItems.add(makeBattery(shard, 1));
inputItems.add(new ItemStack(Items.AMETHYST_SHARD, 64));
outputItems.add(makeBattery(shard, 64));
}
if (charged > 0) {
inputItems.add(new ItemStack(HexItems.CHARGED_AMETHYST, 1));
outputItems.add(makeBattery(charged, 1));
inputItems.add(new ItemStack(HexItems.CHARGED_AMETHYST, 64));
outputItems.add(makeBattery(charged, 64));
}
return new Pair<>(inputItems, outputItems);
}
public static boolean shouldAddRecipe() {
return HexConfig.common().dustManaAmount() > 0 ||
HexConfig.common().shardManaAmount() > 0 ||
HexConfig.common().chargedCrystalManaAmount() > 0;
}
}

View file

@ -1,10 +1,13 @@
@file:JvmName("AccessorWrappers")
package at.petrak.hexcasting.ktxt
import at.petrak.hexcasting.mixin.accessor.AccessorEntity
import at.petrak.hexcasting.mixin.accessor.AccessorLivingEntity
import at.petrak.hexcasting.mixin.accessor.AccessorUseOnContext
import at.petrak.hexcasting.mixin.accessor.AccessorVillager
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.InteractionHand
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.npc.Villager
@ -18,6 +21,18 @@ var LivingEntity.lastHurt: Float
get() = (this as AccessorLivingEntity).`hex$getLastHurt`()
set(value) = (this as AccessorLivingEntity).`hex$setLastHurt`(value)
fun LivingEntity.playHurtSound(source: DamageSource) = (this as AccessorLivingEntity).`hex$playHurtSound`(source)
fun LivingEntity.checkTotemDeathProtection(source: DamageSource) = (this as AccessorLivingEntity).`hex$checkTotemDeathProtection`(source)
val LivingEntity.deathSoundAccessor: SoundEvent? get() = (this as AccessorLivingEntity).`hex$getDeathSound`()
val LivingEntity.soundVolumeAccessor get() = (this as AccessorLivingEntity).`hex$getSoundVolume`()
fun LivingEntity.setHurtWithStamp(source: DamageSource, stamp: Long) = (this as AccessorLivingEntity).apply {
`hex$setLastDamageSource`(source)
`hex$setLastDamageStamp`(stamp)
}
fun Entity.markHurt() = (this as AccessorEntity).`hex$markHurt`()
fun Villager.tellWitnessesThatIWasMurdered(murderer: Entity) = (this as AccessorVillager).`hex$tellWitnessesThatIWasMurdered`(murderer)
@Suppress("FunctionName")

View file

@ -0,0 +1,11 @@
package at.petrak.hexcasting.mixin.accessor;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(Entity.class)
public interface AccessorEntity {
@Invoker("markHurt")
void hex$markHurt();
}

View file

@ -1,8 +1,11 @@
package at.petrak.hexcasting.mixin.accessor;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(LivingEntity.class)
public interface AccessorLivingEntity {
@ -11,4 +14,22 @@ public interface AccessorLivingEntity {
@Accessor("lastHurt")
void hex$setLastHurt(float lastHurt);
@Invoker("playHurtSound")
void hex$playHurtSound(DamageSource source);
@Invoker("checkTotemDeathProtection")
boolean hex$checkTotemDeathProtection(DamageSource source);
@Invoker("getDeathSound")
SoundEvent hex$getDeathSound();
@Invoker("getSoundVolume")
float hex$getSoundVolume();
@Accessor("lastDamageSource")
void hex$setLastDamageSource(DamageSource source);
@Accessor("lastDamageStamp")
void hex$setLastDamageStamp(long stamp);
}

View file

@ -120,8 +120,9 @@ public interface IXplatAbstractions {
<T extends BlockEntity> BlockEntityType<T> createBlockEntityType(BiFunction<BlockPos, BlockState, T> func,
Block... blocks);
boolean tryPlaceFluid(Level level, InteractionHand hand, BlockPos pos, ItemStack stack, Fluid fluid);
boolean tryPlaceFluid(Level level, InteractionHand hand, BlockPos pos, Fluid fluid);
boolean drainAllFluid(Level level, BlockPos pos);
// misc

View file

@ -16,7 +16,6 @@
"item.hexcasting.trinket": "Trinket",
"item.hexcasting.artifact": "Artifact",
"item.hexcasting.battery": "Phial of Media",
"item.hexcasting.manaholder.amount": "N*merical Mana: %s/%s (%.0f%%)",
"item.hexcasting.amethyst_dust": "Amethyst Dust",
"item.hexcasting.charged_amethyst": "Charged Amethyst",
"item.hexcasting.lens": "Scrying Lens",
@ -66,9 +65,8 @@
"item.hexcasting.pride_colorizer_transgender": "Transgender Pigment",
"item.hexcasting.uuid_colorizer": "Soulglimmer Pigment",
"item.hexcasting.creative_unlocker": "The Media Cube",
"item.hexcasting.creative_unlocker.tooltip.0": "Contains %s.",
"item.hexcasting.creative_unlocker.for_emphasis": "INFINITE MEDIA",
"item.hexcasting.creative_unlocker.tooltip.1": "Consume to unlock all %s knowledge.",
"item.hexcasting.creative_unlocker.tooltip": "Consume to unlock all %s knowledge.",
"item.hexcasting.creative_unlocker.mod_name": "Hexcasting",
@ -123,13 +121,14 @@
"hexcasting.tooltip.abacus": "%d",
"hexcasting.tooltip.abacus.reset": "Reset to 0",
"hexcasting.tooltip.abacus.reset.nice": "nice",
"hexcasting.tooltip.lens.impetus.mana": "%s dust",
"hexcasting.tooltip.lens.impetus.storedplayer": "Bound to %s",
"hexcasting.tooltip.lens.impetus.storedplayer.none": "Unbound",
"hexcasting.tooltip.lens.pattern.invalid": "Invalid Pattern",
"hexcasting.tooltip.lens.akashic.bookshelf.location": "Record at %s",
"hexcasting.tooltip.lens.akashic.record.count": "%s iotas stored",
"hexcasting.tooltip.lens.akashic.record.count.single": "%s iota stored",
"hexcasting.tooltip.lens.bee": "%s bees",
"hexcasting.tooltip.lens.bee.single": "%s bee",
"hexcasting.tooltip.brainsweep.min_level": "Level %s or higher",
"hexcasting.tooltip.brainsweep.level": "Level %s",
"hexcasting.tooltip.brainsweep.product": "Mindless Body",
@ -139,19 +138,25 @@
"hexcasting.spelldata.entity.whoknows": "An Entity (this should only show up if this was stored before the 0.5.0 update, use Scribe's Reflection, Scribe's Gambit to fix)",
"hexcasting.spelldata.akashic.nopos": "The owning record does not know of any iota here (this is a bug)",
"hexcasting.tooltip.mana": "%s dust",
"hexcasting.tooltip.mana_amount": "Contains: %s (%s)",
"hexcasting.tooltip.mana_amount.advanced": "Contains: %s/%s (%s)",
"gui.hexcasting.spellcasting": "Hex Grid",
"tag.hexcasting.wands": "Hex Staves",
"tag.hexcasting.akashic_logs": "Edified Logs",
"tag.hexcasting.akashic_planks": "Edified Planks",
"tag.hexcasting.phial_base": "Empty Phials",
"emi.category.hexcasting.brainsweep": "Flay Mind",
"emi.category.hexcasting.craft.battery": "Craft Phial",
"emi.category.hexcasting.edify": "Edify Sapling",
"emi.category.hexcasting.villager_leveling": "Trade Leveling",
"emi.category.hexcasting.villager_profession": "Villager Profession",
"advancement.hexcasting:root": "Hexcasting Research",
"advancement.hexcasting:root.desc": "Find a concentrated form of media growing deep beneath the earth.",
"advancement.hexcasting:root.desc": "Find and mine a concentrated form of media growing deep beneath the earth.",
"advancement.hexcasting:enlightenment": "Achieve Enlightenment",
"advancement.hexcasting:enlightenment.desc": "Go nearly insane from casting a hex using almost all of your health.",
"advancement.hexcasting:enlightenment.desc": "Shatter a barrier by casting a hex using almost all of your health.",
"advancement.hexcasting:wasteful_cast": "Waste Not...",
"advancement.hexcasting:wasteful_cast.desc": "Waste a large amount of media when casting a hex.",
"advancement.hexcasting:big_cast": "... Want Not",
@ -159,7 +164,7 @@
"advancement.hexcasting:y_u_no_cast_angy": "Blind Diversion",
"advancement.hexcasting:y_u_no_cast_angy.desc": "Try to cast a spell from a scroll, but fail.",
"advancement.hexcasting:opened_eyes": "Opened Eyes",
"advancement.hexcasting:opened_eyes.desc": "Have nature take a piece of your mind in payment for a hex.",
"advancement.hexcasting:opened_eyes.desc": "Have nature take a piece of your mind in payment for a hex. What might happen if you let it have more?",
"stat.hexcasting.mana_used": "Media Consumed (in dust)",
"stat.hexcasting.mana_overcasted": "Media Overcast (in dust)",
@ -178,7 +183,10 @@
"hexcasting.pattern.unknown": "Unknown pattern resource location %s",
"hexcasting.debug.mana_withdrawn": "%s - Mana withdrawn: %s",
"hexcasting.debug.mana_withdrawn.with_dust": "%s - Mana withdrawn: %s (%s in dust)",
"hexcasting.debug.mana_inserted": "%s - Mana inserted: %s",
"hexcasting.debug.mana_inserted.with_dust": "%s - Mana inserted: %s (%s in dust)",
"hexcasting.debug.all_mana": "Entire contents",
"hexcasting.debug.infinite_mana": "Infinite",
"hexcasting.message.cant_overcast": "That Hex needed more media than I had... I should double-check my math.",
"hexcasting.message.cant_great_spell": "The spell failed, somehow... am I not skilled enough?",
@ -199,10 +207,40 @@
"hexcasting.subtitles.impetus.fletcher.tick": "Fletcher Impetus ticks",
"hexcasting.subtitles.impetus.cleric.register": "Cleric Impetus dings",
"_comment": "hexcasting.spell.book keys override the name of a pattern in the patchouli book if present",
"hexcasting.spell.book.hexcasting:get_entity_height": "Stadiometer's Prfn.",
"hexcasting.spell.book.hexcasting:get_entity/animal": "Entity Prfn.: Animal",
"hexcasting.spell.book.hexcasting:get_entity/monster": "Entity Prfn.: Monster",
"hexcasting.spell.book.hexcasting:get_entity/item": "Entity Prfn.: Item",
"hexcasting.spell.book.hexcasting:get_entity/player": "Entity Prfn.: Player",
"hexcasting.spell.book.hexcasting:get_entity/living": "Entity Prfn.: Living",
"hexcasting.spell.book.hexcasting:zone_entity": "Zone Dstl.: Any",
"hexcasting.spell.book.hexcasting:zone_entity/animal": "Zone Dstl.: Animal",
"hexcasting.spell.book.hexcasting:zone_entity/monster": "Zone Dstl.: Monster",
"hexcasting.spell.book.hexcasting:zone_entity/item": "Zone Dstl.: Item",
"hexcasting.spell.book.hexcasting:zone_entity/player": "Zone Dstl.: Player",
"hexcasting.spell.book.hexcasting:zone_entity/living": "Zone Dstl.: Living",
"hexcasting.spell.book.hexcasting:zone_entity/not_animal": "Zone Dstl.: Non-Animal",
"hexcasting.spell.book.hexcasting:zone_entity/not_monster": "Zone Dstl.: Non-Monster",
"hexcasting.spell.book.hexcasting:zone_entity/not_item": "Zone Dstl.: Non-Item",
"hexcasting.spell.book.hexcasting:zone_entity/not_player": "Zone Dstl.: Non-Player",
"hexcasting.spell.book.hexcasting:zone_entity/not_living": "Zone Dstl.: Non-Living",
"hexcasting.spell.book.hexcasting:mul_dot": "Multiplicative Dstl.",
"hexcasting.spell.book.hexcasting:div_cross": "Division Dstl.",
"hexcasting.spell.book.hexcasting:arcsin": "Inverse Sine Prfn.",
"hexcasting.spell.book.hexcasting:arccos": "Inverse Cosine Prfn.",
"hexcasting.spell.book.hexcasting:arctan": "Inverse Tangent Prfn.",
"hexcasting.spell.book.hexcasting:const/vec/x": "Vector Rfln. +X/-X",
"hexcasting.spell.book.hexcasting:const/vec/y": "Vector Rfln. +Y/-Y",
"hexcasting.spell.book.hexcasting:const/vec/z": "Vector Rfln. +Z/-Z",
"hexcasting.spell.book.hexcasting:number": "Numerical Reflection",
"hexcasting.spell.book.hexcasting:mask": "Bookkeeper's Gambit",
"hexcasting.spell.hexcasting:get_caster": "Mind's Reflection",
"hexcasting.spell.hexcasting:get_entity_pos": "Compass' Purification",
"hexcasting.spell.hexcasting:get_entity_look": "Alidade's Purification",
"hexcasting.spell.hexcasting:get_entity_height": "Stadiometer's Prfn.",
"hexcasting.spell.hexcasting:get_entity_height": "Stadiometer's Purification",
"hexcasting.spell.hexcasting:get_entity_velocity": "Pace Purification",
"hexcasting.spell.hexcasting:raycast": "Archer's Distillation",
"hexcasting.spell.hexcasting:raycast/axis": "Architect's Distillation",
@ -228,22 +266,22 @@
"hexcasting.spell.hexcasting:construct": "Speaker's Distillation",
"hexcasting.spell.hexcasting:deconstruct": "Speaker's Decomposition",
"hexcasting.spell.hexcasting:get_entity": "Entity Purification",
"hexcasting.spell.hexcasting:get_entity/animal": "Entity Prfn.: Animal",
"hexcasting.spell.hexcasting:get_entity/monster": "Entity Prfn.: Monster",
"hexcasting.spell.hexcasting:get_entity/item": "Entity Prfn.: Item",
"hexcasting.spell.hexcasting:get_entity/player": "Entity Prfn.: Player",
"hexcasting.spell.hexcasting:get_entity/living": "Entity Prfn.: Living",
"hexcasting.spell.hexcasting:zone_entity": "Zone Dstl.: Any",
"hexcasting.spell.hexcasting:zone_entity/animal": "Zone Dstl.: Animal",
"hexcasting.spell.hexcasting:zone_entity/monster": "Zone Dstl.: Monster",
"hexcasting.spell.hexcasting:zone_entity/item": "Zone Dstl.: Item",
"hexcasting.spell.hexcasting:zone_entity/player": "Zone Dstl.: Player",
"hexcasting.spell.hexcasting:zone_entity/living": "Zone Dstl.: Living",
"hexcasting.spell.hexcasting:zone_entity/not_animal": "Zone Dstl.: Non-Animal",
"hexcasting.spell.hexcasting:zone_entity/not_monster": "Zone Dstl.: Non-Monster",
"hexcasting.spell.hexcasting:zone_entity/not_item": "Zone Dstl.: Non-Item",
"hexcasting.spell.hexcasting:zone_entity/not_player": "Zone Dstl.: Non-Player",
"hexcasting.spell.hexcasting:zone_entity/not_living": "Zone Dstl.: Non-Living",
"hexcasting.spell.hexcasting:get_entity/animal": "Entity Purification: Animal",
"hexcasting.spell.hexcasting:get_entity/monster": "Entity Purification: Monster",
"hexcasting.spell.hexcasting:get_entity/item": "Entity Purification: Item",
"hexcasting.spell.hexcasting:get_entity/player": "Entity Purification: Player",
"hexcasting.spell.hexcasting:get_entity/living": "Entity Purification: Living",
"hexcasting.spell.hexcasting:zone_entity": "Zone Distillation: Any",
"hexcasting.spell.hexcasting:zone_entity/animal": "Zone Distillation: Animal",
"hexcasting.spell.hexcasting:zone_entity/monster": "Zone Distillation: Monster",
"hexcasting.spell.hexcasting:zone_entity/item": "Zone Distillation: Item",
"hexcasting.spell.hexcasting:zone_entity/player": "Zone Distillation: Player",
"hexcasting.spell.hexcasting:zone_entity/living": "Zone Distillation: Living",
"hexcasting.spell.hexcasting:zone_entity/not_animal": "Zone Distillation: Non-Animal",
"hexcasting.spell.hexcasting:zone_entity/not_monster": "Zone Distillation: Non-Monster",
"hexcasting.spell.hexcasting:zone_entity/not_item": "Zone Distillation: Non-Item",
"hexcasting.spell.hexcasting:zone_entity/not_player": "Zone Distillation: Non-Player",
"hexcasting.spell.hexcasting:zone_entity/not_living": "Zone Distillation: Non-Living",
"hexcasting.spell.hexcasting:const/null": "Nullary Reflection",
"hexcasting.spell.hexcasting:duplicate": "Gemini Decomposition",
"hexcasting.spell.hexcasting:duplicate_n": "Gemini's Gambit",
@ -253,8 +291,8 @@
"hexcasting.spell.hexcasting:swizzle": "Swindler's Gambit",
"hexcasting.spell.hexcasting:add": "Additive Distillation",
"hexcasting.spell.hexcasting:sub": "Subtractive Distillation",
"hexcasting.spell.hexcasting:mul_dot": "Multiplicative Dstl.",
"hexcasting.spell.hexcasting:div_cross": "Division Dstl.",
"hexcasting.spell.hexcasting:mul_dot": "Multiplicative Distillation",
"hexcasting.spell.hexcasting:div_cross": "Division Distillation",
"hexcasting.spell.hexcasting:abs_len": "Length Purification",
"hexcasting.spell.hexcasting:pow_proj": "Power Distillation",
"hexcasting.spell.hexcasting:construct_vec": "Vector Exaltation",
@ -281,9 +319,9 @@
"hexcasting.spell.hexcasting:sin": "Sine Purification",
"hexcasting.spell.hexcasting:cos": "Cosine Purification",
"hexcasting.spell.hexcasting:tan": "Tangent Purification",
"hexcasting.spell.hexcasting:arcsin": "Inverse Sine Prfn.",
"hexcasting.spell.hexcasting:arccos": "Inverse Cosine Prfn.",
"hexcasting.spell.hexcasting:arctan": "Inverse Tangent Prfn.",
"hexcasting.spell.hexcasting:arcsin": "Inverse Sine Purification",
"hexcasting.spell.hexcasting:arccos": "Inverse Cosine Purification",
"hexcasting.spell.hexcasting:arctan": "Inverse Tangent Purification",
"hexcasting.spell.hexcasting:random": "Entropy Reflection",
"hexcasting.spell.hexcasting:logarithm": "Logarithmic Distillation",
"hexcasting.spell.hexcasting:coerce_axial": "Axial Purification",
@ -302,7 +340,7 @@
"hexcasting.spell.hexcasting:recharge": "Recharge Item",
"hexcasting.spell.hexcasting:erase": "Erase Item",
"hexcasting.spell.hexcasting:create_water": "Create Water",
"hexcasting.spell.hexcasting:destroy_water": "Destroy Water",
"hexcasting.spell.hexcasting:destroy_water": "Destroy Liquid",
"hexcasting.spell.hexcasting:ignite": "Ignite Block",
"hexcasting.spell.hexcasting:extinguish": "Extinguish Area",
"hexcasting.spell.hexcasting:conjure_block": "Conjure Block",
@ -353,16 +391,13 @@
"hexcasting.spell.hexcasting:const/vec/nx": "Vector Reflection -X",
"hexcasting.spell.hexcasting:const/vec/ny": "Vector Reflection -Y",
"hexcasting.spell.hexcasting:const/vec/nz": "Vector Reflection -Z",
"hexcasting.spell.hexcasting:const/vec/x": "Vector Rfln. +X/-X",
"hexcasting.spell.hexcasting:const/vec/y": "Vector Rfln. +Y/-Y",
"hexcasting.spell.hexcasting:const/vec/z": "Vector Rfln. +Z/-Z",
"hexcasting.spell.hexcasting:const/vec/0": "Vector Reflection Zero",
"hexcasting.spell.hexcasting:const/double/pi": "Arc's Reflection",
"hexcasting.spell.hexcasting:const/double/tau": "Circle's Reflection",
"hexcasting.spell.hexcasting:const/double/e": "Euler's Reflection",
"hexcasting.spell.hexcasting:number": "Numerical Reflection",
"hexcasting.spell.hexcasting:mask": "Bookkeeper's Gambit",
"hexcasting.spell.unknown": "Special Handler",
"hexcasting.spell.hexcasting:number": "Numerical Reflection: %s",
"hexcasting.spell.hexcasting:mask": "Bookkeeper's Gambit: %s",
"hexcasting.spell.null": "Unknown Pattern",
"hexcasting.spell.hexcasting:interop/gravity/get": "Gravitational Purification",
"hexcasting.spell.hexcasting:interop/gravity/set": "Alter Gravity",
@ -624,7 +659,7 @@
"hexcasting.page.hexcasting.2": "To do this, I can craft one of three types of magic items: $(l:items/hexcasting)$(item)Cyphers/$, $(l:items/hexcasting)$(item)Trinkets/$, or $(l:items/hexcasting)$(item)Artifacts/$. All of them hold the patterns of a given _Hex inside, along with a small battery containing _media.$(br2)Simply holding one and pressing $(thing)$(k:use)/$ will cast the patterns inside, as if the holder had cast them out of a staff, using its internal battery.",
"hexcasting.page.hexcasting.3": "Each item has its own quirks:$(br2)$(l:items/hexcasting)$(item)Cyphers/$ are fragile, destroyed after their internal _media reserves are gone, and $(italic)cannot/$ be recharged;$(br2)$(l:items/hexcasting)$(item)Trinkets/$ can be cast as much as the holder likes, as long as there's enough _media left, but become useless afterwards until recharged;",
"hexcasting.page.hexcasting.4": "$(l:items/hexcasting)$(item)Artifacts/$ are the most powerful of all-- after their _media is depleted, they can use $(l:items/amethyst)$(item)Amethyst/$ from the holder's inventory to pay for the _Hex, just as I do when casting with a $(l:items/staff)$(item)Staff/$. Of course, this also means the spell might consume their mind if there's not enough $(l:items/amethyst)$(item)Amethyst/$.$(br2)Once I've made an empty magic item in a mundane crafting bench, I infuse the _Hex into it using (what else but) a spell appropriate to the item. $(l:patterns/spells/hexcasting)I've catalogued the patterns here./$",
"hexcasting.page.hexcasting.5": "Each infusion spell requires an entity and a list of patterns on the stack. The entity must be a _media-holding item entity (i.e. $(l:items/amethyst)$(item)amethyst/$ crystals, dropped on the ground); the entity is consumed and forms the battery.$(br2)Usefully, it seems that the _media in the battery is not consumed in chunks as it is when casting with a $(l:items/staff)$(item)Staff/$-- rather, the _media \"melts down\" into one continuous pool. Thus, if I store a _Hex that only costs one $(l:items/amethyst)$(item)Amethyst Dust/$'s worth of mana, a $(l:items/amethyst)$(item)Charged Crystal/$ used as the battery will allow me to cast it 10 times.",
"hexcasting.page.hexcasting.5": "Each infusion spell requires an entity and a list of patterns on the stack. The entity must be a _media-holding item entity (i.e. $(l:items/amethyst)$(item)amethyst/$ crystals, dropped on the ground); the entity is consumed and forms the battery.$(br2)Usefully, it seems that the _media in the battery is not consumed in chunks as it is when casting with a $(l:items/staff)$(item)Staff/$-- rather, the _media \"melts down\" into one continuous pool. Thus, if I store a _Hex that only costs one $(l:items/amethyst)$(item)Amethyst Dust/$'s worth of media, a $(l:items/amethyst)$(item)Charged Crystal/$ used as the battery will allow me to cast it 10 times.",
"hexcasting.page.hexcasting.crafting.desc": "$(italic)We have a saying in our field: \"Magic isn't\". It doesn't \"just work,\" it doesn't respond to your thoughts, you can't throw fireballs or create a roast dinner from thin air or turn a bunch of muggers into frogs and snails./$",
"hexcasting.entry.phials": "Phials of Media",
@ -697,7 +732,7 @@
"hexcasting.entry.akashiclib": "Akashic Libraries",
"hexcasting.page.akashiclib.1": "I KNOW SO MUCH it is ONLY RIGHT to have a place to store it all. Information can be stored in books but it is oh so so so so $(italic)slow/$ to write by hand and read by eye. I demand BETTER. And so I shall MAKE better.$(br2)... I am getting worse ... do not know if I have time to write everything bursting through my head before expiring.",
"hexcasting.page.akashiclib.2": "The library. Here. My plans.$(br2)Like how patterns are associated with actions, I can associate my own patterns with iotas in any way I choose. An $(l:greatwork/akashiclib)$(item)Akashic Record/$ controls the library, and each $(l:greatwork/akashiclib)$(item)Akashic Bookshelf/$ stores one pattern mapped to one iota. These must all be directly connected together, touching. An $(l:greatwork/akashiclib)$(item)Akashic Ligature/$ doesn't do anything but count as a connecting block, to extend the size of my library.",
"hexcasting.page.akashiclib.2": "The library. Here. My plans.$(br2)Like how patterns are associated with actions, I can associate my own patterns with iotas in any way I choose. An $(l:greatwork/akashiclib)$(item)Akashic Record/$ controls the library, and each $(l:greatwork/akashiclib)$(item)Akashic Bookshelf/$ stores one pattern mapped to one iota. These must all be directly connected together, touching, within 32 blocks. An $(l:greatwork/akashiclib)$(item)Akashic Ligature/$ doesn't do anything but count as a connecting block, to extend the size of my library.",
"hexcasting.page.akashiclib.akashic_record": "Allocating and assigning patterns is simple but oh so boring. I have better things to do. I will need a mind well-used to its work for the extraction to stay sound.",
"hexcasting.page.akashiclib.3": "Then to operate the library is simple, the patterns are routed through the librarian and it looks them up and returns the iota to you. Two actions do the work. $(l:patterns/akashic_patterns)Notes here/$.$(br2)Using an empty $(l:items/scroll)$(item)scroll/$ on a bookshelf copies the pattern there onto the $(l:items/scroll)$(item)scroll/$. Sneaking and using an empty hand clears the datum in the shelf.",
@ -723,7 +758,6 @@
"hexcasting.page.basics_pattern.raycast/entity": "Like $(l:patterns/basics#hexcasting:raycast)$(action)Archer's Distillation/$, but instead returns the $(italic)entity/$ I am looking at. Costs a negligible amount of _media.",
"hexcasting.entry.numbers": "Number Literals",
"hexcasting.page.numbers.1.header": "Numerical Reflection",
"hexcasting.page.numbers.1": "Irritatingly, there is no easy way to draw numbers. Here is the method Nature deigned to give us.",
"hexcasting.page.numbers.2": "First, I draw one of the two shapes shown on the other page. Next, the $(italic)angles/$ following will modify a running count starting at 0.$(li)Forward: Add 1$(li)Left: Add 5$(li)Right: Add 10$(li)Sharp Left: Multiply by 2$(li)Sharp Right: Divide by 2.$(br)The clockwise version of the pattern, on the right of the other page, will negate the value at the very end. (The left-hand counter-clockwise version keeps the number positive).$(p)Once I finish drawing, the number's pushed to the top of the stack.",
"hexcasting.page.numbers.example.10.header": "Example 1",
@ -797,7 +831,6 @@
"hexcasting.page.stackmanip.splat": "Remove the list at the top of the stack, then push its contents to the stack.",
"hexcasting.page.stackmanip.duplicate": "Duplicates the top iota of the stack.",
"hexcasting.page.stackmanip.duplicate_n": "Removes the number at the top of the stack, then copies the top iota of the stack that number of times. (A count of 2 results in two of the iota on the stack, not three.)",
"hexcasting.page.stackmanip.mask.header": "Bookkeeper's Gambits",
"hexcasting.page.stackmanip.mask.1": "An infinite family of actions that keep or remove elements at the top of the stack based on the sequence of dips and lines.",
"hexcasting.page.stackmanip.mask.2": "Assuming that I draw a Bookkeeper's Gambit pattern left-to-right, the number of iotas the action will require is determined by the horizontal distance covered by the pattern. From deepest in the stack to shallowest, a flat line will keep the iota, whereas a triangle dipping down will remove it.$(br2)If my stack contains $(italic)0, 1, 2/$ from deepest to shallowest, drawing the first pattern opposite will give me $(italic)1/$, the second will give me $(italic)0/$, and the third will give me $(italic)0, 2/$ (the 0 at the bottom is left untouched).",
"hexcasting.page.stackmanip.swizzle.1": "Rearranges the top elements of the stack based on the given numerical code, which is the index of the permutation wanted. Costs an amount of media that starts negligible and scales up as the numerical code does.",
@ -901,7 +934,7 @@
"hexcasting.entry.basic_spell": "Basic Spells",
"hexcasting.page.basic_spell.explode.1": "Remove a number and vector from the stack, then create an explosion at the given location with the given power.",
"hexcasting.page.basic_spell.explode.2": "A power of 3 is about as much as a Creeper's blast; 4 is about as much as a TNT blast. Nature refuses to give me a blast of more than 10 power, though.$(br2)Strangely, this explosion doesn't seem to harm me. Perhaps it's because $(italic)I/$ am the one exploding?$(br2)Costs a negligible amount at power 0, plus 3 extra $(l:items/amethyst)$(item)Amethyst Dust/$s per point of explosion power.",
"hexcasting.page.basic_spell.explode.2": "A power of 3 is about as much as a Creeper's blast; 4 is about as much as a TNT blast. Nature refuses to give me a blast of more than 10 power, though.$(br2)Strangely, this explosion doesn't seem to harm me. Perhaps it's because $(italic)I/$ am the one exploding?$(br2)Costs a negligible amount at power 0, plus 3 extra $(l:items/amethyst)$(item)Amethyst Dust/$ per point of explosion power.",
"hexcasting.page.basic_spell.explode.fire.1": "Remove a number and vector from the stack, then create a fiery explosion at the given location with the given power.",
"hexcasting.page.basic_spell.explode.fire.2": "Costs one $(l:items/amethyst)$(item)Amethyst Dust/$, plus about 3 extra $(l:items/amethyst)$(item)Amethyst Dust/$s per point of explosion power. Otherwise, the same as $(l:patterns/spells/basic#hexcasting:explode)$(action)Explosion/$, except with fire.",
"hexcasting.page.basic_spell.add_motion": "Remove an entity and direction from the stack, then give a shove to the given entity in the given direction. The strength of the impulse is determined by the length of the vector.$(br)Costs units of $(l:items/amethyst)$(item)Amethyst Dust/$ equal to the square of the length of the vector, plus one for every Impulse except the first targeting an entity.",
@ -912,14 +945,14 @@
"hexcasting.entry.blockworks": "Blockworks",
"hexcasting.page.blockworks.place_block": "Remove a location from the stack, then pick a block item and place it at the given location.$(br)Costs a negligible amount of _media.",
"hexcasting.page.blockworks.break_block": "Remove a location from the stack, then break the block at the given location. This spell can break nearly anything a Diamond Pickaxe can break.$(br)Costs a bit more than one $(l:items/amethyst)$(item)Amethyst Dust/$.",
"hexcasting.page.blockworks.create_water": "Summon a block of water (or insert a bucket's worth) into a block at the given position. Costs about one $(l:items/amethyst)$(item)Amethyst Dust/$.",
"hexcasting.page.blockworks.destroy_water": "Destroy a great deal of liquid (not just water) around the given position. Costs about two $(l:items/amethyst)$(item)Charged Amethyst/$.",
"hexcasting.page.blockworks.create_water": "Summon a block of water (or insert up to a bucket's worth) into a block at the given position. Costs about one $(l:items/amethyst)$(item)Amethyst Dust/$.",
"hexcasting.page.blockworks.destroy_water": "Drains either a liquid container at, or a body of liquid around, the given position. Costs about two $(l:items/amethyst)$(item)Charged Amethyst/$.",
"hexcasting.page.blockworks.conjure_block": "Conjure an ethereal, but solid, block that sparkles with my pigment at the given position. Costs about one $(l:items/amethyst)$(item)Amethyst Dust/$.",
"hexcasting.page.blockworks.conjure_light": "Conjure a magical light that softly glows with my pigment at the given position. Costs about one $(l:items/amethyst)$(item)Amethyst Dust/$.",
"hexcasting.page.blockworks.bonemeal": "Encourage a plant or sapling at the target position to grow, as if $(item)Bonemeal/$ was applied. Costs a bit more than one $(l:items/amethyst)$(item)Amethyst Dust/$.",
"hexcasting.page.blockworks.edify": "Forcibly infuse _media into the sapling at the target position, causing it to grow into an $(l:items/edified)$(thing)Edified Tree/$. Costs about one $(l:items/amethyst)$(item)Charged Crystal/$.",
"hexcasting.page.blockworks.edify": "Forcibly infuse _media into the sapling at the target position, causing it to grow into an $(l:items/edified)$(thing)Edified Tree/$. Costs about one $(l:items/amethyst)$(item)Charged Amethyst/$.",
"hexcasting.page.blockworks.ignite": "Start a fire on top of the given location, as if a $(item)Fire Charge/$ was applied. Costs about one $(l:items/amethyst)$(item)Amethyst Dust/$.",
"hexcasting.page.blockworks.extinguish": "Extinguish blocks in a large area. Costs about six $(l:items/amethyst)$(item)Amethyst Dust/$s.",
"hexcasting.page.blockworks.extinguish": "Extinguish blocks in a large area. Costs about six $(l:items/amethyst)$(item)Amethyst Dust/$.",
"hexcasting.entry.nadirs": "Nadirs",
"hexcasting.page.nadirs.1": "This family of spells all impart a negative potion effect upon an entity. They all take an entity, the recipient, and one or two numbers, the first being the duration and the second, if present, being the potency (starting at 1).$(br2)Each one has a \"base cost;\" the actual cost is equal to that base cost, multiplied by the potency squared.",
@ -950,7 +983,7 @@
"hexcasting.page.colorize": "I must be holding a $(l:items/pigments)$(item)Pigment/$ in my other hand to cast this spell. When I do, it will consume the dye and permanently change my mind's coloration (at least, until I cast the spell again). Costs about one $(l:items/amethyst)$(item)Amethyst Dust/$.",
"hexcasting.page.create_lava.1": "Summon a block of lava or insert a bucket's worth into a block at the given position. Costs about one $(l:items/amethyst)$(item)Charged Amethyst/$.",
"hexcasting.page.create_lava.1": "Summon a block of lava (or insert up to a bucket's worth) into a block at the given position. Costs about one $(l:items/amethyst)$(item)Charged Amethyst/$.",
"hexcasting.page.create_lava.2": "It may be advisable to keep my knowledge of this spell secret. A certain faction of botanists get... touchy about it, or so I've heard.$(br2)Well, no one said tracing the deep secrets of the universe was going to be an easy time.",
"hexcasting.entry.weather_manip": "Weather Manipulation",
@ -962,7 +995,7 @@
"hexcasting.page.flight.2": "The entity (which must be a player) will be endowed with flight. The first number is the number of seconds they may fly for, and the second number is the radius of the zone they may fly in. If the recipient exits that zone, or their timer runs out while midair, the gravity that they spurned will get its revenge. Painfully.$(br2)It costs one quarter of an $(l:items/amethyst)$(item)Amethyst Dust/$, per meter of radius, per second in flight.",
"hexcasting.page.teleport.1": "Far more powerful than $(l:patterns/spells/basic#hexcasting:blink)$(action)Blink/$, this spell lets me teleport nearly anywhere in the entire world! There does seem to be a limit, but it is $(italic)much/$ greater than the normal radius of influence I am used to.",
"hexcasting.page.teleport.2": "The entity will be teleported by the given vector, which is an offset from its given position. No matter the distance, it always seems to cost about ten $(l:items/amethyst)$(item)Charged Amethyst/$.$(br2)The transference is not perfect, and it seems when teleporting something as complex as a player, their inventory doesn't $(italic)quite/$ stay attached, and tends to splatter everywhere at the destination.",
"hexcasting.page.teleport.2": "The entity will be teleported by the given vector, which is an offset from its given position. No matter the distance, it always seems to cost about ten $(l:items/amethyst)$(item)Charged Amethyst/$.$(br2)The transference is not perfect, and it seems when teleporting something as complex as a player, their inventory doesn't $(italic)quite/$ stay attached, and tends to splatter everywhere at the destination. In addition, the target will be forcibly removed from anything inanimate they are riding or sitting on ... but I've read scraps that suggest animals can come along for the ride, so to speak.",
"hexcasting.entry.zeniths": "Zeniths",
"hexcasting.page.zeniths.1": "This family of spells all impart a positive potion effect upon an entity, similar to the $(l:patterns/spells/nadirs)$(action)Nadirs/$. However, these have their _media costs increase with the $(italic)cube/$ of the potency.",
@ -988,13 +1021,13 @@
"hexcasting.entry.interop.gravity": "Gravity Changer",
"hexcasting.page.interop.gravity.1": "I have discovered actions to get and set an entity's gravity. I find them interesting, if slightly nauseating.$(br2)Interestingly, although $(l:patterns/great_spells/flight)$(action)Flight/$ is a great spell, and manipulates gravity similarly, these are not. It baffles me why... Perhaps the mod developer wanted players to have fun, for once.",
"hexcasting.page.interop.gravity.get": "Get the main direction gravity pulls the given entity in, as a unit vector. For most entities, this will be down: [0, -1, 0].",
"hexcasting.page.interop.gravity.set": "Set the main direction gravity pulls the given entity in. The given vector will be coerced into the nearest axis, as per $(l:patterns/math#hexcasting:coerce_axial)$(action)Axial Purification/$. Costs about 1 $(item)Charged Amethyst Crystal/$.",
"hexcasting.page.interop.gravity.get": "Get the main direction gravity pulls the given entity in, as a unit vector. For most entities, this will be down, <0, -1, 0>.",
"hexcasting.page.interop.gravity.set": "Set the main direction gravity pulls the given entity in. The given vector will be coerced into the nearest axis, as per $(l:patterns/math#hexcasting:coerce_axial)$(action)Axial Purification/$. Costs about one $(l:items/amethyst)$(item)Charged Amethyst/$.",
"hexcasting.entry.interop.pehkui": "Pehkui",
"hexcasting.page.interop.pehkui.1": "I have discovered methods of changing the size of entities, and querying how much larger or smaller they are than normal.",
"hexcasting.page.interop.pehkui.get": "Get the scale of the entity, as a proportion of their normal size. For most entities, this will be 1.",
"hexcasting.page.interop.pehkui.set": "Set the scale of the entity, passing in a proportion of their normal size. Costs about 1 $(item)Amethyst Shard/$.",
"hexcasting.page.interop.pehkui.set": "Set the scale of the entity, passing in a proportion of their normal size. Costs about one $(l:items/amethyst)$(item)Amethyst Shard/$.",
"": ""
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -8,7 +8,7 @@
"pages": [
{
"type": "hexcasting:manual_pattern",
"header": "hexcasting.spell.hexcasting:const/vec/x",
"op_id": "hexcasting:const/vec/x",
"anchor": "hexcasting:const/vec/x",
"input": "",
"output": "vector",
@ -28,7 +28,7 @@
},
{
"type": "hexcasting:manual_pattern",
"header": "hexcasting.spell.hexcasting:const/vec/y",
"op_id": "hexcasting:const/vec/y",
"anchor": "hexcasting:const/vec/y",
"input": "",
"output": "vector",
@ -48,7 +48,7 @@
},
{
"type": "hexcasting:manual_pattern",
"header": "hexcasting.spell.hexcasting:const/vec/z",
"op_id": "hexcasting:const/vec/z",
"anchor": "hexcasting:const/vec/z",
"input": "",
"output": "vector",

View file

@ -9,7 +9,9 @@
{
"type": "hexcasting:pattern",
"op_id": "hexcasting:sentinel/create/great",
"text": "hexcasting.page.greater_sentinel.1"
"text": "hexcasting.page.greater_sentinel.1",
"input": "vector",
"output": ""
},
{
"type": "patchouli:text",

View file

@ -8,7 +8,7 @@
"pages": [
{
"type": "hexcasting:manual_pattern",
"header": "hexcasting.page.numbers.1.header",
"op_id": "hexcasting:number",
"anchor": "Numbers",
"input": "",
"output": "number",

View file

@ -68,8 +68,8 @@
},
{
"type": "hexcasting:manual_pattern",
"header": "hexcasting.page.stackmanip.mask.header",
"anchor": "Numbers",
"op_id": "hexcasting:mask",
"anchor": "hexcasting:mask",
"input": "many",
"output": "many",
"text": "hexcasting.page.stackmanip.mask.1",

View file

@ -1,8 +1,9 @@
{
"processor": "at.petrak.hexcasting.interop.patchouli.PatternProcessor",
"components": [
{
"type": "patchouli:header",
"text": "#header",
"text": "#translation_key",
"x": -1,
"y": -1
},

View file

@ -1,8 +1,9 @@
{
"processor": "at.petrak.hexcasting.interop.patchouli.PatternProcessor",
"components": [
{
"type": "patchouli:header",
"text": "#header",
"text": "#translation_key",
"x": -1,
"y": -1
},

View file

@ -1,8 +1,9 @@
{
"processor": "at.petrak.hexcasting.interop.patchouli.PatternProcessor",
"components": [
{
"type": "patchouli:header",
"text": "hexcasting.spell.#op_id#",
"text": "#translation_key",
"x": -1,
"y": -1
},

View file

@ -0,0 +1,30 @@
{
"replace": false,
"values": [
{
"id": "create:contraption",
"required": false
},
{
"id": "create:stationary_contraption",
"required": false
},
{
"id": "create:gantry_contraption",
"required": false
},
{
"id": "create:carriage_contraption",
"required": false
},
"minecraft:pig",
"minecraft:strider",
"minecraft:horse",
"minecraft:skeleton_horse",
"minecraft:zombie_horse",
"minecraft:mule",
"minecraft:donkey",
"minecraft:llama",
"minecraft:trader_llama"
]
}

Some files were not shown because too many files have changed in this diff Show more