diff --git a/.gitignore b/.gitignore index 2da9d42dbf..dc8ca90ac5 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,10 @@ eclipse run .DS_Store +# Idk what prompted gradle to create this folder +Fabric/Fabric + + # MacOS moment .DS_Store diff --git a/Common/build.gradle b/Common/build.gradle index d5e6128c84..947b7269a9 100644 --- a/Common/build.gradle +++ b/Common/build.gradle @@ -28,6 +28,8 @@ repositories { url = "https://modmaven.dev" } + maven { url "https://maven.shedaniel.me/" } + } dependencies { @@ -37,6 +39,8 @@ dependencies { compileOnly "at.petra-k.paucal:paucal-common-$minecraftVersion:$paucalVersion" compileOnly "vazkii.patchouli:Patchouli-xplat:$minecraftVersion-$patchouliVersion-SNAPSHOT" + compileOnly "com.samsthenerd.inline:inline-forge:$minecraftVersion-$inlineVersion" + compileOnly "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" testCompileOnly "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" diff --git a/Common/src/generated/resources/.cache/e5c5eb35b4ba40351ecb7d9f04c3527f2f5779b0 b/Common/src/generated/resources/.cache/e5c5eb35b4ba40351ecb7d9f04c3527f2f5779b0 index d97c48d9f5..4c5fd592d7 100644 --- a/Common/src/generated/resources/.cache/e5c5eb35b4ba40351ecb7d9f04c3527f2f5779b0 +++ b/Common/src/generated/resources/.cache/e5c5eb35b4ba40351ecb7d9f04c3527f2f5779b0 @@ -1,6 +1,7 @@ -// 1.20.1 2023-12-24T17:59:13.7659226 Advancements +// 1.20.1 2024-10-01T23:18:15.2302045 Advancements 4016b178322c4784c12c66c227d5b4ff2e43d32d data/hexcasting/advancements/aaa_wasteful_cast.json 6469361b24f473b4d7d900828e6bbf2bdabf916b data/hexcasting/advancements/aab_big_cast.json +b4b921ec01322795b41e88406685c3ab7c7d09cc data/hexcasting/advancements/creative_unlocker.json e02605ac2dff5c426e1d0a58d46daefecda11946 data/hexcasting/advancements/enlightenment.json e2679742ac4e23ba4c79c17d209f16d42d7bccd8 data/hexcasting/advancements/lore.json 6c3fc955783d450e12494b193e985a403b203f0a data/hexcasting/advancements/lore/cardamom1.json diff --git a/Common/src/generated/resources/data/hexcasting/advancements/creative_unlocker.json b/Common/src/generated/resources/data/hexcasting/advancements/creative_unlocker.json new file mode 100644 index 0000000000..83470d5f2e --- /dev/null +++ b/Common/src/generated/resources/data/hexcasting/advancements/creative_unlocker.json @@ -0,0 +1,39 @@ +{ + "parent": "hexcasting:root", + "criteria": { + "has_creative_unlocker": { + "conditions": { + "items": [ + { + "items": [ + "hexcasting:creative_unlocker" + ] + } + ] + }, + "trigger": "minecraft:inventory_changed" + } + }, + "display": { + "announce_to_chat": false, + "background": "minecraft:textures/block/calcite.png", + "description": { + "translate": "advancement.hexcasting:creative_unlocker.desc" + }, + "frame": "task", + "hidden": true, + "icon": { + "item": "hexcasting:creative_unlocker" + }, + "show_toast": true, + "title": { + "translate": "advancement.hexcasting:creative_unlocker" + } + }, + "requirements": [ + [ + "has_creative_unlocker" + ] + ], + "sends_telemetry_event": true +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt index 26f39da2f3..c6dda37c9a 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt @@ -199,6 +199,18 @@ fun List.getPositiveInt(idx: Int, argc: Int = 0): Int { throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int.positive") } +fun List.getPositiveLong(idx: Int, argc: Int = 0): Long { + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is DoubleIota) { + val double = x.double + val rounded = double.roundToLong() + if (abs(double - rounded) <= DoubleIota.TOLERANCE && rounded >= 0) { + return rounded + } + } + throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int.positive") +} + fun List.getPositiveIntUnder(idx: Int, max: Int, argc: Int = 0): Int { val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } if (x is DoubleIota) { diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/ConstMediaAction.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/ConstMediaAction.kt index a907764c7d..276ee73122 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/ConstMediaAction.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/ConstMediaAction.kt @@ -7,6 +7,7 @@ import at.petrak.hexcasting.api.casting.eval.vm.CastingImage import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs +import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughMedia import at.petrak.hexcasting.common.lib.hex.HexEvalSounds /** @@ -27,6 +28,8 @@ interface ConstMediaAction : Action { override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { val stack = image.stack.toMutableList() + if (env.extractMedia(this.mediaCost, true) > 0) + throw MishapNotEnoughMedia(this.mediaCost) if (this.argc > stack.size) throw MishapNotEnoughArgs(this.argc, stack.size) val args = stack.takeLast(this.argc) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/SpellAction.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/SpellAction.kt index bcea2cd954..fcf17d08d0 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/SpellAction.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/SpellAction.kt @@ -9,6 +9,7 @@ import at.petrak.hexcasting.api.casting.eval.vm.CastingImage import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs +import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughMedia import at.petrak.hexcasting.common.lib.hex.HexEvalSounds import net.minecraft.nbt.CompoundTag @@ -44,6 +45,8 @@ interface SpellAction : Action { val sideEffects = mutableListOf() + if (env.extractMedia(result.cost, true) > 0) + throw MishapNotEnoughMedia(result.cost) if (result.cost > 0) sideEffects.add(OperatorSideEffect.ConsumeMedia(result.cost)) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java index 874428fe7c..9157063a97 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironment.java @@ -19,6 +19,7 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3; @@ -56,11 +57,14 @@ public static void addCreateEventListener(BiConsumer listener) { - createEventListeners.add((env, data) -> {listener.accept(env);}); + createEventListeners.add((env, data) -> { + listener.accept(env); + }); } private boolean createEventTriggered = false; @@ -76,7 +80,8 @@ public final void triggerCreateEvent(CompoundTag userData) { protected final ServerLevel world; - protected Map, @NotNull CastingEnvironmentComponent> componentMap = new HashMap<>(); + protected Map, @NotNull CastingEnvironmentComponent> componentMap = + new HashMap<>(); private final List postExecutions = new ArrayList<>(); private final List postCasts = new ArrayList<>(); @@ -103,16 +108,20 @@ public int maxOpCount() { *

* Implementations should NOT rely on this in general, use the methods on this class instead. * This is mostly for spells (flight, etc) + * * @deprecated as of build 0.11.1-7-pre-619 you are recommended to use {@link #getCastingEntity} */ - @Deprecated(since="0.11.1-7-pre-619") + @Deprecated(since = "0.11.1-7-pre-619") @Nullable public ServerPlayer getCaster() { return getCastingEntity() instanceof ServerPlayer sp ? sp : null; - }; + } + + ; /** * Gets the caster. Can be null if {@link #getCaster()} is also null + * * @return the entity casting */ @Nullable @@ -237,12 +246,12 @@ public boolean isEnlightened() { * If there was enough media found, it will return less or equal to zero; if there wasn't, it will be * positive. */ - public long extractMedia(long cost) { + public long extractMedia(long cost, boolean simulate) { for (var extractMediaComponent : preMediaExtract) - cost = extractMediaComponent.onExtractMedia(cost); - cost = extractMediaEnvironment(cost); + cost = extractMediaComponent.onExtractMedia(cost, simulate); + cost = extractMediaEnvironment(cost, simulate); for (var extractMediaComponent : postMediaExtract) - cost = extractMediaComponent.onExtractMedia(cost); + cost = extractMediaComponent.onExtractMedia(cost, simulate); return cost; } @@ -252,7 +261,7 @@ public long extractMedia(long cost) { * If there was enough media found, it will return less or equal to zero; if there wasn't, it will be * positive. */ - protected abstract long extractMediaEnvironment(long cost); + protected abstract long extractMediaEnvironment(long cost, boolean simulate); /** * Get if the vec is close enough, to the player or sentinel ... @@ -298,7 +307,12 @@ public final boolean isVecInAmbit(Vec3 vec) { } public final boolean isEntityInRange(Entity e) { - return (e instanceof Player && HexConfig.server().trueNameHasAmbit()) || (this.isVecInWorld(e.position()) && this.isVecInRange(e.position())); + return isEntityInRange(e, false); + } + + public final boolean isEntityInRange(Entity e, boolean ignoreTruenameAmbit) { + boolean truenameCheat = !ignoreTruenameAmbit && (e instanceof Player && HexConfig.server().trueNameHasAmbit()); + return truenameCheat || (this.isVecInWorld(e.position()) && this.isVecInRange(e.position())); } /** @@ -360,11 +374,89 @@ public InteractionHand getOtherHand() { */ protected abstract List getUsableStacks(StackDiscoveryMode mode); + protected List getUsableStacksForPlayer(StackDiscoveryMode mode, @Nullable InteractionHand castingHand + , ServerPlayer caster) { + return switch (mode) { + case QUERY -> { + var out = new ArrayList(); + + if (castingHand == null) { + var mainhand = caster.getItemInHand(InteractionHand.MAIN_HAND); + if (!mainhand.isEmpty()) { + out.add(mainhand); + } + + var offhand = caster.getItemInHand(InteractionHand.OFF_HAND); + if (!offhand.isEmpty()) { + out.add(offhand); + } + } else { + var offhand = caster.getItemInHand(HexUtils.otherHand(castingHand)); + if (!offhand.isEmpty()) { + out.add(offhand); + } + } + + // If we're casting from the main hand, try to pick from the slot one to the right of the selected slot + // Otherwise, scan the hotbar left to right + var anchorSlot = castingHand != InteractionHand.OFF_HAND + ? (caster.getInventory().selected + 1) % 9 + : 0; + + + for (int delta = 0; delta < 9; delta++) { + var slot = (anchorSlot + delta) % 9; + out.add(caster.getInventory().getItem(slot)); + } + + yield out; + } + case EXTRACTION -> { + // https://wiki.vg/Inventory is WRONG + // slots 0-8 are the hotbar + // for what purpose i cannot imagine + // http://redditpublic.com/images/b/b2/Items_slot_number.png looks right + // and offhand is 150 Inventory.java:464 + var out = new ArrayList(); + + // First, the inventory backwards + // We use inv.items here to get the main inventory, but not offhand or armor + Inventory inv = caster.getInventory(); + for (int i = inv.items.size() - 1; i >= 0; i--) { + if (i != inv.selected) { + out.add(inv.items.get(i)); + } + } + + // then the offhand, then the selected hand + out.addAll(inv.offhand); + out.add(inv.getSelected()); + + yield out; + } + }; + } + /** * Get the primary/secondary item stacks this env can use (i.e. main hand and offhand for the player). */ protected abstract List getPrimaryStacks(); + protected List getPrimaryStacksForPlayer(InteractionHand castingHand, ServerPlayer caster) { + var primaryItem = caster.getItemInHand(castingHand); + + if (primaryItem.isEmpty()) + primaryItem = ItemStack.EMPTY.copy(); + + var secondaryItem = caster.getItemInHand(HexUtils.otherHand(castingHand)); + + if (secondaryItem.isEmpty()) + secondaryItem = ItemStack.EMPTY.copy(); + + return List.of(new HeldItemInfo(secondaryItem, HexUtils.otherHand(castingHand)), new HeldItemInfo(primaryItem, + castingHand)); + } + /** * Return the slot from which to take blocks and items. */ @@ -459,9 +551,44 @@ public boolean withdrawItem(Predicate stackOk, int count, boolean act /** * Attempt to replace the first stack found which matches the predicate with the stack to replace with. + * * @return whether it was successful. */ - public abstract boolean replaceItem(Predicate stackOk, ItemStack replaceWith, @Nullable InteractionHand hand); + public abstract boolean replaceItem(Predicate stackOk, ItemStack replaceWith, + @Nullable InteractionHand hand); + + + public boolean replaceItemForPlayer(Predicate stackOk, ItemStack replaceWith, + @Nullable InteractionHand hand, ServerPlayer caster) { + if (caster == null) + return false; + + if (hand != null && stackOk.test(caster.getItemInHand(hand))) { + caster.setItemInHand(hand, replaceWith); + return true; + } + + Inventory inv = caster.getInventory(); + for (int i = inv.items.size() - 1; i >= 0; i--) { + if (i != inv.selected) { + if (stackOk.test(inv.items.get(i))) { + inv.setItem(i, replaceWith); + return true; + } + } + } + + if (stackOk.test(caster.getItemInHand(getOtherHand()))) { + caster.setItemInHand(getOtherHand(), replaceWith); + return true; + } + if (stackOk.test(caster.getItemInHand(getCastingHand()))) { + caster.setItemInHand(getCastingHand(), replaceWith); + return true; + } + + return false; + } /** * The order/mode stacks should be discovered in diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironmentComponent.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironmentComponent.java index faee1de0ba..73f7775a44 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironmentComponent.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastingEnvironmentComponent.java @@ -29,15 +29,15 @@ interface ExtractMedia extends CastingEnvironmentComponent { * remaining cost after deducting whatever cost source this component * is responsible for (should be >= 0) */ - long onExtractMedia(long cost); + long onExtractMedia(long cost, boolean simulate); /** - * ExtractMedia component that extracts media BEFORE the call to {@link CastingEnvironment#extractMediaEnvironment(long)} + * ExtractMedia component that extracts media BEFORE the call to {@link CastingEnvironment#extractMediaEnvironment(long, boolean)} */ interface Pre extends ExtractMedia {} /** - * ExtractMedia component that extracts media AFTER the call to {@link CastingEnvironment#extractMediaEnvironment(long)} + * ExtractMedia component that extracts media AFTER the call to {@link CastingEnvironment#extractMediaEnvironment(long, boolean)} * if the input is <= 0 you should also probably return 0 (since media cost was already paid off) */ interface Post extends ExtractMedia {} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/CircleCastEnv.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/CircleCastEnv.java index 94771a57c5..d1d542780d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/CircleCastEnv.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/CircleCastEnv.java @@ -111,7 +111,7 @@ public Vec3 mishapSprayPos() { } @Override - public long extractMediaEnvironment(long cost) { + public long extractMediaEnvironment(long cost, boolean simulate) { var entity = this.getImpetus(); if (entity == null) return cost; @@ -122,7 +122,9 @@ public long extractMediaEnvironment(long cost) { long mediaToTake = Math.min(cost, mediaAvailable); cost -= mediaToTake; - entity.setMedia(mediaAvailable - mediaToTake); + if (!simulate) { + entity.setMedia(mediaAvailable - mediaToTake); + } return cost; } @@ -139,7 +141,7 @@ public boolean isVecInRangeEnvironment(Vec3 vec) { if (sentinel != null && sentinel.extendsRange() && caster.level().dimension() == sentinel.dimension() - && vec.distanceToSqr(sentinel.position()) <= SENTINEL_RADIUS * SENTINEL_RADIUS + && vec.distanceToSqr(sentinel.position()) <= SENTINEL_RADIUS * SENTINEL_RADIUS + 0.00000000001 ) { return true; } @@ -148,6 +150,13 @@ public boolean isVecInRangeEnvironment(Vec3 vec) { return this.execState.bounds.contains(vec); } + @Override + public boolean isEnlightened() { + // have unbound circles be enlightened. + if(getCastingEntity() == null) return true; + return super.isEnlightened(); + } + @Override public boolean hasEditPermissionsAtEnvironment(BlockPos pos) { return true; @@ -159,18 +168,27 @@ public InteractionHand getCastingHand() { } @Override + // TODO: Could do something like get items in inventories adjacent to the circle? protected List getUsableStacks(StackDiscoveryMode mode) { - return new ArrayList<>(); // TODO: Could do something like get items in inventories adjacent to the circle? + if (this.getCaster() != null) + return getUsableStacksForPlayer(mode, null, this.getCaster()); + return new ArrayList<>(); } @Override + // TODO: Adjacent inv! protected List getPrimaryStacks() { - return List.of(); // TODO: Adjacent inv! + if (this.getCaster() != null) + return getPrimaryStacksForPlayer(InteractionHand.OFF_HAND, this.getCaster()); + return List.of(); } @Override + // TODO: Adjacent inv! public boolean replaceItem(Predicate stackOk, ItemStack replaceWith, @Nullable InteractionHand hand) { - return false; // TODO: Adjacent inv! + if (this.getCaster() != null) + return replaceItemForPlayer(stackOk, replaceWith, hand, this.getCaster()); + return false; } @Override diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PackagedItemCastEnv.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PackagedItemCastEnv.java index 2a736d55d0..43d0c67d28 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PackagedItemCastEnv.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PackagedItemCastEnv.java @@ -37,7 +37,7 @@ public void postExecution(CastResult result) { } @Override - public long extractMediaEnvironment(long costLeft) { + public long extractMediaEnvironment(long costLeft, boolean simulate) { if (this.caster.isCreative()) return 0; @@ -52,11 +52,11 @@ public long extractMediaEnvironment(long costLeft) { // The contracts on the AD and on this function are different. // ADs return the amount extracted, this wants the amount left if (casterMediaHolder != null) { - long extracted = casterMediaHolder.withdrawMedia((int) costLeft, false); + long extracted = casterMediaHolder.withdrawMedia((int) costLeft, simulate); costLeft -= extracted; } if (canCastFromInv && costLeft > 0) { - costLeft = this.extractMediaFromInventory(costLeft, this.canOvercast()); + costLeft = this.extractMediaFromInventory(costLeft, this.canOvercast(), simulate); } return costLeft; diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java index c137930071..10ff3b052b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java @@ -70,106 +70,17 @@ public void postExecution(CastResult result) { @Override protected List getUsableStacks(StackDiscoveryMode mode) { - return switch (mode) { - case QUERY -> { - var out = new ArrayList(); - - var offhand = this.caster.getItemInHand(HexUtils.otherHand(this.castingHand)); - if (!offhand.isEmpty()) { - out.add(offhand); - } - - // If we're casting from the main hand, try to pick from the slot one to the right of the selected slot - // Otherwise, scan the hotbar left to right - var anchorSlot = this.castingHand == InteractionHand.MAIN_HAND - ? (this.caster.getInventory().selected + 1) % 9 - : 0; - - - for (int delta = 0; delta < 9; delta++) { - var slot = (anchorSlot + delta) % 9; - out.add(this.caster.getInventory().getItem(slot)); - } - - yield out; - } - case EXTRACTION -> { - // https://wiki.vg/Inventory is WRONG - // slots 0-8 are the hotbar - // for what purpose i cannot imagine - // http://redditpublic.com/images/b/b2/Items_slot_number.png looks right - // and offhand is 150 Inventory.java:464 - var out = new ArrayList(); - - // First, the inventory backwards - // We use inv.items here to get the main inventory, but not offhand or armor - Inventory inv = this.caster.getInventory(); - for (int i = inv.items.size() - 1; i >= 0; i--) { - if (i != inv.selected) { - out.add(inv.items.get(i)); - } - } - - // then the offhand, then the selected hand - out.addAll(inv.offhand); - out.add(inv.getSelected()); - - yield out; - } - }; + return getUsableStacksForPlayer(mode, castingHand, caster); } @Override protected List getPrimaryStacks() { - var primaryItem = this.caster.getItemInHand(this.castingHand); - - if (primaryItem.isEmpty()) - primaryItem = ItemStack.EMPTY.copy(); - - return List.of(new HeldItemInfo(getAlternateItem(), this.getOtherHand()), new HeldItemInfo(primaryItem, - this.castingHand)); - } - - ItemStack getAlternateItem() { - var otherHand = HexUtils.otherHand(this.castingHand); - var stack = this.caster.getItemInHand(otherHand); - if (stack.isEmpty()) { - return ItemStack.EMPTY.copy(); - } else { - return stack; - } + return getPrimaryStacksForPlayer(this.castingHand, this.caster); } @Override public boolean replaceItem(Predicate stackOk, ItemStack replaceWith, @Nullable InteractionHand hand) { - if (caster == null) - return false; - - if (hand != null && stackOk.test(caster.getItemInHand(hand))) { - caster.setItemInHand(hand, replaceWith); - return true; - } - - Inventory inv = this.caster.getInventory(); - for (int i = inv.items.size() - 1; i >= 0; i--) { - if (i != inv.selected) { - if (stackOk.test(inv.items.get(i))) { - inv.setItem(i, replaceWith); - return true; - } - } - } - - if (stackOk.test(caster.getItemInHand(getOtherHand()))) { - caster.setItemInHand(getOtherHand(), replaceWith); - return true; - } - if (stackOk.test(caster.getItemInHand(getCastingHand()))) { - caster.setItemInHand(getCastingHand(), replaceWith); - return true; - } - - return false; + return replaceItemForPlayer(stackOk, replaceWith, hand, this.caster); } @Override @@ -178,12 +89,13 @@ public boolean isVecInRangeEnvironment(Vec3 vec) { if (sentinel != null && sentinel.extendsRange() && this.caster.level().dimension() == sentinel.dimension() - && vec.distanceToSqr(sentinel.position()) <= SENTINEL_RADIUS * SENTINEL_RADIUS + // adding 0.00000000001 to avoid machine precision errors at specific angles + && vec.distanceToSqr(sentinel.position()) <= SENTINEL_RADIUS * SENTINEL_RADIUS + 0.00000000001 ) { return true; } - return vec.distanceToSqr(this.caster.position()) <= AMBIT_RADIUS * AMBIT_RADIUS; + return vec.distanceToSqr(this.caster.position()) <= AMBIT_RADIUS * AMBIT_RADIUS + 0.00000000001; } @Override @@ -194,13 +106,13 @@ public boolean hasEditPermissionsAtEnvironment(BlockPos pos) { /** * Search the player's inventory for media ADs and use them. */ - protected long extractMediaFromInventory(long costLeft, boolean allowOvercast) { + protected long extractMediaFromInventory(long costLeft, boolean allowOvercast, boolean simulate) { List sources = MediaHelper.scanPlayerForMediaStuff(this.caster); var startCost = costLeft; for (var source : sources) { - var found = MediaHelper.extractMedia(source, costLeft, false, false); + var found = MediaHelper.extractMedia(source, costLeft, false, simulate); costLeft -= found; if (costLeft <= 0) { break; @@ -210,24 +122,31 @@ protected long extractMediaFromInventory(long costLeft, boolean allowOvercast) { if (costLeft > 0 && allowOvercast) { double mediaToHealth = HexConfig.common().mediaToHealthRate(); double healthToRemove = Math.max(costLeft / mediaToHealth, 0.5); - var mediaAbleToCastFromHP = this.caster.getHealth() * mediaToHealth; + if (simulate) { + long simulatedRemovedMedia = Mth.ceil(Math.min(this.caster.getHealth(), healthToRemove) * mediaToHealth); + costLeft -= simulatedRemovedMedia; + } else { + var mediaAbleToCastFromHP = this.caster.getHealth() * mediaToHealth; - Mishap.trulyHurt(this.caster, this.caster.damageSources().source(HexDamageTypes.OVERCAST), (float) healthToRemove); + Mishap.trulyHurt(this.caster, this.caster.damageSources().source(HexDamageTypes.OVERCAST), (float) healthToRemove); - var actuallyTaken = Mth.ceil(mediaAbleToCastFromHP - (this.caster.getHealth() * mediaToHealth)); + var actuallyTaken = Mth.ceil(mediaAbleToCastFromHP - (this.caster.getHealth() * mediaToHealth)); - HexAdvancementTriggers.OVERCAST_TRIGGER.trigger(this.caster, actuallyTaken); - this.caster.awardStat(HexStatistics.MEDIA_OVERCAST, actuallyTaken); + HexAdvancementTriggers.OVERCAST_TRIGGER.trigger(this.caster, actuallyTaken); + this.caster.awardStat(HexStatistics.MEDIA_OVERCAST, actuallyTaken); - costLeft -= actuallyTaken; + costLeft -= actuallyTaken; + } } - this.caster.awardStat(HexStatistics.MEDIA_USED, (int) (startCost - costLeft)); - HexAdvancementTriggers.SPEND_MEDIA_TRIGGER.trigger( - this.caster, - startCost - costLeft, - costLeft < 0 ? -costLeft : 0 - ); + if (!simulate) { + this.caster.awardStat(HexStatistics.MEDIA_USED, (int) (startCost - costLeft)); + HexAdvancementTriggers.SPEND_MEDIA_TRIGGER.trigger( + this.caster, + startCost - costLeft, + costLeft < 0 ? -costLeft : 0 + ); + } return costLeft; } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/StaffCastEnv.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/StaffCastEnv.java index 6fbfa7bcf6..29e80d3817 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/StaffCastEnv.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/StaffCastEnv.java @@ -69,16 +69,12 @@ public void postCast(CastingImage image) { } @Override - public long extractMediaEnvironment(long cost) { + public long extractMediaEnvironment(long cost, boolean simulate) { if (this.caster.isCreative()) return 0; var canOvercast = this.canOvercast(); - var remaining = this.extractMediaFromInventory(cost, canOvercast); - if (remaining > 0 && !canOvercast) { - this.caster.sendSystemMessage(Component.translatable("hexcasting.message.cant_overcast")); - } - return remaining; + return this.extractMediaFromInventory(cost, canOvercast, simulate); } @Override diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/sideeffects/OperatorSideEffect.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/sideeffects/OperatorSideEffect.kt index 9143d54d2c..72226fe291 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/sideeffects/OperatorSideEffect.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/sideeffects/OperatorSideEffect.kt @@ -18,14 +18,11 @@ import net.minecraft.world.item.ItemStack */ sealed class OperatorSideEffect { /** Return whether to cancel all further [OperatorSideEffect] */ - abstract fun performEffect(harness: CastingVM): Boolean + abstract fun performEffect(harness: CastingVM) data class RequiredEnlightenment(val awardStat: Boolean) : OperatorSideEffect() { - override fun performEffect(harness: CastingVM): Boolean { + override fun performEffect(harness: CastingVM) { harness.env.castingEntity?.sendSystemMessage("hexcasting.message.cant_great_spell".asTranslatedComponent) - - - return true } } @@ -36,33 +33,28 @@ sealed class OperatorSideEffect { val awardStat: Boolean = true ) : OperatorSideEffect() { - override fun performEffect(harness: CastingVM): Boolean { + override fun performEffect(harness: CastingVM) { this.spell.cast(harness.env, harness.image)?.let { harness.image = it } if (awardStat) (harness.env.castingEntity as? ServerPlayer)?.awardStat(HexStatistics.SPELLS_CAST) - - return false } } data class ConsumeMedia(val amount: Long) : OperatorSideEffect() { - override fun performEffect(harness: CastingVM): Boolean { - val leftoverMedia = harness.env.extractMedia(this.amount) - return leftoverMedia > 0 + override fun performEffect(harness: CastingVM) { + harness.env.extractMedia(this.amount, false) } } data class Particles(val spray: ParticleSpray) : OperatorSideEffect() { - override fun performEffect(harness: CastingVM): Boolean { + override fun performEffect(harness: CastingVM) { harness.env.produceParticles(this.spray, harness.env.pigment) // this.spray.sprayParticles(harness.env.world, harness.env.colorizer) - - return false } } data class DoMishap(val mishap: Mishap, val errorCtx: Mishap.Context) : OperatorSideEffect() { - override fun performEffect(harness: CastingVM): Boolean { + override fun performEffect(harness: CastingVM) { val spray = mishap.particleSpray(harness.env) val color = mishap.accentColor(harness.env, errorCtx) spray.sprayParticles(harness.env.world, color) @@ -75,8 +67,6 @@ sealed class OperatorSideEffect { ) harness.image = harness.image.copy(stack = mishap.executeReturnStack(harness.env, errorCtx, harness.image.stack.toMutableList())) - - return true } } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt index caa000676e..587500355b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt @@ -73,10 +73,10 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { continuation = image2.continuation lastResolutionType = image2.resolutionType try { - performSideEffects(info, image2.sideEffects) + performSideEffects(image2.sideEffects) } catch (e: Exception) { e.printStackTrace() - performSideEffects(info, listOf(OperatorSideEffect.DoMishap(MishapInternalException(e), Mishap.Context(null, null)))) + performSideEffects(listOf(OperatorSideEffect.DoMishap(MishapInternalException(e), Mishap.Context(null, null)))) } info.earlyExit = info.earlyExit || !lastResolutionType.success } @@ -152,13 +152,9 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { /** * Execute the side effects of a pattern, updating our aggregated info. */ - fun performSideEffects(info: TempControllerInfo, sideEffects: List) { + fun performSideEffects(sideEffects: List) { for (haskellProgrammersShakingandCryingRN in sideEffects) { - val mustStop = haskellProgrammersShakingandCryingRN.performEffect(this) - if (mustStop) { - info.earlyExit = true - break - } + haskellProgrammersShakingandCryingRN.performEffect(this) } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/EntityIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/EntityIota.java index fe2ee51f45..2a8023cf01 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/EntityIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/EntityIota.java @@ -2,13 +2,18 @@ import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import com.samsthenerd.inline.api.InlineAPI; +import com.samsthenerd.inline.api.data.EntityInlineData; +import com.samsthenerd.inline.api.data.PlayerHeadData; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,13 +43,29 @@ public boolean isTruthy() { Tag serialize() { var out = new CompoundTag(); out.putUUID("uuid", this.getEntity().getUUID()); - out.putString("name", Component.Serializer.toJson(this.getEntity().getName())); + out.putString("name", Component.Serializer.toJson(getEntityNameWithInline(true))); return out; } @Override public Component display() { - return this.getEntity().getName().copy().withStyle(ChatFormatting.AQUA); + return getEntityNameWithInline(false).copy().withStyle(ChatFormatting.AQUA); + } + + private Component getEntityNameWithInline(boolean fearSerializer){ + MutableComponent baseName = this.getEntity().getName().copy(); + Component inlineEnt = null; + if(this.getEntity() instanceof Player player){ + inlineEnt = new PlayerHeadData(player.getGameProfile()).asText(!fearSerializer); + inlineEnt = inlineEnt.plainCopy().withStyle(InlineAPI.INSTANCE.withSizeModifier(inlineEnt.getStyle(), 1.5)); + } else{ + if(fearSerializer){ // we don't want to have to serialize an entity just to display it + inlineEnt = EntityInlineData.fromType(this.getEntity().getType()).asText(!fearSerializer); + } else { + inlineEnt = EntityInlineData.fromEntity(this.getEntity()).asText(!fearSerializer); + } + } + return baseName.append(Component.literal(": ")).append(inlineEnt); } public static IotaType TYPE = new IotaType<>() { @@ -73,6 +94,7 @@ public Component display(Tag tag) { return Component.translatable("hexcasting.spelldata.entity.whoknows"); } var nameJson = ctag.getString("name"); +// return Component.literal(nameJson); return Component.Serializer.fromJsonLenient(nameJson).withStyle(ChatFormatting.AQUA); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Iota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Iota.java index 7f024abc94..cad6baee82 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Iota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Iota.java @@ -96,6 +96,10 @@ public int size() { return 1; } + public int depth() { + return 1; + } + public Component display() { return this.type.display(this.serialize()); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/IotaType.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/IotaType.java index 4bcf9b4b1f..c0167ae5df 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/IotaType.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/IotaType.java @@ -15,8 +15,6 @@ import net.minecraft.util.FormattedCharSequence; import javax.annotation.Nullable; -import java.util.ArrayDeque; -import java.util.Collections; import java.util.List; import java.util.Objects; @@ -81,33 +79,13 @@ public static boolean isTooLargeToSerialize(Iterable examinee) { } private static boolean isTooLargeToSerialize(Iterable examinee, int startingCount) { - // We don't recurse here, just a work queue (or work stack, if we liked.) - // Each element is a found sub-iota, and how deep it is. - // - // TODO: is it worth trying to cache the depth and size statically on a SpellList. - var listsToExamine = new ArrayDeque<>(Collections.singleton(new Pair<>(examinee, 0))); - int totalEltsFound = startingCount; // count the first list - while (!listsToExamine.isEmpty()) { - var iotaPair = listsToExamine.removeFirst(); - var sublist = iotaPair.getFirst(); - int depth = iotaPair.getSecond(); - for (var iota : sublist) { - totalEltsFound += iota.size(); - if (totalEltsFound >= HexIotaTypes.MAX_SERIALIZATION_TOTAL) { - return true; // too bad - } - var subIotas = iota.subIotas(); - if (subIotas != null) { - if (depth + 1 >= HexIotaTypes.MAX_SERIALIZATION_DEPTH) { - return true; - } - - listsToExamine.addLast(new Pair<>(subIotas, depth + 1)); - } - } + int totalSize = startingCount; + for (Iota iota : examinee) { + if (iota.depth() >= HexIotaTypes.MAX_SERIALIZATION_DEPTH) + return true; + totalSize += iota.size(); } - // we made it! - return false; + return totalSize >= HexIotaTypes.MAX_SERIALIZATION_TOTAL; } /** diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java index 255d54662f..d3eb9455bf 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java @@ -15,12 +15,25 @@ import java.util.ArrayList; import java.util.List; +import static java.lang.Math.max; + /** * This is a wrapper for {@link SpellList}. */ public class ListIota extends Iota { + private final int depth; + private final int size; + public ListIota(@NotNull SpellList list) { super(HexIotaTypes.LIST, list); + int maxChildDepth = 0; + int totalSize = 1; + for (Iota iota : list) { + totalSize += iota.size(); + maxChildDepth = max(maxChildDepth, iota.depth()); + } + depth = maxChildDepth + 1; + size = totalSize; } public ListIota(@NotNull List list) { @@ -78,6 +91,16 @@ public boolean toleratesOther(Iota that) { return this.getList(); } + @Override + public int size() { + return size; + } + + @Override + public int depth() { + return depth; + } + public static IotaType TYPE = new IotaType<>() { @Nullable @Override @@ -107,7 +130,10 @@ public Component display(Tag tag) { out.append(IotaType.getDisplay(csub)); - if (i < list.size() - 1) { + // only add a comma between 2 non-patterns (commas don't look good with Inline patterns) + // TODO: maybe add a config? maybe add a method on IotaType to allow it to opt out of commas + if (i < list.size() - 1 && (IotaType.getTypeFromTag(csub) != PatternIota.TYPE + || IotaType.getTypeFromTag(HexUtils.downcast(list.get(i+1), CompoundTag.TYPE)) != PatternIota.TYPE)) { out.append(", "); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java index 6b52868ad6..b86e8541ce 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java @@ -14,17 +14,18 @@ import at.petrak.hexcasting.api.casting.mishaps.MishapEvalTooMuch; import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidPattern; import at.petrak.hexcasting.api.casting.mishaps.MishapUnenlightened; -import at.petrak.hexcasting.api.mod.HexConfig; import at.petrak.hexcasting.api.mod.HexTags; import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.common.casting.PatternRegistryManifest; import at.petrak.hexcasting.common.lib.hex.HexEvalSounds; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import at.petrak.hexcasting.interop.inline.InlinePatternData; import at.petrak.hexcasting.xplat.IXplatAbstractions; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import org.jetbrains.annotations.NotNull; @@ -163,6 +164,12 @@ public static PatternIota deserialize(Tag tag) throws IllegalArgumentException { } public static Component display(HexPattern pat) { + Component text = (new InlinePatternData(pat)).asText(true); + return text.copy().withStyle(text.getStyle().applyTo(Style.EMPTY.withColor(ChatFormatting.WHITE))); + } + + // keep around just in case it's needed. + public static Component displayNonInline(HexPattern pat){ var bob = new StringBuilder(); bob.append(pat.getStartDir()); @@ -172,7 +179,7 @@ public static Component display(HexPattern pat) { bob.append(sig); } return Component.translatable("hexcasting.tooltip.pattern_iota", - Component.literal(bob.toString()).withStyle(ChatFormatting.WHITE)) - .withStyle(ChatFormatting.GOLD); + Component.literal(bob.toString()).withStyle(ChatFormatting.WHITE)) + .withStyle(ChatFormatting.GOLD); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexDir.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexDir.kt index 514cb0e467..03f4ca5a56 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexDir.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexDir.kt @@ -1,6 +1,7 @@ package at.petrak.hexcasting.api.casting.math import at.petrak.hexcasting.api.utils.getSafe +import com.mojang.serialization.Codec enum class HexDir { NORTH_EAST, EAST, SOUTH_EAST, SOUTH_WEST, WEST, NORTH_WEST; @@ -26,6 +27,11 @@ enum class HexDir { } companion object { + val CODEC: Codec = Codec.STRING.xmap( + HexDir::fromString, + HexDir::name + ) + @JvmStatic fun fromString(key: String): HexDir { return values().getSafe(key, WEST) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexPattern.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexPattern.kt index 6b6c587ea0..001cfdeab4 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexPattern.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexPattern.kt @@ -4,6 +4,8 @@ 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 com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.Tag import net.minecraft.world.phys.Vec2 @@ -127,6 +129,13 @@ data class HexPattern(val startDir: HexDir, val angles: MutableList = const val TAG_START_DIR = "start_dir" const val TAG_ANGLES = "angles" + @JvmField + val CODEC: Codec = RecordCodecBuilder.create({instance -> instance.group( + Codec.STRING.fieldOf(TAG_START_DIR).forGetter(HexPattern::anglesSignature), + HexDir.CODEC.fieldOf(TAG_ANGLES).forGetter(HexPattern::startDir) + ).apply(instance, HexPattern::fromAngles) + }) + @JvmStatic fun isPattern(tag: CompoundTag): Boolean { return tag.contains(TAG_START_DIR, Tag.TAG_ANY_NUMERIC.toInt()) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadCaster.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadCaster.kt new file mode 100644 index 0000000000..99199f9561 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadCaster.kt @@ -0,0 +1,17 @@ +package at.petrak.hexcasting.api.casting.mishaps + +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.pigment.FrozenPigment +import net.minecraft.world.item.DyeColor + +class MishapBadCaster: Mishap() { + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.RED) + + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + } + + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("bad_caster") +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNotEnoughMedia.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNotEnoughMedia.kt new file mode 100644 index 0000000000..07d686b56e --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNotEnoughMedia.kt @@ -0,0 +1,21 @@ +package at.petrak.hexcasting.api.casting.mishaps + +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.pigment.FrozenPigment +import at.petrak.hexcasting.api.utils.asTranslatedComponent +import net.minecraft.world.item.DyeColor + +class MishapNotEnoughMedia(private val cost: Long) : Mishap() { + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.RED) + + override fun resolutionType(ctx: CastingEnvironment) = ResolvedPatternType.ERRORED + + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + env.extractMedia(cost, false) + } + + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = "hexcasting.message.cant_overcast".asTranslatedComponent +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java index 105994f61d..19458cbc8e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java @@ -47,10 +47,13 @@ public interface ClientConfigAccess { double gridSnapThreshold(); + boolean clickingTogglesDrawing(); + boolean DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER = false; boolean DEFAULT_INVERT_SPELLBOOK_SCROLL = false; boolean DEFAULT_INVERT_ABACUS_SCROLL = false; double DEFAULT_GRID_SNAP_THRESHOLD = 0.5; + boolean DEFAULT_CLICKING_TOGGLES_DRAWING = false; } public interface ServerConfigAccess { @@ -71,7 +74,7 @@ public interface ServerConfigAccess { boolean trueNameHasAmbit(); - int DEFAULT_MAX_OP_COUNT = 1_000_000; + int DEFAULT_MAX_OP_COUNT = 100_000; int DEFAULT_MAX_SPELL_CIRCLE_LENGTH = 1024; int DEFAULT_OP_BREAK_HARVEST_LEVEL = 3; diff --git a/Common/src/main/java/at/petrak/hexcasting/client/entity/WallScrollRenderer.java b/Common/src/main/java/at/petrak/hexcasting/client/entity/WallScrollRenderer.java index 351acbef7a..d17e617cd9 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/entity/WallScrollRenderer.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/entity/WallScrollRenderer.java @@ -1,7 +1,6 @@ package at.petrak.hexcasting.client.entity; -import at.petrak.hexcasting.client.render.PatternTextureManager; -import at.petrak.hexcasting.client.render.RenderLib; +import at.petrak.hexcasting.client.render.WorldlyPatternRenderHelpers; import at.petrak.hexcasting.common.entities.EntityWallScroll; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; @@ -15,13 +14,9 @@ import net.minecraft.client.renderer.entity.EntityRendererProvider; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.phys.Vec2; import org.joml.Matrix3f; import org.joml.Matrix4f; -import java.util.List; - import static at.petrak.hexcasting.api.HexAPI.modLoc; public class WallScrollRenderer extends EntityRenderer { @@ -31,7 +26,6 @@ public class WallScrollRenderer extends EntityRenderer { private static final ResourceLocation ANCIENT_BG_LARGE = modLoc("textures/entity/scroll_ancient_large.png"); private static final ResourceLocation ANCIENT_BG_MEDIUM = modLoc("textures/entity/scroll_ancient_medium.png"); private static final ResourceLocation ANCIENT_BG_SMOL = modLoc("textures/block/ancient_scroll_paper.png"); - private static final ResourceLocation WHITE = modLoc("textures/entity/white.png"); public WallScrollRenderer(EntityRendererProvider.Context p_174008_) { super(p_174008_); @@ -64,7 +58,9 @@ public void render(EntityWallScroll wallScroll, float yaw, float partialTicks, P var mat = last.pose(); var norm = last.normal(); - var verts = bufSource.getBuffer(RenderType.entityCutout(this.getTextureLocation(wallScroll))); + RenderType layer = RenderType.entityCutout(this.getTextureLocation(wallScroll)); + + var verts = bufSource.getBuffer(layer); // Remember: CCW // Front face vertex(mat, norm, light, verts, 0, 0, dz, 0, 0, 0, 0, -1); @@ -99,42 +95,8 @@ public void render(EntityWallScroll wallScroll, float yaw, float partialTicks, P ps.popPose(); - if (PatternTextureManager.useTextures && wallScroll.points != null) - PatternTextureManager.renderPatternForScroll(wallScroll.points.pointsKey, ps, bufSource, light, wallScroll.points.zappyPoints, wallScroll.blockSize, wallScroll.getShowsStrokeOrder()); - } - - //TODO: remove old rendering if not needed anymore for comparison - if (!PatternTextureManager.useTextures && wallScroll.points != null) { - var points = wallScroll.points.zappyPoints; - ps.pushPose(); - - ps.mulPose(Axis.YP.rotationDegrees(180f)); - ps.translate(0, 0, 1.1f / 16f); - // make smaller scrolls not be charlie kirk-sized - // i swear, learning about these functions with asymptotes where slope != 0 is the most useful thing - // I've ever learned in a math class - float unCharlieKirk = Mth.sqrt(wallScroll.blockSize * wallScroll.blockSize + 60); - float scale = 1f / 300f * unCharlieKirk; - ps.scale(scale, scale, 0.01f); - - var last = ps.last(); - var mat = last.pose(); - var norm = last.normal(); - var outer = 0xff_d2c8c8; - var inner = 0xc8_322b33; - var verts = bufSource.getBuffer(RenderType.entityCutout(WHITE)); - theCoolerDrawLineSeq(mat, norm, light, verts, points, wallScroll.blockSize * 5f / 3f, outer); - ps.translate(0, 0, 0.01); - theCoolerDrawLineSeq(mat, norm, light, verts, points, wallScroll.blockSize * 2f / 3f, inner); - - if (wallScroll.getShowsStrokeOrder()) { - ps.translate(0, 0, 0.01); - var spotFrac = 0.8f * wallScroll.blockSize; - theCoolerDrawSpot(mat, norm, light, verts, points.get(0), 2f / 3f * spotFrac, - 0xff_5b7bd7); - } - - ps.popPose(); + if(wallScroll.pattern != null) + WorldlyPatternRenderHelpers.renderPatternForScroll(wallScroll.pattern, wallScroll, ps, bufSource, light, wallScroll.blockSize, wallScroll.getShowsStrokeOrder()); } ps.popPose(); @@ -163,146 +125,12 @@ public ResourceLocation getTextureLocation(EntityWallScroll wallScroll) { } private static void vertex(Matrix4f mat, Matrix3f normal, int light, VertexConsumer verts, float x, float y, - float z, float u, - float v, float nx, float ny, float nz) { + float z, float u, + float v, float nx, float ny, float nz) { verts.vertex(mat, x, y, z) - .color(0xffffffff) - .uv(u, v).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(light) - .normal(normal, nx, ny, nz) - .endVertex(); - } - - private static void vertexCol(Matrix4f mat, Matrix3f normal, int light, VertexConsumer verts, int col, Vec2 pos) { - verts.vertex(mat, -pos.x, pos.y, 0) - .color(col) - .uv(0, 0).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(light) - .normal(normal, 0, 0, 1) - .endVertex(); - } - - private static void theCoolerDrawLineSeq(Matrix4f mat, Matrix3f normalMat, int light, VertexConsumer verts, - List points, float width, int color - ) { - if (points.size() <= 1) { - return; - } - - // TODO: abstract some of this out with RenderLib to stop WET code - var joinAngles = new float[points.size()]; - var joinOffsets = new float[points.size()]; - for (int i = 2; i < points.size(); i++) { - var p0 = points.get(i - 2); - var p1 = points.get(i - 1); - var p2 = points.get(i); - var prev = p1.add(p0.negated()); - var next = p2.add(p1.negated()); - var angle = (float) Mth.atan2( - prev.x * next.y - prev.y * next.x, - prev.x * next.x + prev.y * next.y); - joinAngles[i - 1] = angle; - var clamp = Math.min(prev.length(), next.length()) / (width * 0.5f); - joinOffsets[i - 1] = Mth.clamp(Mth.sin(angle) / (1 + Mth.cos(angle)), -clamp, clamp); - } - - for (var i = 0; i < points.size() - 1; i++) { - var p1 = points.get(i); - var p2 = points.get(i + 1); - - var tangent = p2.add(p1.negated()).normalized().scale(width * 0.5f); - var normal = new Vec2(-tangent.y, tangent.x); - - var jlow = joinOffsets[i]; - var jhigh = joinOffsets[i + 1]; - - var p1Down = p1.add(tangent.scale(Math.max(0f, jlow))).add(normal); - var p1Up = p1.add(tangent.scale(Math.max(0f, -jlow))).add(normal.negated()); - var p2Down = p2.add(tangent.scale(Math.max(0f, jhigh)).negated()).add(normal); - var p2Up = p2.add(tangent.scale(Math.max(0f, -jhigh)).negated()).add(normal.negated()); - - // Draw the chamfer hexagon as two trapezoids - // the points are in different orders to keep clockwise - vertexCol(mat, normalMat, light, verts, color, p1); - vertexCol(mat, normalMat, light, verts, color, p2); - vertexCol(mat, normalMat, light, verts, color, p2Up); - vertexCol(mat, normalMat, light, verts, color, p1Up); - - vertexCol(mat, normalMat, light, verts, color, p1); - vertexCol(mat, normalMat, light, verts, color, p1Down); - vertexCol(mat, normalMat, light, verts, color, p2Down); - vertexCol(mat, normalMat, light, verts, color, p2); - - if (i > 0) { - var sangle = joinAngles[i]; - var angle = Math.abs(sangle); - var rnormal = normal.negated(); - var joinSteps = Mth.ceil(angle * 180 / (RenderLib.CAP_THETA * Mth.PI)); - if (joinSteps < 1) continue; - - if (sangle < 0) { - var prevVert = new Vec2(p1.x - rnormal.x, p1.y - rnormal.y); - for (var j = 1; j <= joinSteps; j++) { - var fan = RenderLib.rotate(rnormal, -sangle * ((float) j / joinSteps)); - var fanShift = new Vec2(p1.x - fan.x, p1.y - fan.y); - - vertexCol(mat, normalMat, light, verts, color, p1); - vertexCol(mat, normalMat, light, verts, color, p1); - vertexCol(mat, normalMat, light, verts, color, fanShift); - vertexCol(mat, normalMat, light, verts, color, prevVert); - prevVert = fanShift; - } - } else { - var startFan = RenderLib.rotate(normal, -sangle); - var prevVert = new Vec2(p1.x - startFan.x, p1.y - startFan.y); - for (var j = joinSteps - 1; j >= 0; j--) { - var fan = RenderLib.rotate(normal, -sangle * ((float) j / joinSteps)); - var fanShift = new Vec2(p1.x - fan.x, p1.y - fan.y); - - vertexCol(mat, normalMat, light, verts, color, p1); - vertexCol(mat, normalMat, light, verts, color, p1); - vertexCol(mat, normalMat, light, verts, color, fanShift); - vertexCol(mat, normalMat, light, verts, color, prevVert); - prevVert = fanShift; - } - } - } - } - - for (var pair : new Vec2[][]{ - {points.get(0), points.get(1)}, - {points.get(points.size() - 1), points.get(points.size() - 2)} - }) { - var point = pair[0]; - var prev = pair[1]; - - var tangent = point.add(prev.negated()).normalized().scale(0.5f * width); - var normal = new Vec2(-tangent.y, tangent.x); - var joinSteps = Mth.ceil(180f / RenderLib.CAP_THETA); - for (int j = joinSteps; j > 0; j--) { - var fan0 = RenderLib.rotate(normal, -Mth.PI * ((float) j / joinSteps)); - var fan1 = RenderLib.rotate(normal, -Mth.PI * ((float) (j - 1) / joinSteps)); - - vertexCol(mat, normalMat, light, verts, color, point); - vertexCol(mat, normalMat, light, verts, color, point); - vertexCol(mat, normalMat, light, verts, color, point.add(fan1)); - vertexCol(mat, normalMat, light, verts, color, point.add(fan0)); - } - } - } - - private static void theCoolerDrawSpot(Matrix4f mat, Matrix3f normal, int light, VertexConsumer verts, - Vec2 point, float radius, int color) { - var fracOfCircle = 6; - for (int i = 0; i < fracOfCircle; i++) { - // We do need rects, irritatingly - // so we do fake triangles - vertexCol(mat, normal, light, verts, color, point); - vertexCol(mat, normal, light, verts, color, point); - for (int j = 0; j <= 1; j++) { - var theta = (i - j) / (float) fracOfCircle * Mth.TWO_PI; - var rx = Mth.cos(theta) * radius + point.x; - var ry = Mth.sin(theta) * radius + point.y; - vertexCol(mat, normal, light, verts, color, new Vec2(rx, ry)); - } - } + .color(0xffffffff) + .uv(u, v).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(light) + .normal(normal, nx, ny, nz) + .endVertex(); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index a039b5264b..0a9f66cd2b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -131,7 +131,16 @@ class GuiSpellcasting constructor( if (super.mouseClicked(mxOut, myOut, pButton)) { return true } + if (HexConfig.client().clickingTogglesDrawing()) { + return if (this.drawState is PatternDrawState.BetweenPatterns) + drawStart(mxOut, myOut) + else + drawEnd() + } + return drawStart(mxOut, myOut) + } + private fun drawStart(mxOut: Double, myOut: Double): Boolean { val mx = Mth.clamp(mxOut, 0.0, this.width.toDouble()) val my = Mth.clamp(myOut, 0.0, this.height.toDouble()) if (this.drawState is PatternDrawState.BetweenPatterns) { @@ -156,11 +165,23 @@ class GuiSpellcasting constructor( return false } + override fun mouseMoved(mxOut: Double, myOut: Double) { + super.mouseMoved(mxOut, myOut) + + if (HexConfig.client().clickingTogglesDrawing() && this.drawState !is PatternDrawState.BetweenPatterns) + drawMove(mxOut, myOut) + } + override fun mouseDragged(mxOut: Double, myOut: Double, pButton: Int, pDragX: Double, pDragY: Double): Boolean { if (super.mouseDragged(mxOut, myOut, pButton, pDragX, pDragY)) { return true } + if (HexConfig.client().clickingTogglesDrawing()) + return false + return drawMove(mxOut, myOut) + } + private fun drawMove(mxOut: Double, myOut: Double): Boolean { val mx = Mth.clamp(mxOut, 0.0, this.width.toDouble()) val my = Mth.clamp(myOut, 0.0, this.height.toDouble()) @@ -237,7 +258,12 @@ class GuiSpellcasting constructor( if (super.mouseReleased(mx, my, pButton)) { return true } + if (HexConfig.client().clickingTogglesDrawing()) + return false + return drawEnd() + } + private fun drawEnd(): Boolean { when (this.drawState) { PatternDrawState.BetweenPatterns -> {} is PatternDrawState.JustStarted -> { diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/PatternTooltipComponent.java b/Common/src/main/java/at/petrak/hexcasting/client/gui/PatternTooltipComponent.java index 42d768ddda..c6f7da11d8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/PatternTooltipComponent.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/PatternTooltipComponent.java @@ -1,24 +1,18 @@ package at.petrak.hexcasting.client.gui; import at.petrak.hexcasting.api.casting.math.HexPattern; -import at.petrak.hexcasting.client.render.RenderLib; +import at.petrak.hexcasting.client.render.PatternColors; +import at.petrak.hexcasting.client.render.PatternRenderer; +import at.petrak.hexcasting.client.render.WorldlyPatternRenderHelpers; import at.petrak.hexcasting.common.misc.PatternTooltip; -import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.client.renderer.entity.ItemRenderer; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.inventory.tooltip.TooltipComponent; -import net.minecraft.world.phys.Vec2; import org.jetbrains.annotations.Nullable; -import java.util.List; -import java.util.stream.Collectors; - import static at.petrak.hexcasting.api.HexAPI.modLoc; // https://github.com/VazkiiMods/Botania/blob/95bd2d3fbc857b7c102687554e1d1b112f8af436/Xplat/src/main/java/vazkii/botania/client/gui/ManaBarTooltipComponent.java @@ -36,23 +30,11 @@ public class PatternTooltipComponent implements ClientTooltipComponent { private static final int TEXTURE_SIZE = 48; private final HexPattern pattern; - private final List zappyPoints; - private final List pathfinderDots; - private final float scale; private final ResourceLocation background; public PatternTooltipComponent(PatternTooltip tt) { this.pattern = tt.pattern(); this.background = tt.background(); - - var pair = RenderLib.getCenteredPattern(pattern, RENDER_SIZE, RENDER_SIZE, 16f); - this.scale = pair.getFirst(); - var dots = pair.getSecond(); - this.zappyPoints = RenderLib.makeZappy( - dots, RenderLib.findDupIndices(pattern.positions()), - 10, 0.8f, 0f, 0f, RenderLib.DEFAULT_READABILITY_OFFSET, RenderLib.DEFAULT_LAST_SEGMENT_LEN_PROP, - 0.0); - this.pathfinderDots = dots.stream().distinct().collect(Collectors.toList()); } @Nullable @@ -65,9 +47,6 @@ public static ClientTooltipComponent tryConvert(TooltipComponent cmp) { @Override public void renderImage(Font font, int mouseX, int mouseY, GuiGraphics graphics) { - var width = this.getWidth(font); - var height = this.getHeight(); - var ps = graphics.pose(); // far as i can tell "mouseX" and "mouseY" are actually the positions of the corner of the tooltip @@ -77,28 +56,13 @@ public void renderImage(Font font, int mouseX, int mouseY, GuiGraphics graphics) renderBG(graphics, this.background); // renderText happens *before* renderImage for some asinine reason -// RenderSystem.disableBlend(); ps.translate(0, 0, 100); + ps.scale(RENDER_SIZE, RENDER_SIZE, 1); - RenderSystem.setShader(GameRenderer::getPositionColorShader); - RenderSystem.disableCull(); - RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, - GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); - ps.translate(width / 2f, height / 2f, 1); - - var mat = ps.last().pose(); - var outer = 0xff_d2c8c8; - var innerLight = 0xc8_aba2a2; - var innerDark = 0xc8_322b33; - RenderLib.drawLineSeq(mat, this.zappyPoints, 6f, 0, - outer, outer); - RenderLib.drawLineSeq(mat, this.zappyPoints, 6f * 0.4f, 0, - innerDark, innerLight); - RenderLib.drawSpot(mat, this.zappyPoints.get(0), 2.5f, 1f, 0.1f, 0.15f, 0.6f); - - for (var dot : this.pathfinderDots) { - RenderLib.drawSpot(mat, dot, 1.5f, 0.82f, 0.8f, 0.8f, 0.5f); - } + PatternRenderer.renderPattern(pattern, ps, WorldlyPatternRenderHelpers.READABLE_SCROLL_SETTINGS, + (PatternRenderer.shouldDoStrokeGradient() ? PatternColors.DEFAULT_GRADIENT_COLOR : PatternColors.DEFAULT_PATTERN_COLOR) + .withDots(true, true), + 0, 512); ps.popPose(); } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternLike.java b/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternLike.java new file mode 100644 index 0000000000..1b82992398 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternLike.java @@ -0,0 +1,70 @@ +package at.petrak.hexcasting.client.render; + +import at.petrak.hexcasting.api.casting.math.HexPattern; +import net.minecraft.world.phys.Vec2; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * A simple wrapper around the parts of HexPattern that are actually used for rendering. + * + * This lets the pattern renderer work on arbitrary lists of vecs - this is never used in base hex but is included + * to future-proof and for if addons or something wants to use it. + */ +public interface HexPatternLike { + List getNonZappyPoints(); + + String getName(); + + Set getDups(); + + static HexPatternLike of(HexPattern pat){ + return new HexPatternLikeBecauseItsActuallyAHexPattern(pat); + } + + static HexPatternLike of(List lines, String name){ + return new PureLines(lines, name); + } + + record HexPatternLikeBecauseItsActuallyAHexPattern(HexPattern pat) implements HexPatternLike{ + public List getNonZappyPoints(){ + return pat.toLines(1, Vec2.ZERO); + } + + public String getName(){ + return pat.getStartDir() + "-" + pat.anglesSignature(); + } + + public Set getDups(){ + return RenderLib.findDupIndices(pat.positions()); + } + } + + record PureLines(List lines, String name) implements HexPatternLike{ + + public List getNonZappyPoints(){ + return lines; + } + + public String getName(){ + return name; + } + + public Set getDups(){ + return RenderLib.findDupIndices( + lines().stream().map(p -> + // I hate mojang + new Vec2(p.x, p.y){ + @Override public boolean equals(Object other){ + if(other instanceof Vec2 otherVec) return p.equals(otherVec); + return false; + } + + @Override public int hashCode(){ return Objects.hash(p.x, p.y); } + }).toList() + ); + } + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternPoints.java b/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternPoints.java index b033e8de36..1a55d06b5e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternPoints.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternPoints.java @@ -1,15 +1,134 @@ package at.petrak.hexcasting.client.render; +import com.google.common.collect.ImmutableList; import net.minecraft.world.phys.Vec2; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +/** + * static points making up a hex pattern to be rendered. It's used primarily for positioning, so we keep a + * number of extra values here to avoid recomputing them. + */ public class HexPatternPoints { - public List zappyPoints = null; - public String pointsKey = null; //TODO: if a string key isnt performant enough override hashcode for points + public final ImmutableList zappyPoints; + public final ImmutableList zappyPointsScaled; - public HexPatternPoints(List zappyPoints) { - this.zappyPoints = zappyPoints; - pointsKey = PatternTextureManager.getPointsKey(zappyPoints); + public final ImmutableList dotsScaled; + + public final double rangeX; + public final double rangeY; + public final double finalScale; + + public final double fullWidth; + public final double fullHeight; + + private double minX = Double.MAX_VALUE; + private double minY = Double.MAX_VALUE; + + private final double offsetX; + private final double offsetY; + + private static final ConcurrentMap CACHED_STATIC_POINTS = new ConcurrentHashMap<>(); + + private HexPatternPoints(HexPatternLike patternlike, PatternSettings patSets, double seed) { + + List dots = patternlike.getNonZappyPoints(); + + // always do space calculations with the static version of the pattern + // so that it doesn't jump around resizing itself. + List zappyPoints = RenderLib.makeZappy(dots, patternlike.getDups(), + patSets.getHops(), patSets.getVariance(), 0f, patSets.getFlowIrregular(), + patSets.getReadabilityOffset(), patSets.getLastSegmentProp(), seed); + + + this.zappyPoints = ImmutableList.copyOf(zappyPoints); + double maxY = Double.MIN_VALUE; + double maxX = Double.MIN_VALUE; + for (Vec2 point : zappyPoints) { + minX = Math.min(minX, point.x); + maxX = Math.max(maxX, point.x); + minY = Math.min(minY, point.y); + maxY = Math.max(maxY, point.y); + } + rangeX = maxX - minX; + rangeY = maxY - minY; + + // scales the patterns so that each point is patSets.baseScale units apart + double baseScale = patSets.getBaseScale() / 1.5; + + // size of the pattern in pose space with no other adjustments + double baseWidth = rangeX * baseScale; + double baseHeight = rangeY * baseScale; + + // make sure that the scale fits within our min sizes + double scale = Math.max(1.0, Math.max( + (patSets.getMinWidth() - patSets.getStrokeWidthGuess()) / baseWidth, + (patSets.getMinHeight() - patSets.getStrokeWidthGuess()) / baseHeight) + ); + + boolean vertFit = patSets.getVertAlignment().fit; + boolean horFit = patSets.getHorAlignment().fit; + + // scale down if needed to fit in vertical space + if(vertFit){ + scale = Math.min(scale, (patSets.getTargetHeight() - 2 * patSets.getVertPadding() - patSets.getStrokeWidthGuess())/(baseHeight)); + } + + // scale down if needed to fit in horizontal space + if(horFit){ + scale = Math.min(scale, (patSets.getTargetWidth() - 2 * patSets.getHorPadding() - patSets.getStrokeWidthGuess())/(baseWidth)); + } + + finalScale = baseScale * scale; + double finalStroke = patSets.getStrokeWidth(finalScale); + + double inherentWidth = (baseWidth * scale) + 2 * patSets.getHorPadding() + finalStroke; + double inherentHeight = (baseHeight * scale) + 2 * patSets.getVertPadding() + finalStroke; + + // this is the amount of actual wiggle room we have for configurable position-ing. + double widthDiff = Math.max(patSets.getTargetWidth() - inherentWidth, 0); + double heightDiff = Math.max(patSets.getTargetHeight() - inherentHeight, 0); + + this.fullWidth = inherentWidth + widthDiff; + this.fullHeight = inherentHeight + heightDiff; + + // center in inherent space and put extra space according to alignment stuff + offsetX = ((inherentWidth - baseWidth * scale) / 2) + (widthDiff * patSets.getHorAlignment().amtInFront / 2); + offsetY = ((inherentHeight - baseHeight * scale) / 2) + (heightDiff * patSets.getVertAlignment().amtInFront / 2); + + this.zappyPointsScaled = ImmutableList.copyOf(scaleVecs(zappyPoints)); + this.dotsScaled = ImmutableList.copyOf(scaleVecs(dots)); + } + + public Vec2 scaleVec(Vec2 point){ + return new Vec2( + (float) (((point.x - this.minX) * this.finalScale) + this.offsetX), + (float) (((point.y - this.minY) * this.finalScale) + this.offsetY) + ); + } + + public List scaleVecs(List points){ + List scaledPoints = new ArrayList<>(); + for (Vec2 point : points) { + scaledPoints.add(scaleVec(point)); + } + return scaledPoints; + } + + + /** + * Gets the static points for the given pattern, settings, and seed. This is cached. + * + * This is used in rendering static patterns and positioning non-static patterns. + * + */ + public static HexPatternPoints getStaticPoints(HexPatternLike patternlike, PatternSettings patSets, double seed){ + + String cacheKey = patSets.getCacheKey(patternlike, seed); + + return CACHED_STATIC_POINTS.computeIfAbsent(cacheKey, (key) -> new HexPatternPoints(patternlike, patSets, seed) ); } } \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/PATTERN_RENDER_LORE.md b/Common/src/main/java/at/petrak/hexcasting/client/render/PATTERN_RENDER_LORE.md new file mode 100644 index 0000000000..fdeb3eb6e1 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PATTERN_RENDER_LORE.md @@ -0,0 +1,52 @@ +# Pattern Rendering Lore + +This is an overview of the new pattern rendering systems introduced alongside the Inline pattern rendering + +## Brief History / Motivations + +In v0.10.3 (and probably before) the pattern rendering was well known for causing lag if many patterns were rendered at once. +The pattern code was also duplicated *a lot*, pretty much anywhere that did pattern rendering needed to do it slightly different and so the rendering/positioning code got copy-pasted all around, frequently with a lot of magic numbers. + +During 1.20 development, we [added texture based rendering](https://github.com/FallingColors/HexMod/pull/555), and switched most static rendering over to it. There was still a fair bit of duplicate code though, especially with pattern positioning. + +Now with the new system, all of the rendering is contained to a few classes and outside users (such as slates for example) can specify how they want patterns to be rendered using PatternSettings and PatternColors. + +## System Walkthrough (External) + +### PatternRenderer + +This is the main entrypoint for pattern rendering. It has 3 main methods, all called `renderPattern`. One is the driver method and the others are convenience wrappers. + +Generally the idea here is that you shouldn't need to worry about whether the pattern will be rendered as a texture or dynamically, the `PatternRenderer` will make that decision, prefering the texture renderer when it can. The dynamic renderer will be used if the pattern is moving (speed != 0), if the pattern has a gradient stroke, or if the texture isn't ready yet. + +### PatternSettings + +This is where the vast majority of the rendering configuration happens. Arguably it is overkill. + +It's a class with many getters constructed from 3 records: `PositionSettings`, `StrokeSettings`, and `ZappySettings`. The getters can be overridden when/if needed, the records are more for user convenience. See javadocs for details on what can be configured here. + +Pattern textures are also generated based on settings, so it's **VERY ENCOURAGED** to re-use pattern settings when you can. + +### PatternColors + +This is just a simple record holding colors for different parts of pattern drawing. It has probably too many helpers. The main thing to note here is that you can set the alpha to 0 to skip rendering a section (such as dots or innerStroke). Transparent colors for strokes are **discouraged** due to the dynamic renderer having a sort of internal overlapping that is only noticeable with transparent strokes. + +### WorldlyPatternRenderHelpers + +This is where all the worldly base-hex renders ended up. Good to look at for examples of using the renderer and some pattern settings that could be re-used. + +## System Walkthrough (Internal) + +### HexPatternPoints + +This is where the positioning actually happens. It generates dots and zappy points based on the pattern and PatternSettings passed in. This object is then cached to prevent needing to calculate it all each frame. Note that this includes scaling and all that, the returned zappy points are in pose units. + +### VCDrawHelper (& RenderLib changes) + +We do a silly with this one lol. This allows us to separate the lower level vertex handling from the higher level 'drawing'. + +Previously `RenderLib.drawLineSeq(..)` drew straight to the tesselator with the `POSITION_COLOR` shader/format. Now it just passes color and position data to the `VCDrawHelper` that we give it, allowing us to create and push a vertex however we want to wherever we want. This lets us draw to other vertex consumers and use other shaders/formats, like the `EntityTranslucentCull` that we use for worldly rendering with light and normals. + +To maintain API stability we have the previous `RenderLib.drawLineSeq(..)` method signature just call the new version using the `Basic` draw helper. + +Conveniently we can also use this for drawing our textures! \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternColors.java b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternColors.java new file mode 100644 index 0000000000..fb64669834 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternColors.java @@ -0,0 +1,68 @@ +package at.petrak.hexcasting.client.render; + +/** + * An immutable wrapper for pattern colors. + *

+ * This is separate from PatternRenderSettings because it does not affect the shape of the pattern, so we can re-use + * those parts for different colors. + */ +public record PatternColors(int innerStartColor, int innerEndColor, int outerStartColor, int outerEndColor, + int startingDotColor, int gridDotsColor){ + + // keep some handy frequently used colors here. + public static final PatternColors DEFAULT_PATTERN_COLOR = new PatternColors(0xff_554d54, 0xff_d2c8c8); + public static final PatternColors DIMMED_COLOR = new PatternColors(0xFF_B4AAAA, 0xff_d2c8c8); + public static final PatternColors DEFAULT_GRADIENT_COLOR = DEFAULT_PATTERN_COLOR.withGradientEnds(DIMMED_COLOR); + + public static final int STARTING_DOT = 0xff_5b7bd7; + public static final int GRID_DOTS = 0x80_d2c8c8; + + public static final PatternColors READABLE_SCROLL_COLORS = DEFAULT_PATTERN_COLOR.withDots(true, false); + public static final PatternColors READABLE_GRID_SCROLL_COLORS = DEFAULT_PATTERN_COLOR.withDots(true, true); + + public static final PatternColors SLATE_WOBBLY_COLOR = glowyStroke( 0xff_64c8ff); // old blue color + public static final PatternColors SLATE_WOBBLY_PURPLE_COLOR = glowyStroke(0xff_cfa0f3); // shiny new purple one :) + + // no gradient + public PatternColors(int innerColor, int outerColor){ + this(innerColor, innerColor, outerColor, outerColor, 0, 0); + } + + // single color -- no inner layer + public static PatternColors singleStroke(int color){ + return new PatternColors(0, color); + } + + // makes a stroke color similar to the glowy effect that slates have. + public static PatternColors glowyStroke(int color){ + return new PatternColors(RenderLib.screenCol(color), color); + } + + public static PatternColors gradientStrokes(int innerStartColor, int innerEndColor, int outerStartColor, int outerEndColor){ + return new PatternColors(innerStartColor, innerEndColor, outerStartColor, outerEndColor, 0, 0); + } + + // a single stroke with a gradient -- no inner layer. + public static PatternColors gradientStroke(int startColor, int endColor){ + return PatternColors.gradientStrokes(0, 0, startColor, endColor); + } + + public PatternColors withGradientEnds(int endColorInner, int endColorOuter){ + return new PatternColors(this.innerStartColor, endColorInner, this.outerStartColor, endColorOuter, this.startingDotColor, this.gridDotsColor); + } + + public PatternColors withGradientEnds(PatternColors end){ + return withGradientEnds(end.innerEndColor, end.outerEndColor); + } + + // add dots -- note, this is how you tell the renderer to make dots + public PatternColors withDotColors(int startingDotColor, int gridDotsColor){ + return new PatternColors(this.innerStartColor, this.innerEndColor, this.outerStartColor, this.outerEndColor, + startingDotColor, gridDotsColor); + } + + // adds dots with the default colors. + public PatternColors withDots(boolean startingDot, boolean gridDots){ + return withDotColors(startingDot ? STARTING_DOT : 0, gridDots ? GRID_DOTS : 0); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternRenderer.java b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternRenderer.java new file mode 100644 index 0000000000..8fd307a9a4 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternRenderer.java @@ -0,0 +1,151 @@ +package at.petrak.hexcasting.client.render; + + +import at.petrak.hexcasting.api.casting.math.HexPattern; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FastColor; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class PatternRenderer { + + public static void renderPattern(HexPattern pattern, PoseStack ps, PatternSettings patSets, PatternColors patColors, double seed, int resPerUnit) { + renderPattern(pattern, ps, null, patSets, patColors, seed, resPerUnit); + } + + public static void renderPattern(HexPattern pattern, PoseStack ps, @Nullable WorldlyBits worldlyBits, PatternSettings patSets, PatternColors patColors, double seed, int resPerUnit) { + renderPattern(HexPatternLike.of(pattern), ps, worldlyBits, patSets, patColors, seed, resPerUnit); + } + + /** + * Renders a pattern (or rather a pattern-like) according to the given settings. + * @param patternlike the pattern (or more generally the lines) to render. + * @param ps pose/matrix stack to render based on. (0,0) is treated as the top left corner. The size of the render is determined by patSets. + * @param worldlyBits used for rendering with light/normals/render-layers if possible. This is optional and probably shouldn't be used for UI rendering. + * @param patSets settings that control how the pattern is drawn. + * @param patColors colors to use for drawing the pattern and dots. + * @param seed seed to use for zappy wobbles. + * @param resPerUnit the texture resolution per pose unit space to be used *if* the texture renderer is used. + */ + public static void renderPattern(HexPatternLike patternlike, PoseStack ps, @Nullable WorldlyBits worldlyBits, PatternSettings patSets, PatternColors patColors, double seed, int resPerUnit){ + var oldShader = RenderSystem.getShader(); + HexPatternPoints staticPoints = HexPatternPoints.getStaticPoints(patternlike, patSets, seed); + + boolean shouldRenderDynamic = true; + + // only do texture rendering if it's static and has solid colors + if(patSets.getSpeed() == 0 && PatternTextureManager.useTextures && patColors.innerStartColor() == patColors.innerEndColor() + && patColors.outerStartColor() == patColors.outerEndColor()){ + boolean didRender = renderPatternTexture(patternlike, ps, worldlyBits, patSets, patColors, seed, resPerUnit); + if(didRender) shouldRenderDynamic = false; + } + if(shouldRenderDynamic){ + List zappyPattern; + + if(patSets.getSpeed() == 0) { + // re-use our static points if we're rendering a static pattern anyway + zappyPattern = staticPoints.zappyPoints; + } else { + List nonzappyLines = patternlike.getNonZappyPoints(); + Set dupIndices = RenderLib.findDupIndices(nonzappyLines); + zappyPattern = RenderLib.makeZappy(nonzappyLines, dupIndices, + patSets.getHops(), patSets.getVariance(), patSets.getSpeed(), patSets.getFlowIrregular(), + patSets.getReadabilityOffset(), patSets.getLastSegmentProp(), seed); + } + + List zappyRenderSpace = staticPoints.scaleVecs(zappyPattern); + + if(FastColor.ARGB32.alpha(patColors.outerEndColor()) != 0 && FastColor.ARGB32.alpha(patColors.outerStartColor()) != 0){ + RenderLib.drawLineSeq(ps.last().pose(), zappyRenderSpace, (float)patSets.getOuterWidth(staticPoints.finalScale), + patColors.outerStartColor(), patColors.outerEndColor(), VCDrawHelper.getHelper(worldlyBits, ps,outerZ)); + } + if(FastColor.ARGB32.alpha(patColors.innerEndColor()) != 0 && FastColor.ARGB32.alpha(patColors.innerStartColor()) != 0) { + RenderLib.drawLineSeq(ps.last().pose(), zappyRenderSpace, (float)patSets.getInnerWidth(staticPoints.finalScale), + patColors.innerStartColor(), patColors.innerEndColor(), VCDrawHelper.getHelper(worldlyBits, ps,innerZ)); + } + } + + // render dots and grid dynamically + + float dotZ = 0.0011f; + + if(FastColor.ARGB32.alpha(patColors.startingDotColor()) != 0) { + RenderLib.drawSpot(ps.last().pose(), staticPoints.dotsScaled.get(0), (float)patSets.getStartDotRadius(staticPoints.finalScale), + patColors.startingDotColor(), VCDrawHelper.getHelper(worldlyBits, ps, dotZ)); + } + + if(FastColor.ARGB32.alpha(patColors.gridDotsColor()) != 0) { + for(int i = 1; i < staticPoints.dotsScaled.size(); i++){ + Vec2 gridDot = staticPoints.dotsScaled.get(i); + RenderLib.drawSpot(ps.last().pose(), gridDot, (float)patSets.getGridDotsRadius(staticPoints.finalScale), + patColors.gridDotsColor(), VCDrawHelper.getHelper(worldlyBits, ps, dotZ)); + } + } + + RenderSystem.setShader(() -> oldShader); + } + + private static final float outerZ = 0.0005f; + private static final float innerZ = 0.001f; + + private static boolean renderPatternTexture(HexPatternLike patternlike, PoseStack ps, @Nullable WorldlyBits worldlyBits, PatternSettings patSets, PatternColors patColors, double seed, int resPerUnit){ + Optional> maybeTextures = PatternTextureManager.getTextures(patternlike, patSets, seed, resPerUnit); + if(maybeTextures.isEmpty()){ + return false; + } + + Map textures = maybeTextures.get(); + HexPatternPoints staticPoints = HexPatternPoints.getStaticPoints(patternlike, patSets, seed); + + VertexConsumer vc; + + if(FastColor.ARGB32.alpha(patColors.outerStartColor()) != 0) { + VCDrawHelper vcHelper = VCDrawHelper.getHelper(worldlyBits, ps, outerZ, textures.get("outer")); + vc = vcHelper.vcSetupAndSupply(VertexFormat.Mode.QUADS); + + int cl = patColors.outerStartColor(); + + vcHelper.vertex(vc, cl, new Vec2(0, 0), new Vec2(0, 0), ps.last().pose()); + vcHelper.vertex(vc, cl, new Vec2(0, (float) staticPoints.fullHeight), new Vec2(0, 1), ps.last().pose()); + vcHelper.vertex(vc, cl, new Vec2((float) staticPoints.fullWidth, (float) staticPoints.fullHeight), new Vec2(1, 1), ps.last().pose()); + vcHelper.vertex(vc, cl, new Vec2((float) staticPoints.fullWidth, 0), new Vec2(1, 0), ps.last().pose()); + + vcHelper.vcEndDrawer(vc); + } + + if(FastColor.ARGB32.alpha(patColors.innerStartColor()) != 0) { + VCDrawHelper vcHelper = VCDrawHelper.getHelper(worldlyBits, ps, innerZ, textures.get("inner")); + vc = vcHelper.vcSetupAndSupply(VertexFormat.Mode.QUADS); + + int cl = patColors.innerStartColor(); + + vcHelper.vertex(vc, cl, new Vec2(0, 0), new Vec2(0, 0), ps.last().pose()); + vcHelper.vertex(vc, cl, new Vec2(0, (float) staticPoints.fullHeight), new Vec2(0, 1), ps.last().pose()); + vcHelper.vertex(vc, cl, new Vec2((float) staticPoints.fullWidth, (float) staticPoints.fullHeight), new Vec2(1, 1), ps.last().pose()); + vcHelper.vertex(vc, cl, new Vec2((float) staticPoints.fullWidth, 0), new Vec2(1, 0), ps.last().pose()); + + vcHelper.vcEndDrawer(vc); + } + + return true; + } + + // TODO did we want to un-hardcode this for accessibility reasons ? + public static boolean shouldDoStrokeGradient(){ + return Screen.hasControlDown(); + } + + public record WorldlyBits(@Nullable MultiBufferSource provider, Integer light, Vec3 normal){} +} diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternSettings.java b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternSettings.java new file mode 100644 index 0000000000..4cb223f6f6 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternSettings.java @@ -0,0 +1,142 @@ +package at.petrak.hexcasting.client.render; + +/** + * A class holding settings for shaping and positioning patterns. + * + * By default, it's a simple wrapper of 3 records, however some use cases may require extending and overriding getters. + * This is done to keep the complexity of the records a bit lower. + */ +public class PatternSettings { + + public PatternSettings(String name, PositionSettings posSets, StrokeSettings strokeSets, ZappySettings zapSets){ + this.name = name; + this.posSets = posSets; + this.strokeSets = strokeSets; + this.zapSets = zapSets; + } + + /** + * Settings for positioning the pattern and defining its general size/render area. All values are in 'pose units', + * meaning we use them directly with the pose/matrix stack given to the renderer. + * + *

+ * We do a first pass at the pattern scale using baseScale. We then make sure it's larger than minWidth and + * minHeight. Then on each axis, if that axis is has a FIT alignment then we may scale down the pattern to make sure it + * fits. Note that the padding is not scaled and is always respected. + *

+ */ + public record PositionSettings(double spaceWidth, double spaceHeight, double hPadding, double vPadding, + AxisAlignment hAxis, AxisAlignment vAxis, double baseScale, double minWidth, double minHeight){ + /** + * Makes settings ideal for rendering in a square. This helper exists because this is the most common positioning + * pattern. + * @param padding a value 0-0.5 for how much padding should go on each side. + * @return a PositionSettings object in a 1x1 space with the given padding value such that the pattern is centered + */ + public static PositionSettings paddedSquare(double padding){ + return paddedSquare(padding, 0.25, 0); + } + + public static PositionSettings paddedSquare(double padding, double baseScale, double minSize){ + return new PositionSettings(1.0, 1.0, padding, padding, AxisAlignment.CENTER_FIT, AxisAlignment.CENTER_FIT, baseScale, minSize, minSize); + } + } + + /** + * Settings for stroke and dot sizings. If you want to *not* render dots or inner/outer you should prefer setting + * alpha to 0 in PatternColors. + */ + public record StrokeSettings(double innerWidth, double outerWidth, + double startDotRadius, double gridDotsRadius){ + public static StrokeSettings fromStroke(double stroke){ + return new StrokeSettings(stroke * 2.0/5.0, stroke, 0.8 * stroke * 2.0 / 5.0, 0.4 * stroke * 2.0 / 5.0); + } + } + + /** + * Controls how the pattern is zappified. + * + * @param hops number of little pulses + * @param variance how jumpy/distorting the pulses are + * @param speed how fast the pulses go + * @param flowIrregular randomness of pulse travel + * @param readabilityOffset how curved inward the corners are + * @param lastSegmentLenProportion length of the last segment relative to the others. used for increased readability. + */ + public record ZappySettings(int hops, float variance, float speed, float flowIrregular, float readabilityOffset, float lastSegmentLenProportion){ + public static float READABLE_OFFSET = 0.2f; + public static float READABLE_SEGMENT = 0.8f; + public static ZappySettings STATIC = new ZappySettings(10, 0.5f, 0f, 0.2f, 0, 1f); + public static ZappySettings READABLE = new ZappySettings(10, 0.5f, 0f, 0.2f, READABLE_OFFSET, READABLE_SEGMENT); + public static ZappySettings WOBBLY = new ZappySettings(10, 2.5f, 0.1f, 0.2f, 0, 1f); + } + + public String getCacheKey(HexPatternLike patternlike, double seed){ + return (patternlike.getName() + "-" + getName() + "-" + seed).toLowerCase(); + } + + // determines how the pattern is fit and aligned on a given axis + public enum AxisAlignment{ + // These 3 scale the pattern down to fit if needed. + BEGIN_FIT(true, 0), + CENTER_FIT(true, 1), + END_FIT(true, 2), + // these 3 do *not* scale the pattern down, it will overflow if needed. + BEGIN(false, 0), + CENTER(false, 1), + END(false, 2); + + public final boolean fit; + public final int amtInFront; // how many halves go in front. yes it's a weird way to do it. + + AxisAlignment(boolean fit, int amtInFront){ + this.fit = fit; + this.amtInFront = amtInFront; + } + } + + private final String name; + // leaving these public for more convenient chaining. Should prefer using the getters for overrideability. + public final PositionSettings posSets; + public final StrokeSettings strokeSets; + public final ZappySettings zapSets; + + public String getName(){ return name; } + + public double getTargetWidth(){ return posSets.spaceWidth; } + public double getTargetHeight(){ return posSets.spaceHeight; } + + public double getHorPadding(){ return posSets.hPadding; } + public double getVertPadding(){ return posSets.vPadding; } + + public AxisAlignment getHorAlignment(){ return posSets.hAxis; } + public AxisAlignment getVertAlignment(){ return posSets.vAxis; } + + public double getBaseScale(){ return posSets.baseScale; } + public double getMinWidth(){ return posSets.minWidth; } + public double getMinHeight(){ return posSets.minHeight; } + + /* these sizing getters take in the final pattern scale so that patterns can vary their stroke width when squished. + * the records keep a static value since that's fine for *most* use cases, override these methods if you need to use them. + * note that these widths are still in pose space units. + */ + + public double getInnerWidth(double scale){ return strokeSets.innerWidth; } + public double getOuterWidth(double scale){ return strokeSets.outerWidth; } + + public double getStartDotRadius(double scale){ return strokeSets.startDotRadius; } + public double getGridDotsRadius(double scale){ return strokeSets.gridDotsRadius; } + + public double getStrokeWidth(double scale){ return Math.max(getOuterWidth(scale), getInnerWidth(scale)); } + + // we have a stroke guess getter so that we can *try* to account for the stroke size when fitting the pattern. + public double getStrokeWidthGuess(){ return Math.max(strokeSets.outerWidth, strokeSets.innerWidth); } + + public int getHops(){ return zapSets.hops; } + public float getVariance(){ return zapSets.variance; } + public float getFlowIrregular(){ return zapSets.flowIrregular; } + public float getReadabilityOffset(){ return zapSets.readabilityOffset; } + public float getLastSegmentProp(){ return zapSets.lastSegmentLenProportion; } + + public float getSpeed(){ return zapSets.speed; } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternTextureManager.java b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternTextureManager.java index c28116900f..8486124920 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternTextureManager.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternTextureManager.java @@ -1,340 +1,117 @@ package at.petrak.hexcasting.client.render; -import at.petrak.hexcasting.api.block.HexBlockEntity; -import at.petrak.hexcasting.api.casting.math.HexPattern; -import at.petrak.hexcasting.common.blocks.akashic.BlockAkashicBookshelf; -import at.petrak.hexcasting.common.blocks.akashic.BlockEntityAkashicBookshelf; -import at.petrak.hexcasting.common.blocks.circles.BlockEntitySlate; -import at.petrak.hexcasting.common.blocks.circles.BlockSlate; import com.mojang.blaze3d.platform.NativeImage; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import com.mojang.math.Axis; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.texture.DynamicTexture; -import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Tuple; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.AttachFace; import net.minecraft.world.phys.Vec2; -import org.joml.Matrix3f; -import org.joml.Matrix4f; import java.awt.*; import java.awt.image.BufferedImage; -import java.util.HashMap; import java.util.List; +import java.util.*; import java.util.concurrent.*; -import java.util.stream.Collectors; public class PatternTextureManager { //TODO: remove if not needed anymore for comparison public static boolean useTextures = true; public static int repaintIndex = 0; - public static int resolutionScaler = 4; - public static int fastRenderScaleFactor = 8; // e.g. this is 8, resolution is 1024, so render at 1024/8 = 128 - public static int resolutionByBlockSize = 128 * resolutionScaler; - public static int paddingByBlockSize = 16 * resolutionScaler; - public static int circleRadiusByBlockSize = 2 * resolutionScaler; - public static int scaleLimit = 4 * resolutionScaler; - public static int scrollLineWidth = 3 * resolutionScaler; - public static int otherLineWidth = 4 * resolutionScaler; - - public static void setResolutionScaler(int resolutionScaler) { - PatternTextureManager.resolutionScaler = resolutionScaler; - resolutionByBlockSize = 128 * resolutionScaler; - paddingByBlockSize = 16 * resolutionScaler; - circleRadiusByBlockSize = 2 * resolutionScaler; - scaleLimit = 4 * resolutionScaler; - scrollLineWidth = 3 * resolutionScaler; - otherLineWidth = 4 * resolutionScaler; - } - - private static final ConcurrentMap patternTexturesToAdd = new ConcurrentHashMap<>(); + private static final ConcurrentMap> patternTexturesToAdd = new ConcurrentHashMap<>(); + private static final Set inProgressPatterns = new HashSet<>(); // basically newCachedThreadPool, but with a max pool size private static final ExecutorService executor = new ThreadPoolExecutor(0, 16, 60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>()); + private static final HashMap> patternTextures = new HashMap<>(); - private static final HashMap patternTextures = new HashMap<>(); - - public static String getPointsKey(List zappyPoints) - { - return zappyPoints.stream() - .map(p -> String.format("(%f,%f)", p.x, p.y)) - .collect(Collectors.joining(";")); - } - - public static HexPatternPoints generateHexPatternPoints(HexBlockEntity tile, HexPattern pattern, float flowIrregular) - { - var stupidHash = tile.getBlockPos().hashCode(); - var lines1 = pattern.toLines(1, Vec2.ZERO); - var zappyPoints = RenderLib.makeZappy(lines1, RenderLib.findDupIndices(pattern.positions()), - 10, 0.5f, 0f, flowIrregular, 0f, 1f, stupidHash); - return new HexPatternPoints(zappyPoints); - } - - public static void renderPatternForScroll(String pointsKey, PoseStack ps, MultiBufferSource bufSource, int light, List zappyPoints, int blockSize, boolean showStrokeOrder) - { - renderPattern(pointsKey, ps, bufSource, light, zappyPoints, blockSize, showStrokeOrder, false, true, false,false, true,-1); - } - public static void renderPatternForSlate(BlockEntitySlate tile, HexPattern pattern, PoseStack ps, MultiBufferSource buffer, int light, BlockState bs) - { - if(tile.points == null) - tile.points = generateHexPatternPoints(tile, pattern, 0.2f); - - boolean isOnWall = bs.getValue(BlockSlate.ATTACH_FACE) == AttachFace.WALL; - boolean isOnCeiling = bs.getValue(BlockSlate.ATTACH_FACE) == AttachFace.CEILING; - int facing = bs.getValue(BlockSlate.FACING).get2DDataValue(); + public static Optional> getTextures(HexPatternLike patternlike, PatternSettings patSets, double seed, int resPerUnit) { + String patCacheKey = patSets.getCacheKey(patternlike, seed) + "_" + resPerUnit; - renderPatternForBlockEntity(tile.points, ps, buffer, light, isOnWall, isOnCeiling, true, facing); - } - public static void renderPatternForAkashicBookshelf(BlockEntityAkashicBookshelf tile, HexPattern pattern, PoseStack ps, MultiBufferSource buffer, int light, BlockState bs) - { - if(tile.points == null) - tile.points = generateHexPatternPoints(tile, pattern, 0f); + // move textures from concurrent map to normal hashmap as needed + if (patternTexturesToAdd.containsKey(patCacheKey)) { + var patternTexture = patternTexturesToAdd.remove(patCacheKey); + var oldPatternTexture = patternTextures.put(patCacheKey, patternTexture); + inProgressPatterns.remove(patCacheKey); + if (oldPatternTexture != null) // TODO: is this needed? when does this ever happen? + for(ResourceLocation oldPatternTextureSingle : oldPatternTexture.values()) + Minecraft.getInstance().getTextureManager().getTexture(oldPatternTextureSingle).close(); - int facing = bs.getValue(BlockAkashicBookshelf.FACING).get2DDataValue(); - renderPatternForBlockEntity(tile.points, ps, buffer, light, true, false, false, facing); - } - - public static void renderPatternForBlockEntity(HexPatternPoints points, PoseStack ps, MultiBufferSource buffer, int light, boolean isOnWall, boolean isOnCeiling, boolean isSlate, int facing) - { - var oldShader = RenderSystem.getShader(); - ps.pushPose(); - RenderSystem.setShader(GameRenderer::getPositionTexShader); - renderPattern(points.pointsKey, ps, buffer, light, points.zappyPoints, 1, false, true, isOnWall, isOnCeiling, isSlate, false, facing); - ps.popPose(); - RenderSystem.setShader(() -> oldShader); - } - - public static void renderPattern(String pointsKey, PoseStack ps, MultiBufferSource bufSource, int light, List zappyPoints, int blockSize, boolean showStrokeOrder, boolean useFullSize, boolean isOnWall, boolean isOnCeiling, boolean isSlate, boolean isScroll, int facing) - { - ps.pushPose(); - - PoseStack.Pose last = ps.last(); - Matrix4f mat = last.pose(); - Matrix3f normal = last.normal(); - - float x = blockSize, y = blockSize, z = (-1f / 16f) - 0.01f; - float nx = 0, ny = 0, nz = 0; - - //TODO: refactor this mess of a method - - if(isOnWall) - { - if(isScroll) - { - ps.translate(-blockSize / 2f, -blockSize / 2f, 1f / 32f); - nz = -1; - } - else - { - ps.mulPose(Axis.ZP.rotationDegrees(180)); - - if(isSlate) - { - if(facing == 0) - ps.translate(0,-1,0); - if(facing == 1) - ps.translate(-1,-1,0); - if(facing == 2) - ps.translate(-1,-1,1); - if(facing == 3) - ps.translate(0,-1,1); - } - else - { - z = -0.01f; - if(facing == 0) - ps.translate(0,-1,1); - if(facing == 1) - ps.translate(0,-1,0); - if(facing == 2) - ps.translate(-1,-1,0); - if(facing == 3) - ps.translate(-1,-1,1); - } - - if(facing == 0) - ps.mulPose(Axis.YP.rotationDegrees(180)); - if(facing == 1) - ps.mulPose(Axis.YP.rotationDegrees(270)); - if(facing == 3) - ps.mulPose(Axis.YP.rotationDegrees(90)); - - if(facing == 0 || facing == 2) - nz = -1; - if(facing == 1 || facing == 3) - nx = -1; - ps.translate(0,0,0); - } + return Optional.empty(); // try not giving it immediately to avoid flickering? } - else //slates on the floor or ceiling - { - if(facing == 0) - ps.translate(0,0,0); - if(facing == 1) - ps.translate(1,0,0); - if(facing == 2) - ps.translate(1,0,1); - if(facing == 3) - ps.translate(0,0,1); - ps.mulPose(Axis.YP.rotationDegrees(facing*-90)); + if (patternTextures.containsKey(patCacheKey)) + return Optional.of(patternTextures.get(patCacheKey)); - if(isOnCeiling) - { - ps.mulPose(Axis.XP.rotationDegrees(-90)); - ps.translate(0,-1,1); - } - else - ps.mulPose(Axis.XP.rotationDegrees(90)); - nz = -1; + // render a higher-resolution texture in a background thread so it eventually becomes all nice nice and pretty + if(!inProgressPatterns.contains(patCacheKey)){ + inProgressPatterns.add(patCacheKey); + executor.submit(() -> { + var slowTextures = createTextures(patternlike, patSets, seed, resPerUnit); + + // TextureManager#register doesn't look very thread-safe, so move back to the main thread after the slow part is done + Minecraft.getInstance().execute(() -> { + registerTextures(patCacheKey, slowTextures); + }); + }); } - - int lineWidth = otherLineWidth; - int outerColor = 0xff_d2c8c8; - int innerColor = 0xc8_322b33; - if(isScroll) - lineWidth = scrollLineWidth; - - ResourceLocation texture = getTexture(zappyPoints, pointsKey, blockSize, showStrokeOrder, lineWidth, useFullSize, new Color(innerColor), new Color(outerColor)); - VertexConsumer verts = bufSource.getBuffer(RenderType.entityCutout(texture)); - - vertex(mat, normal, light, verts, 0, 0, z, 0, 0, nx, ny, nz); - vertex(mat, normal, light, verts, 0, y, z, 0, 1, nx, ny, nz); - vertex(mat, normal, light, verts, x, y, z, 1, 1, nx, ny, nz); - vertex(mat, normal, light, verts, x, 0, z, 1, 0, nx, ny, nz); - - ps.popPose(); + return Optional.empty(); } - private static void vertex(Matrix4f mat, Matrix3f normal, int light, VertexConsumer verts, float x, float y, float z, - float u, float v, float nx, float ny, float nz) { - verts.vertex(mat, x, y, z) - .color(0xffffffff) - .uv(u, v).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(light) - .normal(normal, nx, ny, nz) - .endVertex(); - } + private static Map createTextures(HexPatternLike patternlike, PatternSettings patSets, double seed, int resPerUnit) { + HexPatternPoints staticPoints = HexPatternPoints.getStaticPoints(patternlike, patSets, seed); - public static ResourceLocation getTexture(List points, String pointsKey, int blockSize, boolean showsStrokeOrder, float lineWidth, boolean useFullSize, Color innerColor, Color outerColor) { - if (patternTexturesToAdd.containsKey(pointsKey)) { - var patternTexture = patternTexturesToAdd.remove(pointsKey); - var oldPatternTexture = patternTextures.put(pointsKey, patternTexture); - if (oldPatternTexture != null) - Minecraft.getInstance().getTextureManager().getTexture(oldPatternTexture).close(); + List zappyRenderSpace = staticPoints.scaleVecs(staticPoints.zappyPoints); - return patternTexture; - } - if (patternTextures.containsKey(pointsKey)) - return patternTextures.get(pointsKey); + Map patTexts = new HashMap<>(); - // render a higher-resolution texture in a background thread so it eventually becomes all nice nice and pretty - executor.submit(() -> { - var slowTexture = createTexture(points, blockSize, showsStrokeOrder, lineWidth, useFullSize, innerColor, outerColor, false); + NativeImage innerLines = drawLines(zappyRenderSpace, staticPoints, (float)patSets.getInnerWidth((staticPoints.finalScale)), resPerUnit); + patTexts.put("inner", new DynamicTexture(innerLines)); - // TextureManager#register doesn't look very thread-safe, so move back to the main thread after the slow part is done - Minecraft.getInstance().execute(() -> registerTexture(points, pointsKey, slowTexture, true)); - }); + NativeImage outerLines = drawLines(zappyRenderSpace, staticPoints, (float)patSets.getOuterWidth((staticPoints.finalScale)), resPerUnit); + patTexts.put("outer", new DynamicTexture(outerLines)); - // quickly create and cache a low-resolution texture so the client has something to look at - var fastTexture = createTexture(points, blockSize, showsStrokeOrder, lineWidth, useFullSize, innerColor, outerColor, true); - return registerTexture(points, pointsKey, fastTexture, false); + return patTexts; } - private static DynamicTexture createTexture(List points, int blockSize, boolean showsStrokeOrder, float lineWidth, boolean useFullSize, Color innerColor, Color outerColor, boolean fastRender) - { - int resolution = resolutionByBlockSize * blockSize; - int padding = paddingByBlockSize * blockSize; - - if (fastRender) { - resolution /= fastRenderScaleFactor; - padding /= fastRenderScaleFactor; - lineWidth /= (float)fastRenderScaleFactor; + private static Map registerTextures(String patTextureKeyBase, Map dynamicTextures) { + Map resLocs = new HashMap<>(); + for(Map.Entry textureEntry : dynamicTextures.entrySet()){ + String name = "hex_pattern_texture_" + patTextureKeyBase + "_" + textureEntry.getKey() + "_" + repaintIndex + ".png"; + ResourceLocation resourceLocation = Minecraft.getInstance().getTextureManager().register(name, textureEntry.getValue()); + resLocs.put(textureEntry.getKey(), resourceLocation); } + patternTexturesToAdd.put(patTextureKeyBase, resLocs); + return resLocs; + } - double minX = Double.MAX_VALUE, maxX = Double.MIN_VALUE, minY = Double.MAX_VALUE, maxY = Double.MIN_VALUE; - for (Vec2 point : points) - { - minX = Math.min(minX, point.x); - maxX = Math.max(maxX, point.x); - minY = Math.min(minY, point.y); - maxY = Math.max(maxY, point.y); - } - - double rangeX = maxX - minX; - double rangeY = maxY - minY; - - double scale = Math.min((resolution - 2 * padding) / rangeX, (resolution - 2 * padding) / rangeY); - - double limit = blockSize * scaleLimit; - if (!useFullSize && scale > limit) - scale = limit; - - double offsetX = ((resolution - 2 * padding) - rangeX * scale) / 2; - double offsetY = ((resolution - 2 * padding) - rangeY * scale) / 2; - - BufferedImage img = new BufferedImage(resolution, resolution, BufferedImage.TYPE_INT_ARGB); + private static NativeImage drawLines(List points, HexPatternPoints staticPoints, float unscaledLineWidth, int resPerUnit) { + BufferedImage img = new BufferedImage((int)(staticPoints.fullWidth*resPerUnit), (int)(staticPoints.fullHeight*resPerUnit), BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = img.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2d.setColor(outerColor); - g2d.setStroke(new BasicStroke((blockSize * 5f / 3f) * lineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); - drawLines(g2d, points, minX, minY, scale, offsetX, offsetY, padding); - - g2d.setColor(innerColor); - g2d.setStroke(new BasicStroke((blockSize * 2f / 3f) * lineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); - drawLines(g2d, points, minX, minY, scale, offsetX, offsetY, padding); - - - if (showsStrokeOrder) { - g2d.setColor(new Color(0xff_d77b5b)); - Tuple point = getTextureCoordinates(points.get(0), minX, minY, scale, offsetX, offsetY, padding); - int spotRadius = circleRadiusByBlockSize * blockSize; - drawHexagon(g2d, point.getA(), point.getB(), spotRadius); + g2d.setColor(new Color(0xFF_FFFFFF)); // set it to white so we can reuse the texture with different colors + g2d.setStroke(new BasicStroke(unscaledLineWidth * resPerUnit, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + for (int i = 0; i < points.size() - 1; i++) { + Tuple pointFrom = getTextureCoordinates(points.get(i), staticPoints, resPerUnit); + Tuple pointTo = getTextureCoordinates(points.get(i+1), staticPoints, resPerUnit); + g2d.drawLine(pointFrom.getA(), pointFrom.getB(), pointTo.getA(), pointTo.getB()); } - g2d.dispose(); - NativeImage nativeImage = new NativeImage(img.getWidth(), img.getHeight(), true); for (int y = 0; y < img.getHeight(); y++) for (int x = 0; x < img.getWidth(); x++) nativeImage.setPixelRGBA(x, y, img.getRGB(x, y)); - - return new DynamicTexture(nativeImage); - } - - private static ResourceLocation registerTexture(List points, String pointsKey, DynamicTexture dynamicTexture, boolean isSlow) { - // isSlow used to register different textures for the low-resolution, fastly rendered version of each texture - // and the high-resolution, slowly rendered version (this means the slow doesn't replace the fast in the texture manager, - // which causes occasional visual stuttering for a frame). - String name = "hex_pattern_texture_" + points.hashCode() + "_" + repaintIndex + "_" + (isSlow ? "slow" : "fast") + ".png"; - ResourceLocation resourceLocation = Minecraft.getInstance().getTextureManager().register(name, dynamicTexture); - patternTexturesToAdd.put(pointsKey, resourceLocation); - return resourceLocation; - } - - private static void drawLines(Graphics2D g2d, List points, double minX, double minY, double scale, double offsetX, double offsetY, int padding) { - for (int i = 0; i < points.size() - 1; i++) { - Tuple pointFrom = getTextureCoordinates(points.get(i), minX, minY, scale, offsetX, offsetY, padding); - Tuple pointTo = getTextureCoordinates(points.get(i+1), minX, minY, scale, offsetX, offsetY, padding); - g2d.drawLine(pointFrom.getA(), pointFrom.getB(), pointTo.getA(), pointTo.getB()); - } + return nativeImage; } - private static Tuple getTextureCoordinates(Vec2 point, double minX, double minY, double scale, double offsetX, double offsetY, int padding) { - int x = (int) ((point.x - minX) * scale + offsetX) + padding; - int y = (int) ((point.y - minY) * scale + offsetY) + padding; + private static Tuple getTextureCoordinates(Vec2 point, HexPatternPoints staticPoints, int resPerUnit) { + int x = (int) ( point.x * resPerUnit); + int y = (int) ( point.y * resPerUnit); return new Tuple<>(x, y); } + // keeping this around just in case we ever decide to put the dots in the textures instead of dynamic private static void drawHexagon(Graphics2D g2d, int x, int y, int radius) { int fracOfCircle = 6; Polygon hexagon = new Polygon(); diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/RenderLib.kt b/Common/src/main/java/at/petrak/hexcasting/client/render/RenderLib.kt index 38d70fdfa8..823068439f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/RenderLib.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/RenderLib.kt @@ -4,7 +4,7 @@ package at.petrak.hexcasting.client.render import at.petrak.hexcasting.api.casting.math.HexPattern import at.petrak.hexcasting.api.mod.HexConfig -import at.petrak.hexcasting.api.utils.* +import at.petrak.hexcasting.api.utils.TAU import at.petrak.hexcasting.client.ClientTickCounter import at.petrak.hexcasting.client.gui.GuiSpellcasting import com.mojang.blaze3d.vertex.DefaultVertexFormat @@ -16,8 +16,8 @@ import net.minecraft.client.Minecraft import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.screens.Screen import net.minecraft.client.renderer.MultiBufferSource -import net.minecraft.core.BlockPos import net.minecraft.util.FastColor +import net.minecraft.util.FastColor.ARGB32 import net.minecraft.util.Mth import net.minecraft.world.entity.Entity import net.minecraft.world.level.Level @@ -42,6 +42,18 @@ const val CAP_THETA = 180f / 10f const val DEFAULT_READABILITY_OFFSET = 0.2f const val DEFAULT_LAST_SEGMENT_LEN_PROP = 0.8f + +fun drawLineSeq( + mat: Matrix4f, + points: List, + width: Float, + z: Float, + tail: Int, + head: Int +) { + return drawLineSeq(mat, points, width, tail, head, VCDrawHelper.Basic(z)) +} + /** * Draw a sequence of linePoints spanning the given points. * @@ -51,9 +63,9 @@ fun drawLineSeq( mat: Matrix4f, points: List, width: Float, - z: Float, tail: Int, head: Int, + vcHelper: VCDrawHelper ) { if (points.size <= 1) return @@ -61,6 +73,7 @@ fun drawLineSeq( val g1 = FastColor.ARGB32.green(tail).toFloat() val b1 = FastColor.ARGB32.blue(tail).toFloat() val a = FastColor.ARGB32.alpha(tail) + val a1 = a.toFloat() val headSource = if (Screen.hasControlDown() != HexConfig.client().ctrlTogglesOffStrokeOrder()) head else @@ -68,10 +81,9 @@ fun drawLineSeq( val r2 = FastColor.ARGB32.red(headSource).toFloat() val g2 = FastColor.ARGB32.green(headSource).toFloat() val b2 = FastColor.ARGB32.blue(headSource).toFloat() + val a2 = FastColor.ARGB32.alpha(headSource).toFloat() - // they spell it wrong at mojang lmao - val tess = Tesselator.getInstance() - val buf = tess.builder + var vc = vcHelper.vcSetupAndSupply(VertexFormat.Mode.TRIANGLES) val n = points.size val joinAngles = FloatArray(n) @@ -90,10 +102,6 @@ fun drawLineSeq( joinOffsets[i - 1] = Mth.clamp(Mth.sin(angle) / (1 + Mth.cos(angle)), -clamp, clamp) } - fun vertex(color: BlockPos, pos: Vec2) = - buf.vertex(mat, pos.x, pos.y, z).color(color.x, color.y, color.z, a).endVertex() - - buf.begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.POSITION_COLOR) for (i in 0 until points.size - 1) { val p1 = points[i] val p2 = points[i + 1] @@ -103,8 +111,9 @@ fun drawLineSeq( val tangent = p2.add(p1.negated()).normalized().scale(width * 0.5f) val normal = Vec2(-tangent.y, tangent.x) - fun color(time: Float): BlockPos = - BlockPos(Mth.lerp(time, r1, r2).toInt(), Mth.lerp(time, g1, g2).toInt(), Mth.lerp(time, b1, b2).toInt()) + fun color(time: Float): Int = + FastColor.ARGB32.color(Mth.lerp(time, a1, a2).toInt(), Mth.lerp(time, r1, r2).toInt(), + Mth.lerp(time, g1, g2).toInt(), Mth.lerp(time, b1, b2).toInt()) val color1 = color(i.toFloat() / n) val color2 = color((i + 1f) / n) @@ -118,21 +127,21 @@ fun drawLineSeq( val p2Down = p2.add(tangent.scale(Math.max(0f, jhigh)).negated()).add(normal) val p2Up = p2.add(tangent.scale(Math.max(0f, -jhigh)).negated()).add(normal.negated()) - vertex(color1, p1Down) - vertex(color1, p1) - vertex(color1, p1Up) + vcHelper.vertex(vc, color1, p1Down, mat) + vcHelper.vertex(vc, color1, p1, mat) + vcHelper.vertex(vc, color1, p1Up, mat) - vertex(color1, p1Down) - vertex(color1, p1Up) - vertex(color2, p2Up) + vcHelper.vertex(vc, color1, p1Down, mat) + vcHelper.vertex(vc, color1, p1Up, mat) + vcHelper.vertex(vc, color2, p2Up, mat) - vertex(color1, p1Down) - vertex(color2, p2Up) - vertex(color2, p2) + vcHelper.vertex(vc, color1, p1Down, mat) + vcHelper.vertex(vc, color2, p2Up, mat) + vcHelper.vertex(vc, color2, p2, mat) - vertex(color1, p1Down) - vertex(color2, p2) - vertex(color2, p2Down) + vcHelper.vertex(vc, color1, p1Down, mat) + vcHelper.vertex(vc, color2, p2, mat) + vcHelper.vertex(vc, color2, p2Down, mat) if (i > 0) { // Draw the connector to the next line segment @@ -150,9 +159,9 @@ fun drawLineSeq( val fan = rotate(rnormal, -sangle * (j.toFloat() / joinSteps)) val fanShift = Vec2(p1.x - fan.x, p1.y - fan.y) - vertex(color1, p1) - vertex(color1, prevVert) - vertex(color1, fanShift) + vcHelper.vertex(vc, color1, p1, mat) + vcHelper.vertex(vc, color1, prevVert, mat) + vcHelper.vertex(vc, color1, fanShift, mat) prevVert = fanShift } } else { @@ -162,32 +171,33 @@ fun drawLineSeq( val fan = rotate(normal, -sangle * (j.toFloat() / joinSteps)) val fanShift = Vec2(p1.x - fan.x, p1.y - fan.y) - vertex(color1, p1) - vertex(color1, prevVert) - vertex(color1, fanShift) + vcHelper.vertex(vc, color1, p1, mat) + vcHelper.vertex(vc, color1, prevVert, mat) + vcHelper.vertex(vc, color1, fanShift, mat) prevVert = fanShift } } } } - tess.end() + vcHelper.vcEndDrawer(vc) - fun drawCaps(color: BlockPos, point: Vec2, prev: Vec2) { + fun drawCaps(color: Int, point: Vec2, prev: Vec2) { val tangent = point.add(prev.negated()).normalized().scale(0.5f * width) val normal = Vec2(-tangent.y, tangent.x) val joinSteps = Mth.ceil(180f / CAP_THETA) - buf.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_COLOR) - vertex(color, point) + vc = vcHelper.vcSetupAndSupply(VertexFormat.Mode.TRIANGLE_FAN) + vcHelper.vertex(vc, color, point, mat) for (j in joinSteps downTo 0) { val fan = rotate(normal, -Mth.PI * (j.toFloat() / joinSteps)) - buf.vertex(mat, point.x + fan.x, point.y + fan.y, z).color(color.x, color.y, color.z, a).endVertex() + vcHelper.vertex(vc, color, Vec2(point.x + fan.x, point.y + fan.y), mat) } - tess.end() + vcHelper.vcEndDrawer(vc) } - drawCaps(BlockPos(r1.toInt(), g1.toInt(), b1.toInt()), points[0], points[1]) - drawCaps(BlockPos(r2.toInt(), g2.toInt(), b2.toInt()), points[n - 1], points[n - 2]) + drawCaps(ARGB32.color(a1.toInt(), r1.toInt(), g1.toInt(), b1.toInt()), points[0], points[1]) + drawCaps(ARGB32.color(a2.toInt(), r2.toInt(), g2.toInt(), b2.toInt()), points[n - 1], points[n - 2]) } + fun rotate(vec: Vec2, theta: Float): Vec2 { val cos = Mth.cos(theta) val sin = Mth.sin(theta) @@ -343,12 +353,12 @@ fun findDupIndices(pts: Iterable): Set { * include primitive drawing code... */ fun drawSpot(mat: Matrix4f, point: Vec2, radius: Float, r: Float, g: Float, b: Float, a: Float) { - val tess = Tesselator.getInstance() - val buf = tess.builder - // https://stackoverflow.com/questions/20394727/gl-triangle-strip-vs-gl-triangle-fan - // Starting point is the center - buf.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_COLOR) - buf.vertex(mat, point.x, point.y, 1f).color(r, g, b, a).endVertex() + drawSpot(mat, point, radius, ARGB32.color((a*255).toInt(), (r*255).toInt(), (g*255).toInt(), (b*255).toInt()), VCDrawHelper.Basic(1f)) +} + +fun drawSpot(mat: Matrix4f, point: Vec2, radius: Float, color: Int, vcHelper: VCDrawHelper) { + var vc = vcHelper.vcSetupAndSupply(VertexFormat.Mode.TRIANGLE_FAN); + vcHelper.vertex(vc, color, point, mat) // https://github.com/not-fl3/macroquad/blob/master/src/shapes.rs#L98 // yes they are gonna be little hexagons fite me @@ -358,10 +368,10 @@ fun drawSpot(mat: Matrix4f, point: Vec2, radius: Float, r: Float, g: Float, b: F val theta = i.toFloat() / fracOfCircle * TAU.toFloat() val rx = Mth.cos(theta) * radius + point.x val ry = Mth.sin(theta) * radius + point.y - buf.vertex(mat, rx, ry, 1f).color(r, g, b, a).endVertex() + vcHelper.vertex(vc, color, Vec2(rx, ry), mat) } - tess.end() + vcHelper.vcEndDrawer(vc) } fun screenCol(n: Int): Int { diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/VCDrawHelper.kt b/Common/src/main/java/at/petrak/hexcasting/client/render/VCDrawHelper.kt new file mode 100644 index 0000000000..fb6c734e5f --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/VCDrawHelper.kt @@ -0,0 +1,144 @@ +package at.petrak.hexcasting.client.render + +import at.petrak.hexcasting.api.HexAPI +import at.petrak.hexcasting.client.render.PatternRenderer.WorldlyBits +import com.ibm.icu.impl.CurrencyData.provider +import com.mojang.blaze3d.platform.GlStateManager +import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.* +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.GameRenderer +import net.minecraft.client.renderer.LightTexture +import net.minecraft.client.renderer.MultiBufferSource +import net.minecraft.client.renderer.RenderType +import net.minecraft.client.renderer.texture.OverlayTexture +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.phys.Vec2 +import net.minecraft.world.phys.Vec3 +import org.joml.Matrix4f + + +interface VCDrawHelper { + fun vcSetupAndSupply(vertMode: VertexFormat.Mode): VertexConsumer + fun vertex(vc: VertexConsumer, color: Int, pos: Vec2, matrix: Matrix4f){ + vertex(vc, color, pos, Vec2(0f,0f), matrix) + } + fun vertex(vc: VertexConsumer, color: Int, pos: Vec2, uv: Vec2, matrix: Matrix4f) + + fun vcEndDrawer(vc: VertexConsumer) + + companion object { + + @JvmStatic + val WHITE: ResourceLocation = HexAPI.modLoc("textures/entity/white.png") + + @JvmStatic + fun getHelper(worldlyBits: WorldlyBits?, ps: PoseStack, z: Float, texture: ResourceLocation) : VCDrawHelper{ + if(worldlyBits != null){ + return Worldly(worldlyBits, ps, z, texture) + } + return Basic(z, texture) + } + + @JvmStatic + fun getHelper(worldlyBits: WorldlyBits?, ps: PoseStack, z: Float) : VCDrawHelper{ + return getHelper(worldlyBits, ps, z, WHITE) + } + } + + class Basic(val z: Float, val texture: ResourceLocation = WHITE) : VCDrawHelper { + + override fun vcSetupAndSupply(vertMode: VertexFormat.Mode): VertexConsumer{ + val tess = Tesselator.getInstance() + val buf = tess.builder + buf.begin(vertMode, DefaultVertexFormat.POSITION_COLOR_TEX) + RenderSystem.setShader(GameRenderer::getPositionColorTexShader); + RenderSystem.disableCull() + RenderSystem.enableDepthTest() + RenderSystem.enableBlend() + RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, + GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA) + RenderSystem.setShaderTexture(0, texture) + return buf + } + override fun vertex(vc: VertexConsumer, color: Int, pos: Vec2, uv: Vec2, matrix: Matrix4f){ + vc.vertex(matrix, pos.x, pos.y, z).color(color).uv(uv.x, uv.y).endVertex() + } + override fun vcEndDrawer(vc: VertexConsumer){ + Tesselator.getInstance().end() + } + } + + class Worldly(val worldlyBits: WorldlyBits, val ps: PoseStack, val z: Float, val texture: ResourceLocation) : VCDrawHelper { + + var lastVertMode: VertexFormat.Mode ?= null // i guess this assumes that the vcHelper is only used once at a time? maybe reconsider that + + override fun vcSetupAndSupply(vertMode: VertexFormat.Mode): VertexConsumer{ + val provider = worldlyBits.provider + if (provider is MultiBufferSource.BufferSource) { + // tells it to draw whatever was here before so that we don't get depth buffer weirdness + provider.endBatch() + } + lastVertMode = vertMode + val buf = Tesselator.getInstance().builder + if(vertMode == VertexFormat.Mode.QUADS){ + val layer = RenderType.entityTranslucentCull(texture) + layer.setupRenderState() + if (provider == null) { + buf.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.NEW_ENTITY) + RenderSystem.setShader { GameRenderer.getRendertypeEntityTranslucentCullShader() } + return buf + } else { + return provider.getBuffer(layer) + } + } + buf.begin( vertMode, DefaultVertexFormat.NEW_ENTITY ) + // Generally this would be handled by a RenderLayer, but that doesn't seem to actually work here,, + RenderSystem.setShaderTexture(0, texture) + RenderSystem.enableDepthTest() + RenderSystem.disableCull() + Minecraft.getInstance().gameRenderer.lightTexture().turnOnLightLayer() + RenderSystem.enableBlend() + RenderSystem.blendFuncSeparate( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, + GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA ) + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + if (Minecraft.useShaderTransparency()) { + Minecraft.getInstance().levelRenderer.translucentTarget!!.bindWrite(false) + } + RenderSystem.setShader( GameRenderer::getRendertypeEntityTranslucentCullShader ) + return buf + } + + override fun vertex(vc: VertexConsumer, color: Int, pos: Vec2, uv: Vec2, matrix: Matrix4f){ + val nv = worldlyBits.normal?: Vec3(1.0, 1.0, 1.0) + vc.vertex(matrix, pos.x, pos.y, z) + .color(color) + .uv(uv.x, uv.y) + .overlayCoords(OverlayTexture.NO_OVERLAY) + .uv2(worldlyBits.light?: LightTexture.FULL_BRIGHT ) + .normal(ps.last().normal(), nv.x.toFloat(), nv.y.toFloat(), nv.z.toFloat()) + + vc.endVertex() + } + override fun vcEndDrawer(vc: VertexConsumer){ + if(lastVertMode == VertexFormat.Mode.QUADS){ + if (provider == null) { + val layer = RenderType.entityTranslucentCull(texture) + layer.end(Tesselator.getInstance().builder, VertexSorting.ORTHOGRAPHIC_Z) + } + } else { + Tesselator.getInstance().end() + Minecraft.getInstance().gameRenderer.lightTexture().turnOffLightLayer() + RenderSystem.disableBlend() + RenderSystem.defaultBlendFunc() + if (Minecraft.useShaderTransparency()) { + Minecraft.getInstance().mainRenderTarget.bindWrite(false) + } + RenderSystem.enableCull() + } + lastVertMode = null + } + } + + +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/WorldlyPatternRenderHelpers.java b/Common/src/main/java/at/petrak/hexcasting/client/render/WorldlyPatternRenderHelpers.java new file mode 100644 index 0000000000..68c00d5d92 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/WorldlyPatternRenderHelpers.java @@ -0,0 +1,150 @@ +package at.petrak.hexcasting.client.render; + +import at.petrak.hexcasting.api.casting.math.HexPattern; +import at.petrak.hexcasting.common.blocks.akashic.BlockAkashicBookshelf; +import at.petrak.hexcasting.common.blocks.akashic.BlockEntityAkashicBookshelf; +import at.petrak.hexcasting.common.blocks.circles.BlockEntitySlate; +import at.petrak.hexcasting.common.blocks.circles.BlockSlate; +import at.petrak.hexcasting.common.entities.EntityWallScroll; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.core.Vec3i; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.AttachFace; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix3f; + +import javax.annotation.Nullable; + +/** + * Helper methods for rendering patterns in the world. + */ +public class WorldlyPatternRenderHelpers { + + public static final PatternSettings SCROLL_SETTINGS = new PatternSettings("scroll", + PatternSettings.PositionSettings.paddedSquare(2.0/16), + PatternSettings.StrokeSettings.fromStroke(0.8/16), + PatternSettings.ZappySettings.STATIC + ); + + public static final PatternSettings READABLE_SCROLL_SETTINGS = new PatternSettings("scroll_readable", + PatternSettings.PositionSettings.paddedSquare(2.0/16), + PatternSettings.StrokeSettings.fromStroke(0.8/16), + PatternSettings.ZappySettings.READABLE + ); + + public static final PatternSettings WORLDLY_SETTINGS = new PatternSettings("worldly", + PatternSettings.PositionSettings.paddedSquare(2.0/16), + PatternSettings.StrokeSettings.fromStroke(0.8/16), + PatternSettings.ZappySettings.STATIC + ); + + public static final PatternSettings WORLDLY_SETTINGS_WOBBLY = new PatternSettings("wobbly_world", + PatternSettings.PositionSettings.paddedSquare(2.0/16), + PatternSettings.StrokeSettings.fromStroke(0.8/16), + PatternSettings.ZappySettings.WOBBLY + ); + + public static void renderPatternForScroll(HexPattern pattern, EntityWallScroll scroll, PoseStack ps, MultiBufferSource bufSource, int light, int blockSize, boolean showStrokeOrder) + { + ps.pushPose(); + ps.translate(-blockSize / 2f, -blockSize / 2f, 1f / 32f); + // there's almost certainly a better way to do this, but we're just flipping the y and z axes to fix normals + ps.last().normal().mul(new Matrix3f(1, 0, 0, 0, 0, 1, 0, 1, 0)); + renderPattern(pattern, showStrokeOrder ? READABLE_SCROLL_SETTINGS : SCROLL_SETTINGS, + showStrokeOrder ? PatternColors.READABLE_SCROLL_COLORS : PatternColors.DEFAULT_PATTERN_COLOR, + scroll.getPos().hashCode(), ps, bufSource, null, null, light, blockSize); + ps.popPose(); + } + + private static final int[] WALL_ROTATIONS = {180, 270, 0, 90}; + private static final Vec3i[] SLATE_FACINGS = {new Vec3i(0, -1, 0), new Vec3i(-1, -1, 0), new Vec3i(-1, -1, 1), new Vec3i(0, -1 , 1)}; + private static final Vec3[] WALL_NORMALS = {new Vec3(0, 0, -1), new Vec3(-1, 0, 0), new Vec3(0, 0, -1), new Vec3(-1, 0, 0)}; + private static final Vec3i[] SLATE_FLOORCEIL_FACINGS = {new Vec3i(0,0,0), new Vec3i(1,0,0), new Vec3i(1,0,1), new Vec3i(0,0,1)}; + + public static void renderPatternForSlate(BlockEntitySlate tile, HexPattern pattern, PoseStack ps, MultiBufferSource buffer, int light, BlockState bs) + { + + boolean isOnWall = bs.getValue(BlockSlate.ATTACH_FACE) == AttachFace.WALL; + boolean isOnCeiling = bs.getValue(BlockSlate.ATTACH_FACE) == AttachFace.CEILING; + int facing = bs.getValue(BlockSlate.FACING).get2DDataValue(); + + boolean wombly = bs.getValue(BlockSlate.ENERGIZED); + + ps.pushPose(); + + Vec3 normal = null; + if(isOnWall){ + ps.mulPose(Axis.ZP.rotationDegrees(180)); + Vec3i tV = SLATE_FACINGS[facing % 4]; + ps.translate(tV.getX(), tV.getY(), tV.getZ()); + ps.mulPose(Axis.YP.rotationDegrees(WALL_ROTATIONS[facing % 4])); + normal = WALL_NORMALS[facing % 4]; + } else { + Vec3i tV = SLATE_FLOORCEIL_FACINGS[facing % 4]; + ps.translate(tV.getX(), tV.getY(), tV.getZ()); + + ps.mulPose(Axis.YP.rotationDegrees(facing*-90)); + ps.mulPose(Axis.XP.rotationDegrees(90 * (isOnCeiling ? -1 : 1))); + if(isOnCeiling) ps.translate(0,-1,1); + } + + renderPattern(pattern, + wombly ? WORLDLY_SETTINGS_WOBBLY : WORLDLY_SETTINGS, + wombly ? PatternColors.SLATE_WOBBLY_PURPLE_COLOR : PatternColors.DEFAULT_PATTERN_COLOR, + tile.getBlockPos().hashCode(), ps, buffer, normal, null, light, 1); + ps.popPose(); + } + + private static final Vec3i[] BLOCK_FACINGS = {new Vec3i(0, -1, 1), new Vec3i(0, -1, 0), new Vec3i(-1, -1, 0), new Vec3i(-1, -1, 1)}; + + public static void renderPatternForAkashicBookshelf(BlockEntityAkashicBookshelf tile, HexPattern pattern, PoseStack ps, MultiBufferSource buffer, int light, BlockState bs) + { + + int facing = bs.getValue(BlockAkashicBookshelf.FACING).get2DDataValue(); + + ps.pushPose(); + ps.mulPose(Axis.ZP.rotationDegrees(180)); + + Vec3i tV = BLOCK_FACINGS[facing % 4]; + ps.translate(tV.getX(), tV.getY(), tV.getZ()); + ps.mulPose(Axis.YP.rotationDegrees(WALL_ROTATIONS[facing % 4])); + + int actualLight = LevelRenderer.getLightColor(tile.getLevel(), tile.getBlockPos().relative(bs.getValue(BlockAkashicBookshelf.FACING))); + + renderPattern(pattern, WORLDLY_SETTINGS , PatternColors.DEFAULT_PATTERN_COLOR, + tile.getBlockPos().hashCode(), ps, buffer, WALL_NORMALS[facing % 4].multiply(-1, -1, -1), -0.02f, actualLight, 1); + ps.popPose(); + } + + /** + * Renders a pattern in world space based on the given transform requirements + */ + public static void renderPattern(HexPattern pattern, PatternSettings patSets, PatternColors patColors, + double seed, PoseStack ps, MultiBufferSource bufSource, Vec3 normal, @Nullable Float zOffset, + int light, int blockSize) + { + + ps.pushPose(); + + + float z = zOffset != null ? zOffset : ((-1f / 16f) - 0.01f); + + normal = normal != null ? normal : new Vec3(0, 0, -1); + + ps.translate(0,0, z); + ps.scale(blockSize, blockSize, 1); + + + PoseStack noNormalInv = new PoseStack(); + noNormalInv.scale(1, 1, -1); + ps.mulPoseMatrix(noNormalInv.last().pose()); + + PatternRenderer.renderPattern(pattern, ps, new PatternRenderer.WorldlyBits(bufSource, light, normal), + patSets, patColors, seed, blockSize * 512); + + ps.popPose(); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntityAkashicBookshelfRenderer.java b/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntityAkashicBookshelfRenderer.java index e9064fb54c..5bc52dc211 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntityAkashicBookshelfRenderer.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntityAkashicBookshelfRenderer.java @@ -1,21 +1,12 @@ package at.petrak.hexcasting.client.render.be; import at.petrak.hexcasting.api.casting.math.HexPattern; -import at.petrak.hexcasting.client.render.PatternTextureManager; -import at.petrak.hexcasting.client.render.RenderLib; -import at.petrak.hexcasting.common.blocks.akashic.BlockAkashicBookshelf; +import at.petrak.hexcasting.client.render.WorldlyPatternRenderHelpers; import at.petrak.hexcasting.common.blocks.akashic.BlockEntityAkashicBookshelf; -import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.math.Axis; -import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; -import net.minecraft.util.Mth; -import net.minecraft.world.phys.Vec2; -import org.joml.AxisAngle4f; -import org.joml.Quaternionf; public class BlockEntityAkashicBookshelfRenderer implements BlockEntityRenderer { public BlockEntityAkashicBookshelfRenderer(BlockEntityRendererProvider.Context ctx) { @@ -32,66 +23,6 @@ public void render(BlockEntityAkashicBookshelf tile, float pPartialTick, PoseSta var bs = tile.getBlockState(); - if(PatternTextureManager.useTextures) { - PatternTextureManager.renderPatternForAkashicBookshelf(tile, pattern, ps, buffer, light, bs); - return; - } - - //TODO: remove old rendering if not needed anymore for comparison - - var oldShader = RenderSystem.getShader(); - RenderSystem.setShader(GameRenderer::getPositionColorShader); - RenderSystem.enableDepthTest(); - - ps.pushPose(); - - ps.translate(0.5, 0.5, 0.5); - var quarters = (-bs.getValue(BlockAkashicBookshelf.FACING).get2DDataValue()) % 4; - ps.mulPose(Axis.YP.rotation(Mth.HALF_PI * quarters)); - ps.mulPose(Axis.ZP.rotation(Mth.PI)); - - // and now Z is out? - ps.translate(0, 0, 0.5); - ps.scale(1 / 16f, 1 / 16f, 1 / 16f); - ps.translate(0, 0, 0.01); - - // yoink code from the pattern greeble - // Do two passes: one with a random size to find a good COM and one with the real calculation - var com1 = pattern.getCenter(1); - var lines1 = pattern.toLines(1, Vec2.ZERO); - - var maxDx = -1f; - var maxDy = -1f; - for (var dot : lines1) { - var dx = Mth.abs(dot.x - com1.x); - if (dx > maxDx) { - maxDx = dx; - } - var dy = Mth.abs(dot.y - com1.y); - if (dy > maxDy) { - maxDy = dy; - } - } - var scale = Math.min(3.8f, Math.min(16 / 2.5f / maxDx, 16 / 2.5f / maxDy)); - - var com2 = pattern.getCenter(scale); - var lines2 = pattern.toLines(scale, com2.negated()); - // For some reason it is mirrored left to right and i can't seem to posestack-fu it into shape - for (int j = 0; j < lines2.size(); j++) { - var v = lines2.get(j); - lines2.set(j, new Vec2(-v.x, v.y)); - } - - var stupidHash = tile.getBlockPos().hashCode(); - var zappy = RenderLib.makeZappy(lines2, RenderLib.findDupIndices(pattern.positions()), 10, 0.5f, 0f, 0f, 0f, - 1f, stupidHash); - - int outer = 0xff_d2c8c8; - int inner = 0xc8_322b33; - RenderLib.drawLineSeq(ps.last().pose(), zappy, 1f, 0f, outer, outer); - RenderLib.drawLineSeq(ps.last().pose(), zappy, 0.4f, 0.01f, inner, inner); - - ps.popPose(); - RenderSystem.setShader(() -> oldShader); + WorldlyPatternRenderHelpers.renderPatternForAkashicBookshelf(tile, pattern, ps, buffer, light, bs); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntitySlateRenderer.java b/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntitySlateRenderer.java index 37a40babcd..7fbf43ffc6 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntitySlateRenderer.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntitySlateRenderer.java @@ -1,21 +1,11 @@ package at.petrak.hexcasting.client.render.be; -import at.petrak.hexcasting.client.render.PatternTextureManager; -import at.petrak.hexcasting.client.render.RenderLib; +import at.petrak.hexcasting.client.render.WorldlyPatternRenderHelpers; import at.petrak.hexcasting.common.blocks.circles.BlockEntitySlate; -import at.petrak.hexcasting.common.blocks.circles.BlockSlate; -import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.math.Axis; -import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; -import net.minecraft.util.Mth; -import net.minecraft.world.level.block.state.properties.AttachFace; -import net.minecraft.world.phys.Vec2; - -import java.util.ArrayList; public class BlockEntitySlateRenderer implements BlockEntityRenderer { public BlockEntitySlateRenderer(BlockEntityRendererProvider.Context ctx) { @@ -30,94 +20,6 @@ public void render(BlockEntitySlate tile, float pPartialTick, PoseStack ps, var bs = tile.getBlockState(); - if(PatternTextureManager.useTextures && !bs.getValue(BlockSlate.ENERGIZED)) { - PatternTextureManager.renderPatternForSlate(tile, tile.pattern, ps, buffer, light, bs); - return; - } - - //TODO: remove old rendering if not needed anymore for comparison - - var oldShader = RenderSystem.getShader(); - RenderSystem.setShader(GameRenderer::getPositionColorShader); - RenderSystem.enableDepthTest(); - - ps.pushPose(); - - ps.translate(0.5, 0.5, 0.5); - var attchFace = bs.getValue(BlockSlate.ATTACH_FACE); - if (attchFace == AttachFace.WALL) { - var quarters = (-bs.getValue(BlockSlate.FACING).get2DDataValue()) % 4; - ps.mulPose(Axis.YP.rotation(Mth.HALF_PI * quarters)); - ps.mulPose(Axis.ZP.rotation(Mth.PI)); - } else { - var neg = attchFace == AttachFace.FLOOR ? -1 : 1; - ps.mulPose(Axis.XP.rotation(neg * Mth.HALF_PI)); - var quarters = (bs.getValue(BlockSlate.FACING).get2DDataValue() + 2) % 4; - ps.mulPose(Axis.ZP.rotation(neg * Mth.HALF_PI * quarters)); - } - - // Resolution is the number of sub-voxels in the block for rendering purposes, 16 is the default - // padding is the space to leave on the edges free of pattern - var resolution = 16; - var padding = resolution * PatternTextureManager.paddingByBlockSize / PatternTextureManager.resolutionByBlockSize; - - // and now Z is out? - ps.translate(0, 0, -0.5); - ps.scale(1f / resolution, 1f / resolution, 1f / resolution); - ps.translate(0, 0, 1.01); - - var isLit = bs.getValue(BlockSlate.ENERGIZED); - var variance = isLit ? 2.5f : 0.5f; - var speed = isLit ? 0.1f : 0f; - - var lines1 = tile.pattern.toLines(1, Vec2.ZERO); - var stupidHash = tile.getBlockPos().hashCode(); - var zappyPattern = RenderLib.makeZappy(lines1, RenderLib.findDupIndices(tile.pattern.positions()), - 10, variance, speed, 0.2f, 0f, 1f, stupidHash); - - // always do space calculations with the static version of the pattern - // so that it doesn't jump around resizing itself. - var zappyPatternSpace = RenderLib.makeZappy(lines1, RenderLib.findDupIndices(tile.pattern.positions()), - 10, 0.5f, 0f, 0.2f, 0f, 1f, stupidHash); - - double minX = Double.MAX_VALUE, maxX = Double.MIN_VALUE, minY = Double.MAX_VALUE, maxY = Double.MIN_VALUE; - for (Vec2 point : zappyPatternSpace) - { - minX = Math.min(minX, point.x); - maxX = Math.max(maxX, point.x); - minY = Math.min(minY, point.y); - maxY = Math.max(maxY, point.y); - } - - double rangeX = maxX - minX; - double rangeY = maxY - minY; - - double scale = Math.min((resolution - 2 * padding) / rangeX, (resolution - 2 * padding) / rangeY); - - double offsetX = ((- 2 * padding) - rangeX * scale) / 2; - double offsetY = ((- 2 * padding) - rangeY * scale) / 2; - - var zappyRenderSpace = new ArrayList(); - - for (Vec2 point : zappyPattern) { - zappyRenderSpace.add(new Vec2( - (float) (((point.x - minX) * scale + offsetX) + padding), - (float) (((point.y - minY) * scale + offsetY) + padding) - )); - } - - // For some reason it is mirrored left to right and i can't seem to posestack-fu it into shape - for (int i = 0; i < zappyRenderSpace.size(); i++) { - var v = zappyRenderSpace.get(i); - zappyRenderSpace.set(i, new Vec2(-v.x, v.y)); - } - - int outer = isLit ? 0xff_64c8ff : 0xff_d2c8c8; - int inner = isLit ? RenderLib.screenCol(outer) : 0xc8_322b33; - RenderLib.drawLineSeq(ps.last().pose(), zappyRenderSpace, 1f, 0f, outer, outer); - RenderLib.drawLineSeq(ps.last().pose(), zappyRenderSpace, 0.4f, 0.01f, inner, inner); - - ps.popPose(); - RenderSystem.setShader(() -> oldShader); + WorldlyPatternRenderHelpers.renderPatternForSlate(tile, tile.pattern, ps, buffer, light, bs); } } \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockEntitySlate.java b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockEntitySlate.java index 63489e8c0e..9a0ec965bc 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockEntitySlate.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockEntitySlate.java @@ -2,7 +2,6 @@ import at.petrak.hexcasting.api.block.HexBlockEntity; import at.petrak.hexcasting.api.casting.math.HexPattern; -import at.petrak.hexcasting.client.render.HexPatternPoints; import at.petrak.hexcasting.common.lib.HexBlockEntities; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; @@ -15,7 +14,6 @@ public class BlockEntitySlate extends HexBlockEntity { @Nullable public HexPattern pattern; - public HexPatternPoints points; public BlockEntitySlate(BlockPos pos, BlockState state) { super(HexBlockEntities.SLATE_TILE, pos, state); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockSlate.java b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockSlate.java index e265304a9f..a982c4c1e8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockSlate.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockSlate.java @@ -22,6 +22,8 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.SimpleWaterloggedBlock; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; @@ -230,4 +232,12 @@ protected static Direction getConnectedDirection(BlockState pState) { default -> pState.getValue(FACING); }; } + + public BlockState rotate(BlockState state, Rotation rot) { + return (BlockState) state.setValue(FACING, rot.rotate((Direction) state.getValue(FACING))); + } + + public BlockState mirror(BlockState state, Mirror mirror) { + return state.rotate(mirror.getRotation((Direction) state.getValue(FACING))); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/impetuses/BlockRedstoneImpetus.java b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/impetuses/BlockRedstoneImpetus.java index 58b3ac7c18..fe4fb73e1b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/impetuses/BlockRedstoneImpetus.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/impetuses/BlockRedstoneImpetus.java @@ -49,6 +49,8 @@ public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Pla if (usedStack.isEmpty() && pPlayer.isDiscrete()) { tile.clearPlayer(); tile.sync(); + pLevel.playSound(null, pPos, HexSounds.IMPETUS_REDSTONE_CLEAR, SoundSource.BLOCKS, 1f, 1f); + return InteractionResult.sidedSuccess(pLevel.isClientSide); } else { var datumContainer = IXplatAbstractions.INSTANCE.findDataHolder(usedStack); if (datumContainer != null) { @@ -60,8 +62,9 @@ public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Pla tile.setPlayer(player.getGameProfile(), entity.getUUID()); tile.sync(); - pLevel.playSound(pPlayer, pPos, HexSounds.IMPETUS_REDSTONE_DING, + pLevel.playSound(null, pPos, HexSounds.IMPETUS_REDSTONE_DING, SoundSource.BLOCKS, 1f, 1f); + return InteractionResult.sidedSuccess(pLevel.isClientSide); } } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpCanEntityHexFly.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpCanEntityHexFly.kt new file mode 100644 index 0000000000..8e79bd84da --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpCanEntityHexFly.kt @@ -0,0 +1,20 @@ +package at.petrak.hexcasting.common.casting.actions.queryentity + +import at.petrak.hexcasting.api.casting.asActionResult +import at.petrak.hexcasting.api.casting.castables.ConstMediaAction +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.getPlayer +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.xplat.IXplatAbstractions + +object OpCanEntityHexFly : ConstMediaAction { + override val argc = 1 + + override fun execute(args: List, env: CastingEnvironment): List { + val player = args.getPlayer(0, argc) + env.assertEntityInRange(player) + + val flightAbility = IXplatAbstractions.INSTANCE.getFlight(player) + return (flightAbility != null).asActionResult + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/OpEntityHeight.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpEntityHeight.kt similarity index 89% rename from Common/src/main/java/at/petrak/hexcasting/common/casting/actions/OpEntityHeight.kt rename to Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpEntityHeight.kt index eea382d0e4..5e84528387 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/OpEntityHeight.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpEntityHeight.kt @@ -1,4 +1,4 @@ -package at.petrak.hexcasting.common.casting.actions +package at.petrak.hexcasting.common.casting.actions.queryentity import at.petrak.hexcasting.api.casting.asActionResult import at.petrak.hexcasting.api.casting.castables.ConstMediaAction diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/OpEntityLook.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpEntityLook.kt similarity index 89% rename from Common/src/main/java/at/petrak/hexcasting/common/casting/actions/OpEntityLook.kt rename to Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpEntityLook.kt index 14faec64eb..23e47e9076 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/OpEntityLook.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpEntityLook.kt @@ -1,4 +1,4 @@ -package at.petrak.hexcasting.common.casting.actions +package at.petrak.hexcasting.common.casting.actions.queryentity import at.petrak.hexcasting.api.casting.asActionResult import at.petrak.hexcasting.api.casting.castables.ConstMediaAction diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/OpEntityPos.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpEntityPos.kt similarity index 90% rename from Common/src/main/java/at/petrak/hexcasting/common/casting/actions/OpEntityPos.kt rename to Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpEntityPos.kt index 80ab0e23f0..06a6e1cb36 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/OpEntityPos.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpEntityPos.kt @@ -1,4 +1,4 @@ -package at.petrak.hexcasting.common.casting.actions +package at.petrak.hexcasting.common.casting.actions.queryentity import at.petrak.hexcasting.api.casting.asActionResult import at.petrak.hexcasting.api.casting.castables.ConstMediaAction diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/OpEntityVelocity.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpEntityVelocity.kt similarity index 91% rename from Common/src/main/java/at/petrak/hexcasting/common/casting/actions/OpEntityVelocity.kt rename to Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpEntityVelocity.kt index b1b7330969..8ce1097d56 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/OpEntityVelocity.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/queryentity/OpEntityVelocity.kt @@ -1,4 +1,4 @@ -package at.petrak.hexcasting.common.casting.actions +package at.petrak.hexcasting.common.casting.actions.queryentity import at.petrak.hexcasting.api.HexAPI import at.petrak.hexcasting.api.casting.asActionResult diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/selectors/OpGetEntitiesBy.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/selectors/OpGetEntitiesBy.kt index c3366c9aca..1ae7a0a1c6 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/selectors/OpGetEntitiesBy.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/selectors/OpGetEntitiesBy.kt @@ -12,6 +12,7 @@ import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.animal.Animal import net.minecraft.world.entity.animal.WaterAnimal import net.minecraft.world.entity.boss.EnderDragonPart +import net.minecraft.world.entity.decoration.ArmorStand import net.minecraft.world.entity.item.ItemEntity import net.minecraft.world.entity.monster.Enemy import net.minecraft.world.entity.player.Player @@ -36,8 +37,9 @@ class OpGetEntitiesBy(val checker: Predicate, val negate: Boolean) : Con } companion object { + // Ignore truename ambit to fix #792 so you can't slurp up all players in the whole world fun isReasonablySelectable(ctx: CastingEnvironment, e: Entity) = - ctx.isEntityInRange(e) && e.isAlive && !e.isSpectator + ctx.isEntityInRange(e, true) && e.isAlive && !e.isSpectator @JvmStatic fun isAnimal(e: Entity): Boolean = e is Animal || e is WaterAnimal @@ -52,6 +54,6 @@ class OpGetEntitiesBy(val checker: Predicate, val negate: Boolean) : Con fun isPlayer(e: Entity): Boolean = e is Player @JvmStatic - fun isLiving(e: Entity): Boolean = (e is LivingEntity) || (e is EnderDragonPart) + fun isLiving(e: Entity): Boolean = (e !is ArmorStand) && (e is LivingEntity || e is EnderDragonPart) } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpIgnite.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpIgnite.kt index 4e6e7d3458..eeb7fae5cf 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpIgnite.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpIgnite.kt @@ -5,7 +5,11 @@ import at.petrak.hexcasting.api.casting.RenderedSpell import at.petrak.hexcasting.api.casting.castables.SpellAction import at.petrak.hexcasting.api.casting.eval.CastingEnvironment import at.petrak.hexcasting.api.casting.getBlockPos +import at.petrak.hexcasting.api.casting.getEntity +import at.petrak.hexcasting.api.casting.iota.EntityIota import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.iota.Vec3Iota +import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidIota import at.petrak.hexcasting.api.misc.MediaConstants import at.petrak.hexcasting.ktxt.UseOnContext import at.petrak.hexcasting.xplat.IXplatAbstractions @@ -13,6 +17,7 @@ import net.minecraft.core.BlockPos import net.minecraft.core.Direction import net.minecraft.server.level.ServerPlayer import net.minecraft.world.InteractionHand +import net.minecraft.world.entity.Entity import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items @@ -25,17 +30,30 @@ object OpIgnite : SpellAction { args: List, env: CastingEnvironment ): SpellAction.Result { - val target = args.getBlockPos(0, argc) - env.assertPosInRangeForEditing(target) - - return SpellAction.Result( - Spell(target), - MediaConstants.DUST_UNIT, - listOf(ParticleSpray.burst(Vec3.atCenterOf(BlockPos(target)), 1.0)) - ) + when (val target = args[0]) { + is EntityIota -> { + val entity = args.getEntity(0, argc) + env.assertEntityInRange(entity) + return SpellAction.Result( + EntitySpell(entity), + MediaConstants.DUST_UNIT, + listOf(ParticleSpray.burst(entity.position(), 1.0)) + ) + } + is Vec3Iota -> { + val block = args.getBlockPos(0, argc) + env.assertPosInRangeForEditing(block) + return SpellAction.Result( + BlockSpell(block), + MediaConstants.DUST_UNIT, + listOf(ParticleSpray.burst(Vec3.atCenterOf(BlockPos(block)), 1.0)) + ) + } + else -> throw MishapInvalidIota.ofType(target, 0, "entity_or_vector") + } } - private data class Spell(val pos: BlockPos) : RenderedSpell { + private data class BlockSpell(val pos: BlockPos) : RenderedSpell { override fun cast(env: CastingEnvironment) { // help if (!tryToClick(env, pos, Items.FIRE_CHARGE)) { @@ -56,4 +74,10 @@ object OpIgnite : SpellAction { ).consumesAction() } } -} + + private data class EntitySpell(val entity: Entity) : RenderedSpell { + override fun cast(env: CastingEnvironment) { + entity.setSecondsOnFire(8) + } + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpPlaceBlock.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpPlaceBlock.kt index e71a3df9bd..e4d761452b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpPlaceBlock.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpPlaceBlock.kt @@ -29,8 +29,8 @@ object OpPlaceBlock : SpellAction { get() = 1 override fun execute( - args: List, - env: CastingEnvironment + args: List, + env: CastingEnvironment ): SpellAction.Result { val pos = args.getBlockPos(0, argc) env.assertPosInRangeForEditing(pos) @@ -76,7 +76,8 @@ object OpPlaceBlock : SpellAction { // we temporarily give the player the stack, place it using mc code, then give them the old stack back. spoofedStack.count = 1 - val itemUseCtx = UseOnContext(env.world, caster as? ServerPlayer, env.castingHand, spoofedStack, blockHit) + val itemUseCtx = + UseOnContext(env.world, caster as? ServerPlayer, env.otherHand, spoofedStack, blockHit) val placeContext = BlockPlaceContext(itemUseCtx) if (bstate.canBeReplaced(placeContext)) { if (env.withdrawItem({ it == placeeStack }, 1, false)) { diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpCreateSentinel.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpCreateSentinel.kt index 1c0b2793cd..3b45740587 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpCreateSentinel.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpCreateSentinel.kt @@ -6,6 +6,7 @@ import at.petrak.hexcasting.api.casting.castables.SpellAction import at.petrak.hexcasting.api.casting.eval.CastingEnvironment import at.petrak.hexcasting.api.casting.getVec3 import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.mishaps.MishapBadCaster import at.petrak.hexcasting.api.misc.MediaConstants import at.petrak.hexcasting.api.player.Sentinel import at.petrak.hexcasting.xplat.IXplatAbstractions @@ -19,6 +20,9 @@ class OpCreateSentinel(val extendsRange: Boolean) : SpellAction { args: List, env: CastingEnvironment ): SpellAction.Result { + if (env.castingEntity !is ServerPlayer) + throw MishapBadCaster() + val target = args.getVec3(0, argc) env.assertVecInRange(target) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpDestroySentinel.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpDestroySentinel.kt index 97aaf692c4..43785a2adb 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpDestroySentinel.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpDestroySentinel.kt @@ -5,6 +5,7 @@ import at.petrak.hexcasting.api.casting.RenderedSpell import at.petrak.hexcasting.api.casting.castables.SpellAction import at.petrak.hexcasting.api.casting.eval.CastingEnvironment import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.mishaps.MishapBadCaster import at.petrak.hexcasting.api.casting.mishaps.MishapLocationInWrongDimension import at.petrak.hexcasting.api.misc.MediaConstants import at.petrak.hexcasting.xplat.IXplatAbstractions @@ -16,6 +17,9 @@ object OpDestroySentinel : SpellAction { args: List, env: CastingEnvironment ): SpellAction.Result { + if (env.castingEntity !is ServerPlayer) + throw MishapBadCaster() + val sentinel = IXplatAbstractions.INSTANCE.getSentinel(env.castingEntity as? ServerPlayer) // TODO why can't you remove things from other dimensions? diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpGetSentinelPos.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpGetSentinelPos.kt index b038aa92bf..fa56812ca5 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpGetSentinelPos.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpGetSentinelPos.kt @@ -5,6 +5,7 @@ import at.petrak.hexcasting.api.casting.castables.ConstMediaAction import at.petrak.hexcasting.api.casting.eval.CastingEnvironment import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.NullIota +import at.petrak.hexcasting.api.casting.mishaps.MishapBadCaster import at.petrak.hexcasting.api.casting.mishaps.MishapLocationInWrongDimension import at.petrak.hexcasting.api.misc.MediaConstants import at.petrak.hexcasting.xplat.IXplatAbstractions @@ -14,6 +15,9 @@ object OpGetSentinelPos : ConstMediaAction { override val argc = 0 override val mediaCost: Long = MediaConstants.DUST_UNIT / 10 override fun execute(args: List, env: CastingEnvironment): List { + if (env.castingEntity !is ServerPlayer) + throw MishapBadCaster() + val sentinel = IXplatAbstractions.INSTANCE.getSentinel(env.castingEntity as? ServerPlayer) ?: return listOf(NullIota()) if (sentinel.dimension != env.world.dimension()) throw MishapLocationInWrongDimension(sentinel.dimension.location()) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpGetSentinelWayfind.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpGetSentinelWayfind.kt index 87eb1547cb..cad52447ed 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpGetSentinelWayfind.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/sentinel/OpGetSentinelWayfind.kt @@ -6,6 +6,7 @@ import at.petrak.hexcasting.api.casting.eval.CastingEnvironment import at.petrak.hexcasting.api.casting.getVec3 import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.NullIota +import at.petrak.hexcasting.api.casting.mishaps.MishapBadCaster import at.petrak.hexcasting.api.casting.mishaps.MishapLocationInWrongDimension import at.petrak.hexcasting.api.misc.MediaConstants import at.petrak.hexcasting.xplat.IXplatAbstractions @@ -17,6 +18,9 @@ object OpGetSentinelWayfind : ConstMediaAction { override val argc = 1 override val mediaCost: Long = MediaConstants.DUST_UNIT / 10 override fun execute(args: List, env: CastingEnvironment): List { + if (env.castingEntity !is ServerPlayer) + throw MishapBadCaster() + val from = args.getVec3(0, argc) val sentinel = IXplatAbstractions.INSTANCE.getSentinel(env.castingEntity as? ServerPlayer) ?: return listOf(NullIota()) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/stack/OpAlwinfyHasAscendedToABeingOfPureMath.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/stack/OpAlwinfyHasAscendedToABeingOfPureMath.kt index 115ff4b6b5..da6757147d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/stack/OpAlwinfyHasAscendedToABeingOfPureMath.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/stack/OpAlwinfyHasAscendedToABeingOfPureMath.kt @@ -5,10 +5,10 @@ import at.petrak.hexcasting.api.casting.eval.CastingEnvironment import at.petrak.hexcasting.api.casting.eval.OperationResult import at.petrak.hexcasting.api.casting.eval.vm.CastingImage import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation -import at.petrak.hexcasting.api.casting.getPositiveInt +import at.petrak.hexcasting.api.casting.getPositiveLong import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs import at.petrak.hexcasting.common.lib.hex.HexEvalSounds -import it.unimi.dsi.fastutil.ints.IntArrayList +import it.unimi.dsi.fastutil.longs.LongArrayList // "lehmer code" object OpAlwinfyHasAscendedToABeingOfPureMath : Action { @@ -18,10 +18,10 @@ object OpAlwinfyHasAscendedToABeingOfPureMath : Action { if (stack.isEmpty()) throw MishapNotEnoughArgs(1, 0) - val code = stack.getPositiveInt(stack.lastIndex) + val code = stack.getPositiveLong(stack.lastIndex) stack.removeLast() - val strides = IntArrayList() + val strides = LongArrayList() for (f in FactorialIter()) { if (f <= code) strides.add(f) @@ -37,7 +37,7 @@ object OpAlwinfyHasAscendedToABeingOfPureMath : Action { for (divisor in strides.asReversed()) { val index = radix / divisor radix %= divisor - editTarget[0] = swap.removeAt(index) + editTarget[0] = swap.removeAt(index.toInt()) // i hope this isn't O(n) editTarget = editTarget.subList(1, editTarget.size) } @@ -46,12 +46,12 @@ object OpAlwinfyHasAscendedToABeingOfPureMath : Action { return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) } - private class FactorialIter : Iterator { - var acc = 1 - var n = 1 + private class FactorialIter : Iterator { + var acc = 1L + var n = 1L override fun hasNext(): Boolean = true - override fun next(): Int { + override fun next(): Long { val out = this.acc this.acc *= this.n this.n++ diff --git a/Common/src/main/java/at/petrak/hexcasting/common/command/PatternTexturesCommand.java b/Common/src/main/java/at/petrak/hexcasting/common/command/PatternTexturesCommand.java index e653759aef..868f02e2de 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/command/PatternTexturesCommand.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/command/PatternTexturesCommand.java @@ -1,32 +1,29 @@ package at.petrak.hexcasting.common.command; import at.petrak.hexcasting.client.render.PatternTextureManager; -import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; public class PatternTexturesCommand { public static void add(LiteralArgumentBuilder cmd) { + // TODO: do we want these in release ?? cmd.then(Commands.literal("textureToggle") .requires(dp -> dp.hasPermission(Commands.LEVEL_ADMINS)) .executes(ctx -> { PatternTextureManager.useTextures = !PatternTextureManager.useTextures; + String log = (PatternTextureManager.useTextures ? "Enabled" : "Disabled") + " pattern texture rendering. This is meant for debugging."; + ctx.getSource().sendSuccess(() -> Component.literal(log), true); return 1; })); cmd.then(Commands.literal("textureRepaint") .requires(dp -> dp.hasPermission(Commands.LEVEL_ADMINS)) .executes(ctx -> { PatternTextureManager.repaint(); + ctx.getSource().sendSuccess(() -> Component.literal("Repainting pattern textures. This is meant for debugging."), true); return 1; })); - cmd.then(Commands.literal("textureSetResolutionScaler") - .requires(dp -> dp.hasPermission(Commands.LEVEL_ADMINS)) - .then(Commands.argument("integer", IntegerArgumentType.integer()).executes(ctx -> { - PatternTextureManager.setResolutionScaler(IntegerArgumentType.getInteger(ctx, "integer")); - PatternTextureManager.repaint(); - return 1; - }))); } } \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/entities/EntityWallScroll.java b/Common/src/main/java/at/petrak/hexcasting/common/entities/EntityWallScroll.java index cd9ab651cb..03d916c8f7 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/entities/EntityWallScroll.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/entities/EntityWallScroll.java @@ -3,8 +3,6 @@ import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.api.utils.NBTHelper; -import at.petrak.hexcasting.client.render.HexPatternPoints; -import at.petrak.hexcasting.client.render.RenderLib; import at.petrak.hexcasting.common.items.storage.ItemScroll; import at.petrak.hexcasting.common.lib.HexItems; import at.petrak.hexcasting.common.lib.HexSounds; @@ -32,12 +30,9 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; -import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; -import java.util.List; - public class EntityWallScroll extends HangingEntity { private static final EntityDataAccessor SHOWS_STROKE_ORDER = SynchedEntityData.defineId( EntityWallScroll.class, @@ -48,9 +43,6 @@ public class EntityWallScroll extends HangingEntity { public HexPattern pattern; public boolean isAncient; public int blockSize; - // Client-side only! - @Nullable - public HexPatternPoints points; public EntityWallScroll(EntityType type, Level world) { super(type, world); @@ -72,21 +64,9 @@ public void recalculateDisplay() { CompoundTag patternTag = NBTHelper.getCompound(scroll, ItemScroll.TAG_PATTERN); if (patternTag != null) { this.pattern = HexPattern.fromNBT(patternTag); - if (this.level().isClientSide) { - var pair = RenderLib.getCenteredPattern(pattern, 128f / 3 * blockSize, 128f / 3 * blockSize, - 16f / 3 * blockSize); - var dots = pair.getSecond(); - var readOffset = this.getShowsStrokeOrder() ? RenderLib.DEFAULT_READABILITY_OFFSET : 0f; - var lastProp = this.getShowsStrokeOrder() ? RenderLib.DEFAULT_LAST_SEGMENT_LEN_PROP : 1f; - var zappyPoints = RenderLib.makeZappy(dots, RenderLib.findDupIndices(pattern.positions()), 10, 0.4f, - 0f, 0f, readOffset, lastProp, this.getId()); - this.points = new HexPatternPoints(zappyPoints); - } - this.isAncient = NBTHelper.hasString(scroll, ItemScroll.TAG_OP_ID); } else { this.pattern = null; - this.points = null; this.isAncient = false; } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemScroll.java b/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemScroll.java index 96241ea367..036e5129b4 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemScroll.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemScroll.java @@ -9,6 +9,7 @@ import at.petrak.hexcasting.common.entities.EntityWallScroll; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import at.petrak.hexcasting.common.misc.PatternTooltip; +import at.petrak.hexcasting.interop.inline.InlinePatternData; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -132,7 +133,13 @@ public Component getName(ItemStack pStack) { return Component.translatable(descID + ".of", Component.translatable("hexcasting.action." + ResourceLocation.tryParse(ancientId))); } else if (NBTHelper.hasCompound(pStack, TAG_PATTERN)) { - return Component.translatable(descID); + var compound = NBTHelper.getCompound(pStack, ItemScroll.TAG_PATTERN); + var patternLabel = Component.literal(""); + if (compound != null) { + var pattern = HexPattern.fromNBT(compound); + patternLabel = Component.literal(": ").append(new InlinePatternData(pattern).asText(false)); + } + return Component.translatable(descID).append(patternLabel); } else { return Component.translatable(descID + ".empty"); } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemSlate.java b/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemSlate.java index 6ad6f5b51b..98227d1eab 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemSlate.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemSlate.java @@ -11,6 +11,7 @@ import at.petrak.hexcasting.common.blocks.circles.BlockEntitySlate; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import at.petrak.hexcasting.common.misc.PatternTooltip; +import at.petrak.hexcasting.interop.inline.InlinePatternData; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; @@ -38,16 +39,27 @@ public ItemSlate(Block pBlock, Properties pProperties) { @Override public Component getName(ItemStack pStack) { var key = "block." + HexAPI.MOD_ID + ".slate." + (hasPattern(pStack) ? "written" : "blank"); - return Component.translatable(key); + Component patternText = getPattern(pStack) + .map(pat -> Component.literal(": ").append(new InlinePatternData(pat).asText(false))) + .orElse(Component.literal("")); + return Component.translatable(key).append(patternText); } - public static boolean hasPattern(ItemStack stack) { + public static Optional getPattern(ItemStack stack){ var bet = NBTHelper.getCompound(stack, "BlockEntityTag"); - if (bet != null) { - return bet.contains(BlockEntitySlate.TAG_PATTERN, Tag.TAG_COMPOUND) && - !bet.getCompound(BlockEntitySlate.TAG_PATTERN).isEmpty(); + + if (bet != null && bet.contains(BlockEntitySlate.TAG_PATTERN, Tag.TAG_COMPOUND)) { + var patTag = bet.getCompound(BlockEntitySlate.TAG_PATTERN); + if (!patTag.isEmpty()) { + var pattern = HexPattern.fromNBT(patTag); + return Optional.of(pattern); + } } - return false; + return Optional.empty(); + } + + public static boolean hasPattern(ItemStack stack) { + return getPattern(stack).isPresent(); } @SoftImplement("IForgeItem") @@ -112,15 +124,6 @@ public void writeDatum(ItemStack stack, Iota datum) { @Override public Optional getTooltipImage(ItemStack stack) { - var bet = NBTHelper.getCompound(stack, "BlockEntityTag"); - - if (bet != null && bet.contains(BlockEntitySlate.TAG_PATTERN, Tag.TAG_COMPOUND)) { - var patTag = bet.getCompound(BlockEntitySlate.TAG_PATTERN); - if (!patTag.isEmpty()) { - var pattern = HexPattern.fromNBT(patTag); - return Optional.of(new PatternTooltip(pattern, PatternTooltipComponent.SLATE_BG)); - } - } - return Optional.empty(); + return getPattern(stack).map(pat -> new PatternTooltip(pat, PatternTooltipComponent.SLATE_BG)); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexMobEffects.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexMobEffects.java index fe1e0499c4..6398bfea27 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexMobEffects.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexMobEffects.java @@ -26,7 +26,7 @@ public static void register(BiConsumer r) { .addAttributeModifier(HexAttributes.GRID_ZOOM, "d4afaf0f-df37-4253-9fa7-029e8e4415d9", 0.25, AttributeModifier.Operation.MULTIPLY_TOTAL); public static final MobEffect SHRINK_GRID = make("shrink_grid", - new HexMobEffect(MobEffectCategory.HARMFUL, 0xebad1c)) + new HexMobEffect(MobEffectCategory.HARMFUL, 0xc0e660)) .addAttributeModifier(HexAttributes.GRID_ZOOM, "1ce492a9-8bf5-4091-a482-c6d9399e448a", -0.2, AttributeModifier.Operation.MULTIPLY_TOTAL); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexPotions.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexPotions.java index f970235b5d..e86409e77c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexPotions.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexPotions.java @@ -3,18 +3,22 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.alchemy.Potions; +import net.minecraft.world.item.Items; import java.util.LinkedHashMap; import java.util.Map; import java.util.function.BiConsumer; import static at.petrak.hexcasting.api.HexAPI.modLoc; +import at.petrak.hexcasting.mixin.accessor.AccessorPotionBrewing; public class HexPotions { public static void register(BiConsumer r) { for (var e : POTIONS.entrySet()) { r.accept(e.getValue(), e.getKey()); } + HexPotions.addRecipes(); } private static final Map POTIONS = new LinkedHashMap<>(); @@ -26,12 +30,24 @@ public static void register(BiConsumer r) { public static final Potion ENLARGE_GRID_STRONG = make("enlarge_grid_strong", new Potion("enlarge_grid_strong", new MobEffectInstance(HexMobEffects.ENLARGE_GRID, 1800, 1))); + public static final Potion SHRINK_GRID = make("shrink_grid", + new Potion("shrink_grid", new MobEffectInstance(HexMobEffects.SHRINK_GRID, 3600))); + public static final Potion SHRINK_GRID_LONG = make("shrink_grid_long", + new Potion("shrink_grid_long", new MobEffectInstance(HexMobEffects.SHRINK_GRID, 9600))); + public static final Potion SHRINK_GRID_STRONG = make("shrink_grid_strong", + new Potion("shrink_grid_strong", new MobEffectInstance(HexMobEffects.SHRINK_GRID, 1800, 1))); + public static void addRecipes() { - /* AccessorPotionBrewing.addMix(Potions.AWKWARD, HexItems.AMETHYST_DUST, ENLARGE_GRID); AccessorPotionBrewing.addMix(ENLARGE_GRID, Items.REDSTONE, ENLARGE_GRID_LONG); AccessorPotionBrewing.addMix(ENLARGE_GRID, Items.GLOWSTONE_DUST, ENLARGE_GRID_STRONG); - */ + + AccessorPotionBrewing.addMix(ENLARGE_GRID, Items.FERMENTED_SPIDER_EYE, SHRINK_GRID); + AccessorPotionBrewing.addMix(ENLARGE_GRID_LONG, Items.FERMENTED_SPIDER_EYE, SHRINK_GRID_LONG); + AccessorPotionBrewing.addMix(ENLARGE_GRID_STRONG, Items.FERMENTED_SPIDER_EYE, SHRINK_GRID_STRONG); + + AccessorPotionBrewing.addMix(SHRINK_GRID, Items.REDSTONE, SHRINK_GRID_LONG); + AccessorPotionBrewing.addMix(SHRINK_GRID, Items.GLOWSTONE_DUST, SHRINK_GRID_STRONG); } private static T make(String id, T potion) { diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexSounds.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexSounds.java index b70f239a12..268eaedd19 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexSounds.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexSounds.java @@ -42,6 +42,7 @@ public static void registerSounds(BiConsumer r) { public static final SoundEvent IMPETUS_LOOK_TICK = sound("impetus.fletcher.tick"); public static final SoundEvent IMPETUS_REDSTONE_DING = sound("impetus.redstone.register"); + public static final SoundEvent IMPETUS_REDSTONE_CLEAR = sound("impetus.redstone.clear"); public static final SoundEvent READ_LORE_FRAGMENT = sound("lore_fragment.read"); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java index 27398b8787..dc57ed4997 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java @@ -11,24 +11,43 @@ import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.api.misc.MediaConstants; import at.petrak.hexcasting.api.utils.HexUtils; -import at.petrak.hexcasting.common.casting.actions.*; -import at.petrak.hexcasting.common.casting.actions.akashic.*; -import at.petrak.hexcasting.common.casting.actions.circles.*; +import at.petrak.hexcasting.common.casting.actions.akashic.OpAkashicRead; +import at.petrak.hexcasting.common.casting.actions.akashic.OpAkashicWrite; +import at.petrak.hexcasting.common.casting.actions.circles.OpCircleBounds; +import at.petrak.hexcasting.common.casting.actions.circles.OpImpetusDir; +import at.petrak.hexcasting.common.casting.actions.circles.OpImpetusPos; import at.petrak.hexcasting.common.casting.actions.eval.*; -import at.petrak.hexcasting.common.casting.actions.lists.*; -import at.petrak.hexcasting.common.casting.actions.local.*; -import at.petrak.hexcasting.common.casting.actions.math.*; -import at.petrak.hexcasting.common.casting.actions.math.logic.*; -import at.petrak.hexcasting.common.casting.actions.raycast.*; +import at.petrak.hexcasting.common.casting.actions.lists.OpEmptyList; +import at.petrak.hexcasting.common.casting.actions.lists.OpLastNToList; +import at.petrak.hexcasting.common.casting.actions.lists.OpSingleton; +import at.petrak.hexcasting.common.casting.actions.lists.OpSplat; +import at.petrak.hexcasting.common.casting.actions.local.OpPeekLocal; +import at.petrak.hexcasting.common.casting.actions.local.OpPushLocal; +import at.petrak.hexcasting.common.casting.actions.math.OpCoerceToAxial; +import at.petrak.hexcasting.common.casting.actions.math.OpRandom; +import at.petrak.hexcasting.common.casting.actions.math.logic.OpBoolIf; +import at.petrak.hexcasting.common.casting.actions.math.logic.OpBoolNot; +import at.petrak.hexcasting.common.casting.actions.math.logic.OpCoerceToBool; +import at.petrak.hexcasting.common.casting.actions.math.logic.OpEquality; +import at.petrak.hexcasting.common.casting.actions.queryentity.*; +import at.petrak.hexcasting.common.casting.actions.raycast.OpBlockAxisRaycast; +import at.petrak.hexcasting.common.casting.actions.raycast.OpBlockRaycast; +import at.petrak.hexcasting.common.casting.actions.raycast.OpEntityRaycast; import at.petrak.hexcasting.common.casting.actions.rw.*; -import at.petrak.hexcasting.common.casting.actions.selectors.*; +import at.petrak.hexcasting.common.casting.actions.selectors.OpGetCaster; +import at.petrak.hexcasting.common.casting.actions.selectors.OpGetEntitiesBy; +import at.petrak.hexcasting.common.casting.actions.selectors.OpGetEntityAt; import at.petrak.hexcasting.common.casting.actions.spells.*; import at.petrak.hexcasting.common.casting.actions.spells.great.*; -import at.petrak.hexcasting.common.casting.actions.spells.sentinel.*; +import at.petrak.hexcasting.common.casting.actions.spells.sentinel.OpCreateSentinel; +import at.petrak.hexcasting.common.casting.actions.spells.sentinel.OpDestroySentinel; +import at.petrak.hexcasting.common.casting.actions.spells.sentinel.OpGetSentinelPos; +import at.petrak.hexcasting.common.casting.actions.spells.sentinel.OpGetSentinelWayfind; import at.petrak.hexcasting.common.casting.actions.stack.*; -import at.petrak.hexcasting.common.casting.actions.eval.OpEvalBreakable; import at.petrak.hexcasting.common.lib.HexItems; -import at.petrak.hexcasting.interop.pehkui.*; +import at.petrak.hexcasting.interop.pehkui.OpGetScale; +import at.petrak.hexcasting.interop.pehkui.OpSetScale; +import at.petrak.hexcasting.interop.pehkui.PehkuiInterop; import at.petrak.hexcasting.xplat.IXplatAbstractions; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceLocation; @@ -224,7 +243,7 @@ public class HexActions { public static final ActionRegistryEntry COLORIZE = make("colorize", new ActionRegistryEntry(HexPattern.fromAngles("awddwqawqwawq", HexDir.EAST), OpColorize.INSTANCE)); public static final ActionRegistryEntry CYCLE_VARIANT = make("cycle_variant", - new ActionRegistryEntry(HexPattern.fromAngles("dwaawedwewdwe", HexDir.WEST), OpCycleVariant.INSTANCE)); + new ActionRegistryEntry(HexPattern.fromAngles("dwaawedwewdwe", HexDir.WEST), OpCycleVariant.INSTANCE)); public static final ActionRegistryEntry CREATE_WATER = make("create_water", new ActionRegistryEntry(HexPattern.fromAngles("aqawqadaq", HexDir.SOUTH_EAST), new OpCreateFluid( MediaConstants.DUST_UNIT, @@ -318,6 +337,9 @@ public class HexActions { public static final ActionRegistryEntry FLIGHT$TIME = make("flight/time", new ActionRegistryEntry(HexPattern.fromAngles("dwdwdewq", HexDir.NORTH_EAST), new OpFlight(OpFlight.Type.LimitTime))); + public static final ActionRegistryEntry FLIGHT$CAN_FLY = make("flight/can_fly", + new ActionRegistryEntry(HexPattern.fromAngles("dwdwdeweaqa", HexDir.NORTH_EAST), + OpCanEntityHexFly.INSTANCE)); public static final ActionRegistryEntry SENTINEL$CREATE = make("sentinel/create", new ActionRegistryEntry(HexPattern.fromAngles("waeawae", HexDir.EAST), new OpCreateSentinel(false))); @@ -510,14 +532,14 @@ public class HexActions { public static final ActionRegistryEntry APPEND = make("append", new OperationAction(HexPattern.fromAngles("edqde", HexDir.SOUTH_WEST))); public static final ActionRegistryEntry UNAPPEND = make("unappend", - new OperationAction(HexPattern.fromAngles("qaeaq", HexDir.NORTH_WEST))); -// public static final ActionRegistryEntry CONCAT = make("concat", + new OperationAction(HexPattern.fromAngles("qaeaq", HexDir.NORTH_WEST))); + // public static final ActionRegistryEntry CONCAT = make("concat", // new ActionRegistryEntry(HexPattern.fromAngles("qaeaq", HexDir.NORTH_WEST), OpConcat.INSTANCE)); public static final ActionRegistryEntry INDEX = make("index", new OperationAction(HexPattern.fromAngles("deeed", HexDir.NORTH_WEST))); public static final ActionRegistryEntry FOR_EACH = make("for_each", new ActionRegistryEntry(HexPattern.fromAngles("dadad", HexDir.NORTH_EAST), OpForEach.INSTANCE)); -// public static final ActionRegistryEntry LIST_SIZE = make("list_size", + // public static final ActionRegistryEntry LIST_SIZE = make("list_size", // new ActionRegistryEntry(HexPattern.fromAngles("aqaeaq", HexDir.EAST), OpListSize.INSTANCE)); public static final ActionRegistryEntry SINGLETON = make("singleton", new ActionRegistryEntry(HexPattern.fromAngles("adeeed", HexDir.EAST), OpSingleton.INSTANCE)); diff --git a/Common/src/main/java/at/petrak/hexcasting/datagen/HexAdvancements.java b/Common/src/main/java/at/petrak/hexcasting/datagen/HexAdvancements.java index 28012e0da5..d095b06044 100644 --- a/Common/src/main/java/at/petrak/hexcasting/datagen/HexAdvancements.java +++ b/Common/src/main/java/at/petrak/hexcasting/datagen/HexAdvancements.java @@ -53,6 +53,18 @@ public void generate(HolderLookup.Provider provider, Consumer consu ItemPredicate.Builder.item().of(HexTags.Items.GRANTS_ROOT_ADVANCEMENT).build())) .save(consumer, prefix("root")); // how the hell does one even read this + //Creative Debug Unlocker + Advancement.Builder.advancement() + .display(new DisplayInfo(new ItemStack(HexItems.CREATIVE_UNLOCKER), + Component.translatable("advancement.hexcasting:creative_unlocker"), + Component.translatable("advancement.hexcasting:creative_unlocker.desc"), + new ResourceLocation("minecraft", "textures/block/calcite.png"), + FrameType.TASK, true, false, true)) + .parent(root) + .addCriterion("has_creative_unlocker", InventoryChangeTrigger.TriggerInstance.hasItems( + ItemPredicate.Builder.item().of(HexItems.CREATIVE_UNLOCKER).build())) + .save(consumer, prefix("creative_unlocker")); + // weird names so we have alphabetical parity Advancement.Builder.advancement() .display(simpleDisplay(Items.GLISTERING_MELON_SLICE, "wasteful_cast", FrameType.TASK)) diff --git a/Common/src/main/java/at/petrak/hexcasting/datagen/recipe/HexplatRecipes.java b/Common/src/main/java/at/petrak/hexcasting/datagen/recipe/HexplatRecipes.java index cd568fad1d..828c8ad92f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/datagen/recipe/HexplatRecipes.java +++ b/Common/src/main/java/at/petrak/hexcasting/datagen/recipe/HexplatRecipes.java @@ -457,6 +457,12 @@ public void buildRecipes(Consumer recipes) { .unlockedBy("enlightenment", enlightenment) .save(recipes, modLoc("brainsweep/directrix_redstone")); + new BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_DIRECTRIX), + new VillagerIngredient(VillagerProfession.SHEPHERD, null, 1), + HexBlocks.DIRECTRIX_BOOLEAN.defaultBlockState(), MediaConstants.CRYSTAL_UNIT * 10) + .unlockedBy("enlightenment", enlightenment) + .save(recipes, modLoc("brainsweep/directrix_boolean")); + new BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.AKASHIC_LIGATURE), new VillagerIngredient(VillagerProfession.LIBRARIAN, null, 5), HexBlocks.AKASHIC_RECORD.defaultBlockState(), MediaConstants.CRYSTAL_UNIT * 10) diff --git a/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexBlockTagProvider.java b/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexBlockTagProvider.java index dffbb06364..c354a0464e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexBlockTagProvider.java +++ b/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexBlockTagProvider.java @@ -6,8 +6,11 @@ import at.petrak.paucal.api.datagen.PaucalBlockTagProvider; import net.minecraft.core.HolderLookup; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.data.PackOutput; +import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagKey; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; @@ -129,6 +132,10 @@ protected void addTags(HolderLookup.Provider provider) { Blocks.KELP, Blocks.KELP_PLANT, Blocks.SEAGRASS, Blocks.TALL_SEAGRASS); add(tag(HexTags.Blocks.CHEAP_TO_BREAK_BLOCK), HexBlocks.CONJURED_BLOCK, HexBlocks.CONJURED_LIGHT); + + // this is a hack but fixes #532 + var createBrittle = TagKey.create(Registries.BLOCK, new ResourceLocation("create", "brittle")); + tag(createBrittle).addOptionalTag(BuiltInRegistries.BLOCK.getKey(HexBlocks.SLATE)); } void add(TagAppender appender, Block... blocks) { diff --git a/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexItemTagProvider.java b/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexItemTagProvider.java index e6ecbf58db..520df8a48c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexItemTagProvider.java +++ b/Common/src/main/java/at/petrak/hexcasting/datagen/tag/HexItemTagProvider.java @@ -47,7 +47,7 @@ protected void addTags(HolderLookup.Provider provider) { Items.GLASS_BOTTLE); add(tag(HexTags.Items.GRANTS_ROOT_ADVANCEMENT), HexItems.AMETHYST_DUST, Items.AMETHYST_SHARD, - HexItems.CHARGED_AMETHYST); + HexItems.CHARGED_AMETHYST, HexItems.CREATIVE_UNLOCKER); add(tag(HexTags.Items.SEAL_MATERIALS), Items.HONEYCOMB); diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/HexInterop.java b/Common/src/main/java/at/petrak/hexcasting/interop/HexInterop.java index 79220f4ab9..6ecbc2d40f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/interop/HexInterop.java +++ b/Common/src/main/java/at/petrak/hexcasting/interop/HexInterop.java @@ -1,5 +1,7 @@ package at.petrak.hexcasting.interop; +import at.petrak.hexcasting.interop.inline.InlineHex; +import at.petrak.hexcasting.interop.inline.InlineHexClient; import at.petrak.hexcasting.interop.pehkui.PehkuiInterop; import at.petrak.hexcasting.xplat.IClientXplatAbstractions; import at.petrak.hexcasting.xplat.IXplatAbstractions; @@ -30,10 +32,13 @@ public static void init() { } xplat.initPlatformSpecific(); + + InlineHex.init(); } public static void clientInit() { IClientXplatAbstractions.INSTANCE.initPlatformSpecific(); + InlineHexClient.init(); } private static void initPatchouli() { diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/inline/HexPatternMatcher.java b/Common/src/main/java/at/petrak/hexcasting/interop/inline/HexPatternMatcher.java new file mode 100644 index 0000000000..55af9756e8 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/interop/inline/HexPatternMatcher.java @@ -0,0 +1,105 @@ +package at.petrak.hexcasting.interop.inline; + +import at.petrak.hexcasting.api.HexAPI; +import at.petrak.hexcasting.api.casting.math.HexDir; +import at.petrak.hexcasting.api.casting.math.HexPattern; +import com.samsthenerd.inline.api.InlineAPI; +import com.samsthenerd.inline.api.matching.InlineMatch; +import com.samsthenerd.inline.api.matching.MatchContext; +import com.samsthenerd.inline.api.matching.MatcherInfo; +import com.samsthenerd.inline.api.matching.RegexMatcher; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Tuple; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.MatchResult; +import java.util.regex.Pattern; + +public class HexPatternMatcher implements RegexMatcher { + + private static final ResourceLocation patternMatcherID = HexAPI.modLoc("pattern"); + private static final MatcherInfo patternMatcherInfo = MatcherInfo.fromId(patternMatcherID); + + // thx kyra <3 + private static final Pattern PATTERN_PATTERN_REGEX = Pattern.compile("(?\\\\?)(?:HexPattern)?[<(\\[{]\\s*(?[a-zA-Z_-]+)(?:\\s*(?[,!+:; ])\\s*(?[aqwedsAQWEDS]+)?)\\s*[>)\\]}]", Pattern.CASE_INSENSITIVE); + + public static HexPatternMatcher INSTANCE = new HexPatternMatcher(); + + public Pattern getRegex(){ + return PATTERN_PATTERN_REGEX; + } + + @Override + @NotNull + public Tuple getMatchAndGroup(MatchResult regexMatch, MatchContext ctx) { + String escaped = regexMatch.group(1); + String dirString = regexMatch.group(2).toLowerCase().strip().replace("_", ""); + String sizeModString = regexMatch.group(3); + String angleSigs = regexMatch.group(4); + if(escaped == null){ + return new Tuple<>(new InlineMatch.TextMatch(Component.literal("")), 1); + } + // need to convert dirString to a HexDir + HexDir dir = dirMap.get(dirString); + if(dir == null) + return new Tuple<>(null, 0); + HexPattern pat; + if(angleSigs == null){ + angleSigs = ""; + } + try{ + pat = HexPattern.fromAngles(angleSigs.toLowerCase(), dir); + InlinePatternData patData = new InlinePatternData(pat); + Style patDataStyle = patData.getExtraStyle(); + if(sizeModString != null && sizeModString.equals("+")) + patDataStyle = InlineAPI.INSTANCE.withSizeModifier(patDataStyle, 2); + if(sizeModString != null && sizeModString.equals("!")) + patDataStyle = InlineAPI.INSTANCE.withSizeModifier(patDataStyle, 1.5); + return new Tuple<>(new InlineMatch.DataMatch(patData,patDataStyle ), 0); + } catch (Exception e){ + return new Tuple<>(null, 0); + } + } + + // not really used since we're doing escaping + @Override + @Nullable + public InlineMatch getMatch(MatchResult mr, MatchContext ctx){ + return null; // nop + } + + public MatcherInfo getInfo(){ + return patternMatcherInfo; + } + + /** + * Get the ID for this matcher + * @return matcher's ID + */ + public ResourceLocation getId(){ + return patternMatcherID; + } + + private static final Map dirMap = new HashMap<>(); + + static { + dirMap.put("northwest", HexDir.NORTH_WEST); + dirMap.put("west", HexDir.WEST); + dirMap.put("southwest", HexDir.SOUTH_WEST); + dirMap.put("southeast", HexDir.SOUTH_EAST); + dirMap.put("east", HexDir.EAST); + dirMap.put("northeast", HexDir.NORTH_EAST); + dirMap.put("nw", HexDir.NORTH_WEST); + + dirMap.put("w", HexDir.WEST); + dirMap.put("sw", HexDir.SOUTH_WEST); + dirMap.put("se", HexDir.SOUTH_EAST); + dirMap.put("e", HexDir.EAST); + dirMap.put("ne", HexDir.NORTH_EAST); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlineHex.java b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlineHex.java new file mode 100644 index 0000000000..fa9ce27a93 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlineHex.java @@ -0,0 +1,9 @@ +package at.petrak.hexcasting.interop.inline; + +import com.samsthenerd.inline.api.InlineAPI; + +public class InlineHex { + public static void init(){ + InlineAPI.INSTANCE.addDataType(InlinePatternData.InlinePatternDataType.INSTANCE); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlineHexClient.java b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlineHexClient.java new file mode 100644 index 0000000000..aa61b7e193 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlineHexClient.java @@ -0,0 +1,11 @@ +package at.petrak.hexcasting.interop.inline; + +import com.samsthenerd.inline.api.client.InlineClientAPI; + +public class InlineHexClient { + + public static void init(){ + InlineClientAPI.INSTANCE.addMatcher(HexPatternMatcher.INSTANCE); + InlineClientAPI.INSTANCE.addRenderer(InlinePatternRenderer.INSTANCE); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternData.java b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternData.java new file mode 100644 index 0000000000..5dc2dba2ff --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternData.java @@ -0,0 +1,89 @@ +package at.petrak.hexcasting.interop.inline; + +import at.petrak.hexcasting.api.HexAPI; +import at.petrak.hexcasting.api.casting.PatternShapeMatch; +import at.petrak.hexcasting.api.casting.iota.PatternIota; +import at.petrak.hexcasting.api.casting.math.HexPattern; +import at.petrak.hexcasting.common.casting.PatternRegistryManifest; +import at.petrak.hexcasting.common.lib.HexItems; +import com.mojang.serialization.Codec; +import com.samsthenerd.inline.api.InlineData; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class InlinePatternData implements InlineData{ + + public static final ResourceLocation rendererId = HexAPI.modLoc("pattern"); + + @NotNull + public final HexPattern pattern; + + public InlinePatternData(@NotNull HexPattern pattern){ + this.pattern = pattern; + } + + @Override + public InlinePatternDataType getType(){ + return InlinePatternDataType.INSTANCE; + } + + @Override + public ResourceLocation getRendererId(){ + return rendererId; + } + + @Override + public Style getExtraStyle() { + ItemStack scrollStack = new ItemStack(HexItems.SCROLL_MEDIUM); + HexItems.SCROLL_MEDIUM.writeDatum(scrollStack, new PatternIota(pattern)); + scrollStack.setHoverName(getPatternName(pattern).copy().withStyle(ChatFormatting.WHITE)); + HoverEvent he = new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(scrollStack)); + ClickEvent ce = new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, pattern.toString()); + return Style.EMPTY.withHoverEvent(he).withClickEvent(ce); + } + + public static Component getPatternName(HexPattern pattern){ + try { + PatternShapeMatch shapeMatch = PatternRegistryManifest.matchPattern(pattern, null, false); + if(shapeMatch instanceof PatternShapeMatch.Normal normMatch){ + return HexAPI.instance().getActionI18n(normMatch.key, false); + } + // TODO: this doesn't actually ever hit because it errors out with server castinv env stuff first :( + if(shapeMatch instanceof PatternShapeMatch.Special specialMatch){ + return HexAPI.instance().getSpecialHandlerI18n(specialMatch.key); + } + } catch (Exception e){ + // nop + } + return PatternIota.displayNonInline(pattern); + } + + @Override + public Component asText(boolean withExtra) { + return Component.literal(pattern.toString()).withStyle(asStyle(withExtra)); + } + + public static class InlinePatternDataType implements InlineDataType { + private static final ResourceLocation ID = new ResourceLocation(HexAPI.MOD_ID, "pattern"); + public static final InlinePatternDataType INSTANCE = new InlinePatternDataType(); + + @Override + public ResourceLocation getId(){ + return ID; + } + + @Override + public Codec getCodec(){ + return HexPattern.CODEC.xmap( + InlinePatternData::new, + data -> data.pattern + ); + } + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternRenderer.java b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternRenderer.java new file mode 100644 index 0000000000..eb7b798487 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternRenderer.java @@ -0,0 +1,82 @@ +package at.petrak.hexcasting.interop.inline; + +import at.petrak.hexcasting.client.render.*; +import com.samsthenerd.inline.api.client.GlowHandling; +import com.samsthenerd.inline.api.client.InlineRenderer; +import com.samsthenerd.inline.impl.InlineStyle; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; + +public class InlinePatternRenderer implements InlineRenderer { + + public static final InlinePatternRenderer INSTANCE = new InlinePatternRenderer(); + + public ResourceLocation getId(){ + return InlinePatternData.rendererId; + } + + public static final PatternSettings INLINE_SETTINGS = new PatternSettings("inline", + new PatternSettings.PositionSettings(1.0, 9.0, 0, 0.5, + PatternSettings.AxisAlignment.CENTER, PatternSettings.AxisAlignment.CENTER_FIT, 4.0, 0, 0), + PatternSettings.StrokeSettings.fromStroke(1.0), + new PatternSettings.ZappySettings(10, 0, 0, 0, + PatternSettings.ZappySettings.READABLE_OFFSET, 0.7f) + ){ + @Override + public double getOuterWidth(double scale){ + if(scale >= 1) return 1; + if(scale >= 0.75) return 0.75; + if(scale >= 0.5) return 0.5; + return 0.25; + } + }; + + public static final PatternSettings INLINE_SETTINGS_GLOWY = new PatternSettings("inlineglowy", + new PatternSettings.PositionSettings(1.0, 11.0, 0, 0.5, + PatternSettings.AxisAlignment.CENTER, PatternSettings.AxisAlignment.CENTER_FIT, 4.0, 0, 0), + new PatternSettings.StrokeSettings(1, 3, 0.8 * 1 * 2.0 / 5.0, 0.4 * 1 * 2.0 / 5.0), + INLINE_SETTINGS.zapSets + ){ + @Override + public double getInnerWidth(double scale){ + if(scale >= 1) return 1; + if(scale >= 0.75) return 0.75; + if(scale >= 0.5) return 0.5; + return 0.25; + } + }; + + @Override + public GlowHandling getGlowPreference(InlinePatternData forData) { + return new GlowHandling.None(); + } + + public static final int INLINE_TEXTURE_RES = 16; // 128px so it looks good and pretty on up close signs and whatnot + + public int render(InlinePatternData data, GuiGraphics drawContext, int index, Style style, int codepoint, TextRenderingContext trContext){ + if(trContext.isGlowy()) return charWidth(data, style, codepoint); + int glowyParentColor = ((InlineStyle) style).getComponent(InlineStyle.GLOWY_PARENT_COMP); + boolean isGlowy = glowyParentColor != -1; + drawContext.pose().pushPose(); + drawContext.pose().translate(isGlowy ? -1f : 0, isGlowy ? -1.5f : -0.5f, 0f); + int color = trContext.usableColor(); + PatternRenderer.renderPattern(data.pattern, drawContext.pose(), new PatternRenderer.WorldlyBits(drawContext.bufferSource(), trContext.light(), null), + isGlowy ? INLINE_SETTINGS_GLOWY : INLINE_SETTINGS, + isGlowy ? new PatternColors(color, 0xFF_000000 | glowyParentColor) : PatternColors.singleStroke(color), + 0, INLINE_TEXTURE_RES); + + drawContext.pose().popPose(); + return charWidth(data, style, codepoint); + } + + public int charWidth(InlinePatternData data, Style style, int codepoint){ + + HexPatternPoints staticPoints = HexPatternPoints.getStaticPoints(HexPatternLike.of(data.pattern), INLINE_SETTINGS, 0); + + double baseScale = 4.0 / 1.5; + double baseHeight = staticPoints.rangeY * baseScale; + + return (int)Math.ceil(Math.min(baseHeight, 8.0) * staticPoints.rangeX / staticPoints.rangeY) + 1; // (+2 for padding) + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/AbstractPatternComponent.java b/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/AbstractPatternComponent.java index 4165756417..997bb09411 100644 --- a/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/AbstractPatternComponent.java +++ b/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/AbstractPatternComponent.java @@ -1,14 +1,11 @@ package at.petrak.hexcasting.interop.patchouli; -import at.petrak.hexcasting.api.casting.math.HexCoord; import at.petrak.hexcasting.api.casting.math.HexPattern; -import at.petrak.hexcasting.client.render.RenderLib; -import at.petrak.hexcasting.interop.utils.PatternDrawingUtil; -import at.petrak.hexcasting.interop.utils.PatternEntry; +import at.petrak.hexcasting.client.render.PatternColors; +import at.petrak.hexcasting.client.render.PatternRenderer; +import at.petrak.hexcasting.client.render.PatternSettings; import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.datafixers.util.Pair; import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.world.phys.Vec2; import vazkii.patchouli.api.IComponentRenderContext; import vazkii.patchouli.api.ICustomComponent; import vazkii.patchouli.api.IVariable; @@ -23,8 +20,7 @@ abstract public class AbstractPatternComponent implements ICustomComponent { protected transient int x, y; protected transient float hexSize; - private transient List patterns; - private transient List zappyPoints; + private transient List patterns; /** * Pass -1, -1 to center it. @@ -35,29 +31,57 @@ public void build(int x, int y, int pagenum) { this.y = y == -1 ? 70 : y; } - public abstract List> getPatterns(UnaryOperator lookup); + public abstract List getPatterns(UnaryOperator lookup); public abstract boolean showStrokeOrder(); @Override public void render(GuiGraphics graphics, IComponentRenderContext context, float pticks, int mouseX, int mouseY) { - PatternDrawingUtil.drawPattern(graphics, this.x, this.y, this.patterns, this.zappyPoints, - this.showStrokeOrder(), - 0xff_d2c8c8, 0xc8_aba2a2, 0xc8_322b33, 0x80_d1cccc); + PoseStack ps = graphics.pose(); + // want to position x: [0, 116], y: [16, 80] + ps.pushPose(); + + int cols = (int)Math.ceil(Math.sqrt(patterns.size())); + int rows = (int)Math.ceil(patterns.size()/(double)cols); + + double cellW = 116 / (double)cols; + double cellH = 64 / (double)rows; + + PatternSettings patSets = new PatternSettings("book" + patterns.size() + (showStrokeOrder() ? "" : "r"), + new PatternSettings.PositionSettings(cellW, cellH, 2, 2, + PatternSettings.AxisAlignment.CENTER_FIT, PatternSettings.AxisAlignment.CENTER_FIT, 16, 0, 0), + PatternSettings.StrokeSettings.fromStroke(4), + showStrokeOrder() ? PatternSettings.ZappySettings.READABLE : PatternSettings.ZappySettings.STATIC + ); + + PatternColors patCols = PatternColors.DIMMED_COLOR.withDots(false, true); + + if(showStrokeOrder()){ + patCols = PatternRenderer.shouldDoStrokeGradient() ? PatternColors.DEFAULT_GRADIENT_COLOR.withDots(true, true) + : PatternColors.READABLE_GRID_SCROLL_COLORS; + } + + for(int p = 0; p < patterns.size(); p++){ + + int r = p / cols; + int c = p % cols; + HexPattern pattern = patterns.get(p); + + ps.pushPose(); + ps.translate(cellW * c, cellH * r + 16, 100); + + PatternRenderer.renderPattern(pattern, graphics.pose(), patSets, patCols, 0, 4); + ps.popPose(); + } + ps.popPose(); } @Override public void onVariablesAvailable(UnaryOperator lookup) { - var patterns = this.getPatterns(lookup); - var data = PatternDrawingUtil.loadPatterns( - patterns, - this.showStrokeOrder() ? RenderLib.DEFAULT_READABILITY_OFFSET : 0f, - this.showStrokeOrder() ? RenderLib.DEFAULT_LAST_SEGMENT_LEN_PROP : 1f); - this.hexSize = data.hexSize(); - this.patterns = data.patterns(); - this.zappyPoints = data.pathfinderDots(); + this.patterns = this.getPatterns(lookup); } + // used for deserialization from patchi protected static class RawPattern { protected String startdir; protected String signature; diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/LookupPatternComponent.java b/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/LookupPatternComponent.java index 7f5517b361..632c7abf79 100644 --- a/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/LookupPatternComponent.java +++ b/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/LookupPatternComponent.java @@ -1,11 +1,9 @@ package at.petrak.hexcasting.interop.patchouli; -import at.petrak.hexcasting.api.casting.math.HexCoord; import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.api.mod.HexTags; import at.petrak.hexcasting.xplat.IXplatAbstractions; import com.google.gson.annotations.SerializedName; -import com.mojang.datafixers.util.Pair; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import vazkii.patchouli.api.IVariable; @@ -24,13 +22,13 @@ public class LookupPatternComponent extends AbstractPatternComponent { protected boolean strokeOrder; @Override - public List> getPatterns(UnaryOperator lookup) { + public List getPatterns(UnaryOperator lookup) { var key = ResourceKey.create(IXplatAbstractions.INSTANCE.getActionRegistry().key(), this.opName); var entry = IXplatAbstractions.INSTANCE.getActionRegistry().get(key); this.strokeOrder = !IXplatAbstractions.INSTANCE.getActionRegistry().getHolderOrThrow(key).is(HexTags.Actions.PER_WORLD_PATTERN); - return List.of(new Pair<>(entry.prototype(), HexCoord.getOrigin())); + return List.of(entry.prototype()); } @Override diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/ManualPatternComponent.java b/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/ManualPatternComponent.java index f03946384c..06f77903ba 100644 --- a/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/ManualPatternComponent.java +++ b/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/ManualPatternComponent.java @@ -1,12 +1,10 @@ package at.petrak.hexcasting.interop.patchouli; -import at.petrak.hexcasting.api.casting.math.HexCoord; import at.petrak.hexcasting.api.casting.math.HexDir; import at.petrak.hexcasting.api.casting.math.HexPattern; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.annotations.SerializedName; -import com.mojang.datafixers.util.Pair; import vazkii.patchouli.api.IVariable; import java.util.ArrayList; @@ -25,19 +23,18 @@ public class ManualPatternComponent extends AbstractPatternComponent { protected transient boolean strokeOrder; @Override - public List> getPatterns(UnaryOperator lookup) { + public List getPatterns(UnaryOperator lookup) { this.strokeOrder = lookup.apply(IVariable.wrap(this.strokeOrderRaw)).asBoolean(true); var patsRaw = lookup.apply(IVariable.wrap(patternsRaw)).asListOrSingleton(); - var out = new ArrayList>(); + var out = new ArrayList(); for (var ivar : patsRaw) { JsonElement json = ivar.unwrap(); RawPattern raw = new Gson().fromJson(json, RawPattern.class); var dir = HexDir.fromString(raw.startdir); var pat = HexPattern.fromAngles(raw.signature, dir); - var origin = new HexCoord(raw.q, raw.r); - out.add(new Pair<>(pat, origin)); + out.add(pat); } return out; diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index d0cca4e21b..2eb861e346 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -123,7 +123,7 @@ directrix: { empty: "Empty Directrix", redstone: "Mason Directrix", - boolean: "??? Directrix", + boolean: "Shepherd Directrix", }, impetus: { @@ -210,6 +210,47 @@ }, }, + "effect.hexcasting": { + enlarge_grid: "Clarity", + shrink_grid: "Clouding", + }, + + "item.minecraft.potion.effect": { + enlarge_grid: "Potion of Clarity", + enlarge_grid_long: "Potion of Clarity", + enlarge_grid_strong: "Potion of Clarity", + shrink_grid: "Potion of Clouding", + shrink_grid_long: "Potion of Clouding", + shrink_grid_strong: "Potion of Clouding", + }, + + "item.minecraft.splash_potion.effect": { + enlarge_grid: "Splash Potion of Clarity", + enlarge_grid_long: "Splash Potion of Clarity", + enlarge_grid_strong: "Splash Potion of Clarity", + shrink_grid: "Splash Potion of Clouding", + shrink_grid_long: "Splash Potion of Clouding", + shrink_grid_strong: "Splash Potion of Clouding", + }, + + "item.minecraft.lingering_potion.effect": { + enlarge_grid: "Lingering Potion of Clarity", + enlarge_grid_long: "Lingering Potion of Clarity", + enlarge_grid_strong: "Lingering Potion of Clarity", + shrink_grid: "Lingering Potion of Clouding", + shrink_grid_long: "Lingering Potion of Clouding", + shrink_grid_strong: "Lingering Potion of Clouding", + }, + + "item.minecraft.tipped_arrow.effect": { + enlarge_grid: "Arrow of Clarity", + enlarge_grid_long: "Arrow of Clarity", + enlarge_grid_strong: "Arrow of Clarity", + shrink_grid: "Arrow of Clouding", + shrink_grid_long: "Arrow of Clouding", + shrink_grid_strong: "Arrow of Clouding", + }, + "itemGroup.hexcasting": { "": "Hexcasting", creative_tab: "Hexcasting", @@ -300,6 +341,10 @@ "": "Grid Snap Threshold", "@Tooltip": "When using a staff, the distance from one dot you have to go to snap to the next dot, where 0.5 means 50% of the way (0.5-1)", }, + clickingTogglesDrawing: { + "": "Clicking Toggles Drawing", + "@Tooltip": "Whether you click to start and stop drawing instead of clicking and dragging to draw", + }, }, server: { @@ -349,6 +394,10 @@ "": "Hexcasting Research", desc: "Find and mine a concentrated form of media growing deep beneath the earth.", }, + creative_unlocker: { + "": "UNLIMITED POWAH", + "desc": "Found the creative debug item." + }, enlightenment: { "": "Achieve Enlightenment", desc: "Shatter a barrier by casting a hex using almost all of your health.", @@ -430,6 +479,14 @@ }, }, + // === Inline ! === + "matcher.hexcasting.pattern": { + "title": "Hex Pattern", + "title.styled": "Hex Pattern ", + "description": "Visual Hex Casting Patterns", + "example": "" + }, + hexcasting: { "pattern.unknown": "Unknown pattern resource location %s", @@ -549,6 +606,7 @@ "scroll.scribble": "Scroll is scribbled", "impetus.fletcher.tick": "Fletcher Impetus ticks", "impetus.redstone.register": "Cleric Impetus dings", + "impetus.redstone.clear": "Cleric Impetus clears", "lore_fragment.read": "Read lore fragment", "flight.ambience": "Player flies", "flight.finish": "Flight ends", @@ -604,7 +662,7 @@ }, append: "Integration Distillation", - unappend: "Derivation Distillation", + unappend: "Derivation Decomposition", index: "Selection Distillation", singleton: "Single's Purification", empty_list: "Vacant Reflection", @@ -724,7 +782,7 @@ erase: "Erase Item", create_water: "Create Water", destroy_water: "Destroy Liquid", - ignite: "Ignite Block", + ignite: "Ignite", extinguish: "Extinguish Area", conjure_block: "Conjure Block", conjure_light: "Conjure Light", @@ -758,6 +816,7 @@ flight: "Altiora", "flight/range": "Anchorite's Flight", "flight/time": "Wayfarer's Flight", + "flight/can_fly": "Aviator's Purification", lightning: "Summon Lightning", summon_rain: "Summon Rain", @@ -905,6 +964,7 @@ invalid_spell_datum_type: "Tried to use a value of invalid type as a SpellDatum: %s (class %s). This is a bug in the mod.", unknown: "threw an exception (%s). This is a bug in the mod.", stack_size: "Exceeded the size limit of the stack", + bad_caster: "Tried to execute a pattern that requires a greater mind", invalid_value: { "": "expected %s at index %s of the stack, but got %s", @@ -925,6 +985,8 @@ living: "a living entity", }, + entity_or_vector: "an entity or a vector", + unknown: "(unknown, uh-oh, this is a bug)", }, @@ -1057,15 +1119,20 @@ great_spells: { "": "Great Spells", desc: "The spells catalogued here are purported to be of legendary difficulty and power. \ - They seem to have been recorded only sparsely (for good reason, the texts claim). \ + They seem to have been recorded only $(l:items/scroll)sparsely/$ (for good reason, the texts claim). \ It's probably just the ramblings of extinct traditionalists, though -- a pattern's a pattern.$(br2)\ What could possibly go wrong?", }, + creative_unlocker: { + "": "The Media Cube", + desc: "I have found a source of near limitless power, The Media Cube.", + } }, // ^ categories entry: { media: "Media", + media_cube: "The Media Cube", geodes: "Geodes", couldnt_cast: "A Frustration", start_to_see: "WHAT DID I SEE", @@ -1093,6 +1160,7 @@ // why is this called "hexcasting"? hexcasting: "Casting Items", phials: "Phials of Media", + potions: "Hex Potions", pigments: "Pigments", edified: "Edified Trees", @@ -1163,6 +1231,13 @@ // ^ entries page: { + creative_unlocker: { + "1": "The $(#74b3f2)Media Cube/$ is a near-limitless source of power, acting as an infinite source of _media for whatever _Hexes I wish to cast. It also looks quite tasty. Eating it might perhaps reveal more about the world, possibly delving into spoiler territory...", + "2": "Additionally, it seems that by renaming a $(#74b3f2)Media Cube/$ at an Anvil, I should be able to utilize it to test and understand more about my _Hexes. If I have somehow acquired Creative power over the world, I might wish to turn that off before attempting any of the following.", + "3": "By renaming the $(#74b3f2)Media Cube/$ to 'debug media', I will be given feedback in the chat window about the amount of media consumed.", + // NOTE: The following feature documented is broken in the current implementation. It should be uncommented and the page in patchouli should be updated when the feature is fixed. + //"4": "By renaming the $(#74b3f2)Media Cube/$ to 'debug patterns', I will be given feedback in the chat window about the order patterns are ran." + }, media: { "1": "_Media is a form of mental energy external to a mind. All living creatures generate trace amounts of _media when thinking about anything; after the thought is finished, the media is released into the environment.$(br2)The art of casting _Hexes is all about manipulating _media to do your bidding.", "2": "_Media can exert influences on other media-- the strength and type of influence can be manipulated by drawing _media out into patterns.$(p)Scholars of the art used a concentrated blob of _media on the end of a stick: by waving it in the air in precise configurations, they were able to manipulate enough _media with enough precision to influence the world itself, in the form of a _Hex.", @@ -1431,6 +1506,12 @@ desc: "$(italic)Drink the milk./$", }, + potions: { + "1": "Peering through a $(l:items/lens)$(item)Scrying Lens/$ allows me to fit more patterns onto my casting grid. But what if I want to improve my casting even further? Alchemy has an answer.$(br2)By adding a pinch of Amethyst Dust to an $(item)Awkward Potion/$, I can create a brew that increases the size of my grid in much the same way as the Lens. I can even use both at once for the ultimate grid!", + "2": "The Potion of Clarity can be extended, strengthened, and modified like any other potion.$(br2)It can also be corrupted with a $(item)Fermented Spider Eye/$ to produce a variant with the opposite effect. This may be useful for dealing with enemy casters.", + "effects.header": "Clarity and Clouding", + }, + pigments: { "1": "The old practitioners of my art sometimes identified themselves by a color, emblematic of them and their _Hexes. Although their names have faded, their colors remain. It seems a special kind of pigment, offered to Nature in the right way, would \"[...] paint one's thoughts in a manner pleasing to Nature, inducing a miraculous change in personal colour.\"", "2": "I'm not certain on the specifics, but I believe I have isolated the formulae for many different colors and blends of pigments. To apply a pigment, I hold it in one hand and cast $(l:patterns/spells/colorize)$(action)Internalize Pigment/$ with the other; this consumes the pigment.$(br2)The pigments seem to affect the color of the sparks of _media emitted when I cast a _Hex and my $(l:patterns/spells/sentinels)$(thing)sentinel/$, but I don't doubt that the color will show up elsewhere.", @@ -1506,6 +1587,7 @@ empty_directrix: "Firstly, a design for the cradle ... although, perhaps \"substrate\" would be more accurate a word. Without a mind guiding it, the output direction is determined by microscopic fluctuations in the _media wave and surroundings, making it effectively random.", directrix_redstone: "A $(l:greatwork/directrix)$(item)Mason Directrix/$ switches output side based on a redstone signal. Without a signal, the exit is the _media-color side; with a signal, the exit is the redstone-color side.", + directrix_boolean: "A $(l:greatwork/directrix)$(item)Shepherd Directrix/$ switches output side based on a boolean on the stack. A $(thing)True/$ makes the wave exit from the back, a $(thing)False/$ from the front." }, akashiclib: { @@ -1596,8 +1678,8 @@ floor: "\"Floors\" a number, cutting off the fractional component and leaving an integer value. If passed a vector, instead floors each of its components.", ceil: "\"Ceilings\" a number, raising it to the next integer value if it has a fractional component. If passed a vector, instead ceils each of its components.", - construct_vec: "Combine three numbers at the top of the stack into a vector's X, Y, and Z components (top to bottom).", - deconstruct_vec: "Split a vector into its X, Y, and Z components (top to bottom).", + construct_vec: "Combine three numbers at the top of the stack into a vector's X, Y, and Z components (bottom to top).", + deconstruct_vec: "Split a vector into its X, Y, and Z components (bottom to top).", modulo: "Takes the modulus of two numbers. This is the amount $(italics)remaining/$ after division - for example, 5 %% 2 is 1, and 5 %% 3 is 2. When applied on vectors, performs the above operation elementwise.", coerce_axial: "For a vector, coerce it to its nearest axial direction, a unit vector. For a number, return the sign of the number; 1 if positive, -1 if negative. In both cases, zero is unaffected.", random: "Creates a random number between 0 and 1.", @@ -1618,7 +1700,7 @@ numlist: "Set operations are odd, in that some of them can accept two numbers or two lists, but not a combination thereof. Such arguments will be written as \"(num, num)|(list, list)\".$(br2)When numbers are used in those operations, they are being used as so-called binary \"bitsets\", lists of 1 and 0, true and false, \"on\" and \"off\".", "or.1": "Unifies two sets.", - "or.2": "As such:$(li)With two numbers at the top of the stack, combines them into a bitset containing every \"on\" bit in either bitset.$(li)With two lists, this creates a list of every element from the first list, plus every element from the second list that is not in the first list. This is similar to $(l:patterns/lists#hexcasting:add)$(action)Combination Distillation/$.", + "or.2": "As such:$(li)With two numbers at the top of the stack, combines them into a bitset containing every \"on\" bit in either bitset.$(li)With two lists, this creates a list of every element from the first list, plus every element from the second list that is not in the first list. This is somewhat similar to $(l:patterns/lists#hexcasting:add)$(action)Additive Distillation/$.", "and.1": "Takes the intersection of two sets.", "and.2": "As such:$(li)With two numbers at the top of the stack, combines them into a bitset containing every \"on\" bit present in $(italics)both/$ bitsets.$(li)With two lists, this creates a list of every element from the first list that is also in the second list.", @@ -1678,7 +1760,7 @@ }, logic: { - bool_coerce: "Convert an argument to a boolean. The number $(thing)0/$, $(l:casting/influences)$(thing)Null/$, and the empty list become False; everything else becomes True.", + bool_coerce: "Convert an argument to a boolean. The number $(thing)0/$, $(l:casting/influences)$(thing)Null/$, False, and the empty list become False; everything else becomes True.", bool_to_number: "Convert a boolean to a number; True becomes $(thing)1/$, and False becomes $(thing)0/$.", not: "If the argument is True, return False; if it is False, return True.", or: "Returns True if at least one of the arguments are True; otherwise returns False.", @@ -1749,11 +1831,11 @@ "2": "If I draw another $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Introspection/$, it'll still be saved to the list, but I'll then have to draw $(italic)two/$ $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$ to get back to normal casting.", "3": "Also, I can escape the special behavior of $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ by drawing a $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ before them, which will simply add them to the list without affecting which the number of $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospections/$ I need to return to casting.$(br2)If I draw two $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Considerations/$ in a row while $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)introspecting/$, it will add a single $(l:patterns/patterns_as_iotas#hexcasting:escape)$(action)Consideration/$ to the list.", }, - undo: "Finally, if I make a mistake while drawning patterns inside $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ I can draw $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ to remove the last pattern that I drew from the pattern list that is being constructed.", + undo: "Finally, if I make a mistake while drawing patterns inside $(l:patterns/patterns_as_iotas#hexcasting:open_paren)$(action)Intro-/$ and $(l:patterns/patterns_as_iotas#hexcasting:close_paren)$(action)Retrospection/$ I can draw $(l:patterns/patterns_as_iotas#hexcasting:undo)$(action)Evanition/$ to remove the last pattern that I drew from the pattern list that is being constructed.", }, readwrite: { - "1": "This section deals with the storage of $(thing)Iotas/$ in a more permanent medium. Nearly any iota can be stored to a suitable item, such as a $(l:items/focus)$(item)Focus/$ or $(l:items/spellbook)$(item)Spellbook/$), and read back later. Certain items, such as an $(l:items/abacus)$(item)Abacus/$, can only be read from.$(br2)Iotas are usually read and written from the other hand, but it is also possible to read and write with an item when it is sitting on the ground as an item entity, or when in an item frame.", + "1": "This section deals with the storage of $(thing)Iotas/$ in a more permanent medium. Nearly any iota can be stored to a suitable item, such as a $(l:items/focus)$(item)Focus/$ or $(l:items/spellbook)$(item)Spellbook/$, and read back later. Certain items, such as an $(l:items/abacus)$(item)Abacus/$, can only be read from.$(br2)Iotas are usually read and written from the other hand, but it is also possible to read and write with an item when it is sitting on the ground as an item entity, or when in an item frame.", "2": "There may be other entities I can interact with in this way. For example, a $(l:items/scroll)$(item)Scroll/$ hung on the wall can have its pattern read off of it.$(br2)However, it seems I am unable to save a reference to another player, only me. I suppose an entity reference is similar to the idea of a True Name; perhaps Nature is helping to keep our Names out of the hands of enemies. If I want a friend to have my Name I can make a $(l:items/focus)$(item)Focus/$ for them.", read: "Copy the iota stored in the item in my other hand and add it to the stack.", write: "Remove the top iota from the stack, and save it into the item in my other hand.", @@ -1830,7 +1912,7 @@ 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/$.", 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/$.", 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/$.", - 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/$.", + ignite: "Start a fire on top of the given location, as if a $(item)Fire Charge/$ was applied, or sets fire to a creature. Costs about one $(l:items/amethyst)$(item)Amethyst Dust/$.", extinguish: "Extinguish blocks in a large area. Costs about six $(l:items/amethyst)$(item)Amethyst Dust/$.", }, @@ -1869,9 +1951,9 @@ }, 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/$.", - + cycle_variant: "Certain items I create seem oddly receptive to the influence of _media. By holding a $(l:items/hexcasting)$(item)Cypher/$, $(l:items/hexcasting)$(item)Trinket/$, $(l:items/hexcasting)$(item)Artifact/$, $(l:items/focus)$(item)Focus/$, or $(l:items/spellbook)$(item)Spellbook/$ in my other hand, I can use this spell to change the appearance of the item. Costs about one $(l:items/amethyst)$(item)Amethyst Dust/$.", - + flights: { "1": "Although it seems that true, limitless flight is out of my grasp, I have nonetheless found some methods of holding one in the sky, each with their respective drawbacks.$(br2)All forms produce a shimmer of excess _media; as the spell gets closer to ending, the sparks are shot through with more red and black.", "2": "Other forms of flight do exist, of course. For example, a combination of $(l:patterns/spells/basic#hexcasting:add_motion)$(action)Impulse/$ and $(l:patterns/spells/nadirs#hexcasting:potion/levitation)$(action)Blue Sun's Nadir/$ has been used since antiquity for a flight of sorts.$(br2)I've also heard tell of a thin membrane worn on the back that allows the ability to glide. From my research, I believe the Great spell $(l:patterns/great_spells/altiora)$(action)Altiora/$ may be used to mimic it.", @@ -1881,6 +1963,10 @@ "time.1": "A flight limited in its duration.", "time.2": "The second argument is an amount of time in seconds for which the spell is stable. After that time, the spell ends and I am dropped from the sky. $(br2)It is relatively expensive at about 1 $(l:items/amethyst)$(item)Charged Crystal/$ per second of flight; I believe it is best suited for travel.", + + // Putting this in this category instead of elsewhere -- see sentinel chapter also containing some reflections/nonspells + "can_fly.1": "Returns whether the given player is under the effects of $(l:patterns/spells/flight#hexcasting:flight/range)Anchorite's/$ or $(l:patterns/spells/flight#hexcasting:flight/time)Wayfarer's Flight/$.", + "can_fly.2": "Does not detect whether the player can fly by other means.$(br2)It is unclear to me what use this has. But I suppose its utility is in the eye of the beholder." }, create_lava: { @@ -1901,7 +1987,7 @@ "teleport/great": { "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.", - "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.", + "2": "The entity will be teleported by the given vector. Curiously, this vector seems to be an offset, not an absolute position in the world; for example, if I use $(l:patterns/consts#hexcasting:const/vec/x)$(action)Vector Reflection +X/$, the entity will end up precisely one block east of its original 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.", }, zeniths: { @@ -2025,7 +2111,7 @@ "3": "Finally, if I find myself interested in the lore and stories of this world, I do not think any notes compiled while examining these interoperations should be considered as anything more than light trifles.", 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.", + "1": "I have discovered actions to get and set an entity's gravity. I find them interesting, if slightly nauseating.$(br2)Interestingly, although the $(l:patterns/spells/flight)$(action)Flight/$ spells are limited in range or duration, and manipulate gravity similarly, these are not. It baffles me why... Perhaps the mod developer wanted players to have fun, for once.", 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>.", 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/$.", }, diff --git a/Common/src/main/resources/assets/hexcasting/lang/zh_cn.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/zh_cn.flatten.json5 index 20c7e04591..7715983dec 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/zh_cn.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/zh_cn.flatten.json5 @@ -35,7 +35,7 @@ of: "是如何拿到此%s卷轴的", empty: "空白中型卷轴", }, - + scroll: { "": "大型卷轴", of: "%s之远古卷轴", @@ -123,7 +123,7 @@ directrix: { empty: "空白导向石", redstone: "石匠导向石", - boolean: "???导向石", + boolean: "牧羊人导向石", }, impetus: { @@ -152,7 +152,7 @@ bricks_small: "板岩小型砖", pillar: "板岩柱", }, - + amethyst_: { dust_block: "紫水晶粉块", tiles: "紫水晶瓦", @@ -210,6 +210,47 @@ }, }, + "effect.hexcasting": { + enlarge_grid: "明晰", + shrink_grid: "蒙翳", + }, + + "item.minecraft.potion.effect": { + enlarge_grid: "明晰药水", + enlarge_grid_long: "明晰药水", + enlarge_grid_strong: "明晰药水", + shrink_grid: "蒙翳药水", + shrink_grid_long: "蒙翳药水", + shrink_grid_strong: "蒙翳药水", + }, + + "item.minecraft.splash_potion.effect": { + enlarge_grid: "喷溅型明晰药水", + enlarge_grid_long: "喷溅型明晰药水", + enlarge_grid_strong: "喷溅型明晰药水", + shrink_grid: "喷溅型蒙翳药水", + shrink_grid_long: "喷溅型蒙翳药水", + shrink_grid_strong: "喷溅型蒙翳药水", + }, + + "item.minecraft.lingering_potion.effect": { + enlarge_grid: "滞留型明晰药水", + enlarge_grid_long: "滞留型明晰药水", + enlarge_grid_strong: "滞留型明晰药水", + shrink_grid: "滞留型蒙翳药水", + shrink_grid_long: "滞留型蒙翳药水", + shrink_grid_strong: "滞留型蒙翳药水", + }, + + "item.minecraft.tipped_arrow.effect": { + enlarge_grid: "明晰之箭", + enlarge_grid_long: "明晰之箭", + enlarge_grid_strong: "明晰之箭", + shrink_grid: "蒙翳之箭", + shrink_grid_long: "蒙翳之箭", + shrink_grid_strong: "蒙翳之箭", + }, + "itemGroup.hexcasting": { "": "咒法学", creative_tab: "咒法学", @@ -300,8 +341,12 @@ "": "咒术网格吸附阈值", "@Tooltip": "使用法杖时,吸附至另一点所需划过的距离,0.5意味着需划过50%(取值0.5~1)", }, + clickingTogglesDrawing: { + "": "单击切换绘制与否", + "@Tooltip": "单击即可开始或结束绘制,无需按住拖动", + }, }, - + server: { opBreakHarvestLevel: { "": "破坏方块挖掘等级", @@ -349,6 +394,10 @@ "": "咒法学研究", desc: "在地底深处找到并采集不断生长着的、凝聚的媒质", }, + creative_unlocker: { + "": "无尽能量!", + "desc": "找到创造调试物品。" + }, enlightenment: { "": "获得启迪", desc: "施放咒术至生命将尽来击碎屏障", @@ -408,18 +457,21 @@ }, "death.attack.hexcasting": { - overcast: "%s的意识消散为了能量", + overcast: { + "": "%s的意识消散为了能量", + player: "在与%2$s战斗时,%1$s的意识消散为了能量" + }, }, "command.hexcasting": { recalc: "已重生成此世界特有图案", - + pats: { listing: "此世界特有的图案:", all: "给予%s所有%d张卷轴", "specific.success": "给予%3$sID为%2$s的%1$s", }, - + brainsweep: { "": "已清除%s的意识", "fail.badtype": "%s不是生物", @@ -427,6 +479,14 @@ }, }, + // === Inline ! === + "matcher.hexcasting.pattern": { + "title": "咒术图案", + "title.styled": "咒术图案", + "description": "咒法学图案可视化", + "example": "" + }, + hexcasting: { "pattern.unknown": "未知图案资源 %s", @@ -473,7 +533,7 @@ many_exits: "%s处的媒质流的可选去路过多", no_closure: "%s处的媒质流无法返回促动石" }, - + lens: { "pattern.invalid": "无效图案", bee: { @@ -498,7 +558,7 @@ level: "%s级", product: "无意识的躯体", }, - + media: "%d 紫水晶粉", media_amount: "含有:%s(%s)", "media_amount.advanced": "含有:%s/%s(%s)", @@ -556,7 +616,7 @@ // TODO: the +1 is kind of janky scry_sight: "探知视觉" }, - + // Action localizations action: { @@ -601,7 +661,7 @@ }, append: "整合之馏化", - unappend: "派生之馏化", + unappend: "派生之分解", index: "选择之馏化", singleton: "单体之纯化", empty_list: "空无之精思", @@ -721,13 +781,14 @@ erase: "清除物品", create_water: "制造水源", destroy_water: "清除流体", - ignite: "点燃方块", + ignite: "点燃", extinguish: "广域熄灭", conjure_block: "构筑方块", conjure_light: "构筑光源", bonemeal: "催生", edify: "启迪树苗", colorize: "内化染色剂", + cycle_variant: "施法者之魅力", "sentinel/": { create: "召唤哨卫", @@ -766,6 +827,7 @@ "eval/cc": "伊里斯之策略", for_each: "托特之策略", halt: "卡戎之策略", + thanatos: "塔纳托斯之精思", "interop/": { "gravity/": { @@ -849,7 +911,7 @@ garbage: "垃圾", vec3: "向量", }, - + mishap: { "": "%s:%s", @@ -878,7 +940,7 @@ project: "试图将%s投影至%s", exponent: "试图计算%s的%s", logarithm: "试图计算%s以%s为底的对数", - + zero: { "": "零", power: "零次幂", @@ -899,6 +961,8 @@ disallowed_circle: "已被服务器管理员禁用于法术环", invalid_spell_datum_type: "尝试将某无效类型的值用作SpellDatum:%s(class %s)。这是模组中的bug。", unknown: "抛出异常(%s)。这是模组中的bug。", + stack_size: "超出了栈的大小上限", + bad_caster: "试图运行的图案需要强大的意识才能承受", invalid_value: { "": "本应在栈下标为%2$s处接受%1$s,而实际接受了%3$s", @@ -919,13 +983,15 @@ living: "一个生物", }, + entity_or_vector: "一个实体或向量", + unknown: "(未知,这是个bug)", }, numvec: "一个数或向量", numlist: "一个整数或列表", "list.pattern": "一个由图案组成的列表", - + double: { positive: { "": "一个正数", @@ -960,7 +1026,7 @@ bad_item: { "": "需要%s,而实际持有%d个%s", offhand: "需要在另一只手里持有%s,而实际持有%d个%s", - + iota: { "": "一个可以存储iota的地方", read: "一个可以读出iota的地方", @@ -983,7 +1049,7 @@ sapling: "一个树苗", replaceable: "一个可放置方块的地方", }, - + "circle.bool_directrix": { no_bool: "%s处的iota实际为%s,而非布尔值", empty_stack: "%s处的栈为空栈" @@ -1039,7 +1105,7 @@ "": "模组联动", desc: "好像我装了某些能和咒法学联动的模组!详情如下。" }, - + patterns: { "": "图案", desc: "我所发现的各式图案,和它们的功用。", @@ -1051,15 +1117,20 @@ great_spells: { "": "卓越法术", desc: "这类法术据信极难施放,但也极为强力。\ - 记载它们的文献极为少见(据称理由充分)。\ + 记载它们的文献极为$(l:items/scroll)少见/$(据称理由充分)。\ 这也许只是某些消亡的老古董口中的胡言乱语,不过——它们只是简单的图案罢了。$(br2)\ 试试看又能出什么错呢?", }, + creative_unlocker: { + "": "媒质立方", + desc: "我找到了一个近乎无限的媒质源——媒质立方。", + } }, // ^ categories entry: { media: "媒质", + media_cube: "媒质立方", geodes: "晶洞", couldnt_cast: "挫折", start_to_see: "我看到了什么?", @@ -1087,6 +1158,7 @@ // why is this called "hexcasting"? hexcasting: "施法物品", phials: "媒质之瓶", + potions: "咒术药水", pigments: "染色剂", edified: "启迪树", @@ -1157,6 +1229,13 @@ // ^ entries page: { + creative_unlocker: { + "1": "$(#74b3f2)媒质立方/$是近乎无尽的媒质源,能为所有$(hex)咒术/$无限提供$(media)媒质/$。它看上去也挺好吃的。食用后也许会揭示世界的更多秘密,亦有可能触碰到剧透内容……", + "2": "而且,在铁砧中为$(#74b3f2)媒质立方/$重命名,似乎就能用来测试$(hex)咒术/$,以此加深理解。假使真有一天,我得到了创造的力量,在按照后页所说内容操作之前,最好还是先把它禁用掉。", + "3": "将$(#74b3f2)媒质立方/$重命名为“debug media”,聊天栏中即会出现所消耗媒质量的反馈信息。", + // NOTE: The following feature documented is broken in the current implementation. It should be uncommented and the page in patchouli should be updated when the feature is fixed. + //"4": "将$(#74b3f2)媒质立方/$重命名为“debug patterns”,聊天栏中即会出现所运行图案的运行顺序。" + }, media: { "1": "$(media)媒质/$是一种独立于意识的思维能量。所有生物都会在思考时产生痕量的$(media)媒质/$,而在这一思考过程结束后,产生的媒质就会被释放至环境中。$(br2)所谓施放$(hex)咒术/$,就是操纵$(media)媒质/$以产生有效影响的过程。", "2": "$(media)媒质/$可以影响其他媒质,而这种影响的强度和种类都可通过将$(media)媒质/$绘制为各式图案来进行控制。$(p)研究这种魔法的学者则会使用接在木棍一端的浓缩的$(media)媒质/$。只要在空中按特定模式挥舞,学者们就能以足够的精度操控足量的$(media)媒质/$来影响世界,也即“$(hex)咒术/$”。", @@ -1275,6 +1354,9 @@ "not_enough_iotas.title": "Iota 过少", not_enough_iotas: "该操作需要比当前栈中元素数还多的 iota。$(br2)产生淡灰色火花,并向栈顶压入缺少的参数的数量个$(l:casting/influences)$(action)垃圾/$。", + "stack_size.title": "Iota 过大", + "stack_size": "试图施放超出栈大小限制的法术。$(br2)产生黑色火花,我的整个栈变为仅含有单个$(l:casting/influences)$(action)垃圾/$。", + "incorrect_iota.title": "Iota 错误", incorrect_iota: "该操作需要一种特定类型的 iota 作为参数,而实际 iota 无效。如果有多个 iota 无效,错误信息只会提示最靠近栈底的错误。$(br2)产生深灰色火花,无效的 iota 会被替换为$(l:casting/influences)$(action)垃圾/$。", @@ -1336,10 +1418,10 @@ "bad_mindflay.title": "惰性剥离", bad_mindflay: "试图剥离已被剥除意识的生物的意识,或是试图剥离不适用于目标方块的生物的意识。$(br2)产生暗绿色火花,并杀死对应生物。倘若被村民看到了,他们应该不会对此有什么好评价。", - + "no_circle.title": "缺失法术环", no_circle: "试图直接执行只能在法术环内执行的操作。$(br2)产生淡蓝色火花,并将我物品栏中所有物品散落在地。", - + "no_record.title": "缺失阿卡夏记录", no_record: "试图在无对应方块处访问$(l:greatwork/akashiclib)$(item)阿卡夏记录/$。$(br2)产生紫色火花,并消耗部分经验。", }, @@ -1382,7 +1464,7 @@ abacus: { "1": "虽然有$(l:patterns/numbers)$(action)对应数的图案/$,但它们确实……过于复杂。$(br2)幸运的是,从前研究这门学问的大师们发明了一个名为“$(l:items/abacus)$(item)算盘/$”的天才般的装置,它们能极为方便地表示数。只需要在其上设定好,然后用$(l:patterns/readwrite#hexcasting:read)$(action)书吏之精思/$把值读出即可,就和读出$(l:items/focus)$(item)核心/$中 iota 的操作一样。", - "2": "操作方法是潜行时手持算盘滚动滚轮。如果是主手持算盘,则其数值会以 1 增加或减少,按住 Control 或 Command 时则以 10。若是副手持算盘,则其数值会以 0.1 增加或减少,按住 Control 或 Command 时则以 0.001。$(br2)也可以在潜行时手持算盘右击以重设为 0。", + "2": "操作方法是潜行时手持算盘滚动滚轮。如果是主手持算盘,则其数值会以 1 增加或减少,按住 $(k:sprint) 时则以 10。若是副手持算盘,则其数值会以 0.1 增加或减少,按住 $(k:sprint) 时则以 0.001。$(br2)也可以在潜行时手持算盘右击以重设为 0。", "crafting.desc": "$(italic)数学?那是给聪明人用的!/$", }, @@ -1422,6 +1504,12 @@ desc: "$(italic)玉液琼浆。/$", }, + potions: { + "1": "透过$(l:items/lens)$(item)探知透镜/$观察,就能在咒术网格里塞进更多图案。想让施法变得更紧凑?我想炼金术可能有方法。$(br2)向$(item)粗制的药水/$里放进一撮紫水晶粉,酿成的药水便能像透镜那样增大我的咒术网格。甚至还能两者一起使用,将网格推向极致!", + "2": "明晰效果可延长,可增强,也可如同其他药水那样精制。$(br2)还可用$(item)发酵蛛眼/$腐化,以产生效果相反的药水。那些不怀好意的咒术师可以尝尝它的威力。", + "effects.header": "明晰与蒙翳", + }, + pigments: { "1": "咒法学的古代研究者们偶尔会用一种颜色来象征自己和自己的$(hex)咒术/$。他们的名字已不可考,但那些颜色留存至今。倘若直接向自然呈上一种特殊的染色剂,就能“……以自然乐见的方式为某人的思维涂上颜色,并使个人代表色产生奇妙的变化。”", "2": "我不懂它们的具体原理,但我相信我已琢磨出制造不同颜色染色剂的配方了。若要使用染色剂,可手持之并以另一只手施放$(l:patterns/spells/colorize)$(action)内化染色剂/$,染色剂则会被消耗。$(br2)染色剂似乎会影响我施放$(hex)咒术/$时的$(media)媒质/$火花以及我的$(l:patterns/spells/sentinels)$(thing)哨卫/$的颜色,但影响到其他地方也完全是正常现象。", @@ -1485,7 +1573,7 @@ empty_impetus: "第一步,基床。虽然和纯粹的$(l:greatwork/impetus)$(item)促动石/$不大一样,但它能让$(media)媒质/$从箭头所指方向流出。这就提供了一种改变媒质波所在平面的方法。", impetus_rightclick: "第二步,转移意识。村民职业的不同意味着$(l:greatwork/impetus)$(item)促动石/$激活条件的不同。$(l:greatwork/impetus)$(item)工具匠促动石/$会在对其按下$(k:use)时启动。", impetus_storedplayer: { - "1": "$(l:greatwork/impetus)$(item)牧师促动石/$必须绑定至某一玩家,对其使用存有代表玩家的 iota 的物品(例如$(l:items/focus)$(item)核心/$)即可。这之后,它会在收到红石信号时启动。", + "1": "$(l:greatwork/impetus)$(item)牧师促动石/$会在接收到红石信号时启动。也可绑定至某一玩家,对其使用存有代表玩家的 iota 的物品(例如$(l:items/focus)$(item)核心/$)即可。", "2": "这种$(l:greatwork/impetus)$(item)促动石/$也使得被绑定的玩家和他们周围一小块区域会一直受法术环影响。不管该玩家离法术环多远都会和站在法术环里一样。$(br2)用$(l:items/lens)$(item)探知透镜/$观察$(l:greatwork/impetus)$(item)牧师促动石/$就会显示被绑定的玩家。", }, impetus_look: "$(l:greatwork/impetus)$(item)制箭师促动石/$会在被注视一段时间后启动。", @@ -1497,6 +1585,7 @@ empty_directrix: "第一步,基床……用“基底”这个名字会准确些。倘若没有意识引导,媒质的流向是由$(media)媒质/$波的微观涨落和环境决定的,换句话说,随机的。", directrix_redstone: "$(l:greatwork/directrix)$(item)石匠导向石/$会根据红石信号改变输出方向。没有信号的话,出口是$(media)媒质/$色的一端;有信号的话,出口是红石色的一端。", + directrix_boolean: "$(l:greatwork/directrix)$(item)牧羊人导向石/$会根据栈中的布尔值改变输出方向。为$(thing)True/$时,媒质自后方流出;为$(thing)False/$时,媒质自前方流出。" }, akashiclib: { @@ -1587,8 +1676,8 @@ floor: "对一个数取底,也即去掉小数部分取整。或对向量的每个分量取底。", ceil: "对一个数取顶,也即将小数部分不为零的数换为大于其的最小整数。或对向量的每个分量取顶。", - construct_vec: "将三个数作为向量的 X,Y,Z 分量组合(最下方为 X 分量)。", - deconstruct_vec: "将一个向量拆分为其 X,Y,Z 分量(最下方为 X 分量)。", + construct_vec: "将三个数作为向量的 X,Y,Z 分量组合(自底向上排列)。", + deconstruct_vec: "将一个向量拆分为其 X,Y,Z 分量(自底向上排列)。", modulo: "取两数除法的余数。也即执行除法后$(italic)剩余/$的数。例如,5 %% 2 得 1,5 %% 3 得 2。或对向量的每个分量执行上述操作。", coerce_axial: "若栈顶为向量,则返回与其夹角最小的轴向单位向量;零向量不受影响。若栈顶为数,则返回该数的符号;所给数为正则返回 1,为负则返回 -1,0 不受影响。", random: "返回一个 0 与 1 之间的随机数。", @@ -1609,7 +1698,7 @@ numlist: "集合操作比较奇怪,部分操作只能接受两个数或两个列表,一个数一个列表就不行。这类参数记为“(num, num) (list, list)”。$(br2)当接受的是数时,它们将被视为所谓二进制的“位组”,也就是由 1 和 0、真和假、“开”和“关”组成的列表。", "or.1": "取两集合的并集。", - "or.2": "操作如下:$(li)若栈顶为两个数,将其组合为在两个位组中有一个为 1 处为 1 的位组。$(li)若栈顶为两个列表,则创建一个由第一个列表中所有元素和第二个列表独有的元素组成的列表。和$(l:patterns/lists#hexcasting:add)$(action)组合之馏化/$类似。", + "or.2": "操作如下:$(li)若栈顶为两个数,将其组合为在两个位组中有一个为 1 处为 1 的位组。$(li)若栈顶为两个列表,则创建一个由第一个列表中所有元素和第二个列表独有的元素组成的列表。和$(l:patterns/lists#hexcasting:add)$(action)加法之馏化/$有些类似。", "and.1": "取两集合的交集。", "and.2": "操作如下:$(li)若栈顶为两个数,将其组合为仅在两个位组中$(italic)均/$为 1 处为 1 的位组。$(li)若栈顶为两个列表,则创建一个由第一个和第二个列表共有的元素组成的列表。", @@ -1669,7 +1758,7 @@ }, logic: { - bool_coerce: "将参数变换为布尔值。数 $(thing)0/$、$(l:casting/influences)$(thing)Null/$,以及空列表会变为 False。其余所有则变为 True。", + bool_coerce: "将参数变换为布尔值。数 $(thing)0/$、$(l:casting/influences)$(thing)Null/$、False,以及空列表会变为 False。其余所有则变为 True。", bool_to_number: "将布尔值变换为数。True 变为 $(thing)1/$, False 变为 $(thing)0/$。", not: "如果参数是 True,返回 False;如果参数是 False,返回 True。", or: "如果至少有一个参数是 True,返回 True。否则返回 False。", @@ -1772,6 +1861,8 @@ "eval/cc.1": "此图案和$(l:patterns/meta#hexcasting:eval)$(action)赫尔墨斯之策略/$类似,也能运行图案或依次运行图案列表,但会在开始之前将一独特的“跳转” iota 压入栈中。", "eval/cc.2": "当执行至“跳转” iota 时,法术会直接跳过图案列表中剩余的图案至列表尾部。$(p)当然因为已经有了$(l:patterns/meta#hexcasting:halt)$(action)卡戎之策略/$,这种性质难免显得有些冗余。不过它能以可控方式退出$(italic)多层嵌套/$调用的$(l:patterns/meta#hexcasting:eval)$(action)赫尔墨斯之策略/$,而卡戎之策略只能退出一层。$(p)“跳转” iota 甚至会在所有策略都执行完毕后还留在栈上……真是细思恐极。", + + "thanatos.1": "将$(hex)咒术/$仍能运行的图案数压入列表。每运行一个图案,所得数即减少 1。" }, circle_patterns: { @@ -1812,14 +1903,14 @@ blockworks: { place_block: "移除一个位置向量,然后挑选一个方块并放在给定位置。$(br)消耗大约 1/8 个$(l:items/amethyst)$(item)紫水晶粉/$。", - break_block: "移除一个位置向量,然后破坏给定位置的方块。此法术能破坏几乎所有钻石镐能破坏的方块。$(br)消耗大约 1/8 个$(l:items/amethyst)$(item)紫水晶粉/$。", + break_block: "移除一个位置向量,然后破坏给定位置的方块。此法术能破坏几乎所有钻石镐能破坏的方块。$(br)消耗大约 1/8 个$(l:items/amethyst)$(item)紫水晶粉/$。破坏$(l:patterns/spells/blockworks#hexcasting:conjure_block)$(action)构筑的方块/$或$(l:patterns/spells/blockworks#hexcasting:conjure_light)$(action)构筑的光源/$时仅消耗极少量媒质。", create_water: "在给定位置生成一格水(或给流体容器注入至多一桶水)。消耗大约 1 个$(l:items/amethyst)$(item)紫水晶粉/$。", destroy_water: "清空给定位置的流体容器,或是清除给定位置周围的液体。消耗大约 2 个$(l:items/amethyst)$(item)充能紫水晶/$。", conjure_block: "在给定位置构筑一个空灵缥缈却坚硬可触的,闪着光的方块。消耗大约 1 个$(l:items/amethyst)$(item)紫水晶粉/$。", conjure_light: "在给定位置构筑一个发着我染色剂颜色的光的魔法光源。消耗大约 1 个$(l:items/amethyst)$(item)紫水晶粉/$。", bonemeal: "使目标位置的植物或树苗更快成长,就像对其施以$(item)骨粉/$一样。消耗略多于 1 个$(l:items/amethyst)$(item)紫水晶粉/$。", edify: "将$(media)媒质/$强制注入目标位置的树苗,使其长为$(l:items/edified)$(thing)启迪树/$。消耗大约 1 个$(l:items/amethyst)$(item)充能紫水晶/$。", - ignite: "在给定位置上方生火,就像在该位置使用$(item)火焰弹/$一样。消耗大约 1 个$(l:items/amethyst)$(item)紫水晶粉/$。", + ignite: "在给定位置上方生火,或点燃给定生物,就像对其使用$(item)火焰弹/$一样。消耗大约 1 个$(l:items/amethyst)$(item)紫水晶粉/$。", extinguish: "熄灭周围较大区域内的火焰。消耗大约 6 个$(l:items/amethyst)$(item)紫水晶粉/$。", }, @@ -1859,12 +1950,14 @@ colorize: "我需要在施法时在另一只手中持有$(l:items/pigments)$(item)染色剂/$。施法后,染色剂将被消耗而我意识的颜色也将永久改变(至少是在再次施法前)。消耗大约 1 个$(l:items/amethyst)$(item)紫水晶粉/$。", + cycle_variant: "我制造的一些物品极易受$(media)媒质/$的影响。另一只手持有$(l:items/hexcasting)$(item)杂件/$、$(l:items/hexcasting)$(item)缀品/$、$(l:items/hexcasting)$(item)造物/$、$(l:items/focus)$(item)核心/$、$(l:items/spellbook)$(item)法术书/$时,可以使用此法术更改它们的外表。消耗大约 1 个$(l:items/amethyst)$(item)紫水晶粉/$。", + flights: { "1": "尽管无法掌握自由飞行的力量,我还是找到了若干能将物体滞空的方法,但各种方法各有缺陷。$(br2)所有种类的飞行法术都会产生微量多余$(media)媒质/$。在法术效果将要结束时,其会产生越来越多红色和黑色的火花。", "2": "当然,也有其他种类的飞行法术。例如,将$(l:patterns/spells/basic#hexcasting:add_motion)$(action)驱动/$和$(l:patterns/spells/nadirs#hexcasting:potion/levitation)$(action)蓝阳西沉/$结合使用的类飞行技术自古代起就多有运用。$(br2)我还听说过一种能给予滑翔能力的,可以穿在背上的薄透翼膜。研究表明,被称为“$(l:patterns/great_spells/altiora)$(action)翱翔/$”的卓越法术也许能模仿这种翼膜的功用。", "range.1": "受范围限制的飞行法术。", - "range.2": "第二参数代表水平方向上的半径(以米计),在此范围内,法术能保持稳定。走出该范围就会结束该法术,滞空的物体会直接坠向地面。但只要一直呆在这个范围内,法术的效果便会无限持续。此法术还会额外产生微量$(media)媒质/$用以标记安全区域中心点。$(br2)每米安全范围消耗大约 1 个$(l:items/amethyst)$(item)紫水晶粉/$。", + "range.2": "第二参数代表水平方向上的半径(以米计),在此范围内,法术能保持稳定。走出该范围就会结束该法术,滞空的物体会直接坠向地面。但只要一直待在这个范围内,法术的效果便会无限持续。此法术还会额外产生微量$(media)媒质/$用以标记安全区域中心点。$(br2)每米安全范围消耗大约 1 个$(l:items/amethyst)$(item)紫水晶粉/$。", "time.1": "受时间限制的飞行法术。", "time.2": "第二参数代表持续时间(以秒计),在此限制内,法术能保持稳定。持续时间超过限制就会结束该法术,滞空的物体会直接坠向地面。$(br2)此法术相对较昂贵,每秒持续时间消耗大约 1 个$(l:items/amethyst)$(item)充能紫水晶/$。我觉得它极其适合长途旅行。", @@ -1888,7 +1981,7 @@ "teleport/great": { "1": "比$(l:patterns/spells/basic#hexcasting:blink)$(action)闪现/$更为强大,此法术能让我传送到世界上几乎任何一处!当然它也有极限,但可比我熟悉的施法距离要远得$(italic)多/$。", - "2": "实体会按所给向量偏移出其原有位置。它似乎一直会消耗大约 10 个$(l:items/amethyst)$(item)充能紫水晶/$,不论传送距离。$(br2)当然这种传送也不是尽善尽美的,在传送如玩家般复杂的实体时,实体身上的物品就不会$(italic)非常/$安稳了,它们可能会散落在目的地周围。还有,传送的实体将会被强制从其所乘坐的无生命载具中卸下……但我曾读到过动物可以一起被传送,大概吧。", + "2": "实体会被移动至所给向量处。奇怪的是,这个向量似乎代表了偏差值,而非世界中的绝对位置;比如,使用$(l:patterns/consts#hexcasting:const/vec/x)$(action)向量之精思,+X 型/$时,目标实体会被传送到原有位置恰好 1 格以东处。它似乎一直会消耗大约 10 个$(l:items/amethyst)$(item)充能紫水晶/$,不论传送距离。$(br2)当然这种传送也不是尽善尽美的,在传送如玩家般复杂的实体时,实体身上的物品就不会$(italic)非常/$安稳了,它们可能会散落在目的地周围。还有,传送的实体将会被强制从其所乘坐的无生命载具中卸下……但我曾读到过动物可以一起被传送,大概吧。", }, zeniths: { @@ -1941,7 +2034,7 @@ "2": "更奇怪的是,不管大图书馆的学生怎么激那帮冗杂官僚里管行政的人员,送去的信件都还被一一打回。连其他教授都不愿谈及他。$(br2)也正如你可能在担心的,阿曼妮塔十分沮丧。无论大图书馆派来哪位教授接任,都不如原先的教授那样善解人意和乐于助人。", "3": "但是,这还不是最怪的那件事。这件事——我很希望之后不要再碰到比这还骇人的事了——发生在我和地质学联合会的又一次考察中,我们计划前去某个村庄附近。", "4": "通常去居民点附近考察时,我们要与村庄的村长或长老进行长时间的磋商,以确定我们的行为是被允许的,并划定我们能去的地方和能做的事。但这次不一样,这次没有磋商。出发前两天我们才被告知这次要跟着咒法学联合会的一位学长去。", - "5": "我们在村庄附近的密林中扎营,不知道为什么不选附近的平原。支帐篷的地方几乎看不到村庄。在刚到那天的晚上,我铺开睡袋时,周围是死一般的寂静。就算我们看不见村庄,我们也应该能听到村庄里的声音。但是在地表呆着的整段时间里,我却几乎什么都听不见。", + "5": "我们在村庄附近的密林中扎营,不知道为什么不选附近的平原。支帐篷的地方几乎看不到村庄。在刚到那天的晚上,我铺开睡袋时,周围是死一般的寂静。就算我们看不见村庄,我们也应该能听到村庄里的声音。但是在地表待着的整段时间里,我却几乎什么都听不见。", "6": "我听到的为数不多的声音都像是劳作时发出的那种,比如锤子砸到铁砧上的响声和锄头翻土的噪声。我完全没听到说话声。$(br2)第二天早晨我们就备好灯笼进入了地下。", "7": "我们没被告知到底要找什么,但有学生听说我们是来找紫水晶的,这貌似说得通。我早已练出能在矿洞壁上瞄中任何一小点紫色闪光的绝技,但就在灰色岩石和黑色板岩的交界处,我面前却赫然出现了一座奇观。$(br2)那是一整个紫水晶晶洞,快有十个我那么高,并随着灯光闪着紫色光芒,洞壁的每一面都被尖锐的紫色水晶覆盖。那些紫水晶比我来大图书馆之后整个探险组挖到的所有加起来还多。", "8": "我们每人被发了一副手套并被告知加紧开采。和我们同行的一位学长拿出了一个奇特的淡紫色盒子,高层人员会拿这种盒子来装东西,我和其他学生则兢兢业业地把那些玻璃质水晶从墙上砸下,然后放到盒子里去。外层脆性水晶的基座后方似乎有两种更为致密的构造。一种和外层水晶类似,但另一种更为……我词穷了。", @@ -1980,7 +2073,7 @@ "5": "他们觉得他们把我摧毁得能用毛毡盖眼哄骗我但我是清醒的清醒到能感到痛苦$(br2)我不睡觉但我醒来时我不敢把硬壳从我眼睛上搓掉不然会割破皮而且我不想看见紫色闪光", "6": "他们没杀我,因为我丈夫有我的核心,我死了他会知道。但他不是咒术师所以他没法凭他自己找到我。我走投无$(br2)思考很甬苦。真的很痛苦。思维是累赘累赘印在无数细小水晶上", "7": "我记得那个明亮房间里的医生强迫我吸入某种类似沙子的东西,但更锐利还非常痛。一开始只是黏膜试图吸住玻璃渣的物理性创伤但之后他们把指甲插到我的刺激反应里他们说几句话就能做到$(br2)我记得去露营时看到联合会成员围着一个村庄铺设法术环然后地面就开始震动", - "8": "没了时间观念。有时我觉得我能看到未来,因为那些场景好像说得通但现在不可能发生因为我知道我余生就呆在这了因为亮房里的人这么说了。我能看到我整个人倒了过来我的颅骨裂成两半里面都是长矛样的淌着血的不是紫水晶的东西扎穿一块满是皱纹还幻想自己是蝴蝶的三磅重的脂肪和肉", + "8": "没了时间观念。有时我觉得我能看到未来,因为那些场景好像说得通但现在不可能发生因为我知道我余生就待在这了因为亮房里的人这么说了。我能看到我整个人倒了过来我的颅骨裂成两半里面都是长矛样的淌着血的不是紫水晶的东西扎穿一块满是皱纹还幻想自己是蝴蝶的三磅重的脂肪和肉", "9": "我希望我的学生们还好。我为什么会这么想?累赘。他们告诉我我是累赘,他们不满足于摧毁我,他们还想让我觉得这是我应得的。不用棍棒摧毁身体,而以言语击垮精神。就算他们把我放出去也没人会信我因为我看起来就像一个沉溺于过度施法的瘾君子$(br2)但他们还是把我琐着我不知道这算不算仁慈", "10": "周围这么多媒质我试过好多次施法逃出去或是至少减轻痛苦但那些扫过我的意识的图案在我试着绘制时不停窃笑溶解。我似乎记得我被迫忘记它们,我记得卓伟的互相连接的知识体系被凿空并在刻意忽视的重压下碎裂但回忆起忘记你曾记得你学过的东西再痛苦不过", "11": "也许我已在过度施法依赖症的晚期中的晚期了,我听见图案裁进我眼睛和眼睑间的空间,我神经的紫色边缘。强迫自己相信什么是真实的和我没在受折磨有意义吗。我该受折磨。如果我再也不能和其他人谈这件事为什么还要尝试呢", @@ -2012,7 +2105,7 @@ "3": "最后,如果对剧情和故事感兴趣,就应理解试验这些协同力量时所做的笔记只会是些琐事。", gravity: { - "1": "我发现了获取与改变实体所受引力的操作。挺有趣,也挺让人头晕。$(br2)有趣的是,虽然$(l:patterns/great_spells/flight)$(action)飞行/$是一种卓越法术,且也能操控引力,但这些法术似乎不是这样操控的。我不能理解……也许模组制作者们只是想让玩家们玩得开心。", + "1": "我发现了获取与改变实体所受引力的操作。挺有趣,也挺让人头晕。$(br2)有趣的是,虽然$(l:patterns/spells/flight)$(action)飞行/$的范围和持续时间有限,且也能操控引力,但这些法术的操控方式似乎有所区别。我不能理解……也许模组制作者们只是想让玩家们玩得开心。", get: "获取所给实体的所受引力的主方向上的单位向量。对大多数实体而言,此向量会是向下的:<0, -1, 0>。", set: "设置所给实体所受引力的主方向。所给向量会被转换为与其夹角最小的轴向单位向量,如同$(l:patterns/math#hexcasting:coerce_axial)$(action)轴向之纯化/$那样。消耗大约 1 个$(l:items/amethyst)$(item)充能紫水晶/$。", }, diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/categories/creative_unlocker.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/categories/creative_unlocker.json new file mode 100644 index 0000000000..bfb372c2c2 --- /dev/null +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/categories/creative_unlocker.json @@ -0,0 +1,7 @@ +{ + "name": "hexcasting.category.creative_unlocker", + "icon": "hexcasting:creative_unlocker", + "description": "hexcasting.category.creative_unlocker.desc", + "sortnum": 99, + "secret": true +} \ No newline at end of file diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/creative_unlocker/media_cube.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/creative_unlocker/media_cube.json new file mode 100644 index 0000000000..fc8e245733 --- /dev/null +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/creative_unlocker/media_cube.json @@ -0,0 +1,22 @@ +{ + "name": "hexcasting.entry.media_cube", + "icon": "hexcasting:creative_unlocker", + "category": "hexcasting:creative_unlocker", + "sortnum": 0, + "priority": true, + "advancement": "hexcasting:creative_unlocker", + "pages": [ + { + "type": "patchouli:text", + "text": "hexcasting.page.creative_unlocker.1" + }, + { + "type": "patchouli:text", + "text": "hexcasting.page.creative_unlocker.2" + }, + { + "type": "patchouli:text", + "text": "hexcasting.page.creative_unlocker.3" + } + ] +} \ No newline at end of file diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/directrix.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/directrix.json index 7d00dd56fc..1f9b909a2c 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/directrix.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/greatwork/directrix.json @@ -23,6 +23,11 @@ "type": "hexcasting:brainsweep", "recipe": "hexcasting:brainsweep/directrix_redstone", "text": "hexcasting.page.directrix.directrix_redstone" + }, + { + "type": "hexcasting:brainsweep", + "recipe": "hexcasting:brainsweep/directrix_boolean", + "text": "hexcasting.page.directrix.directrix_boolean" } ] } diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/items/phials.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/items/phials.json index f0100e8432..0f930d1ec8 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/items/phials.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/items/phials.json @@ -1,7 +1,7 @@ { "name": "hexcasting.entry.phials", "category": "hexcasting:items", - "icon": "hexcasting:battery{media:10000,max_media:10000}", + "icon": "hexcasting:battery", "sortnum": 7, "advancement": "hexcasting:root", "read_by_default": true, @@ -21,7 +21,7 @@ { "type": "patchouli:spotlight", "text": "hexcasting.page.phials.desc", - "item": "hexcasting:battery{media:10000,max_media:10000}", + "item": "hexcasting:battery{\"hexcasting:media\":640000,\"hexcasting:start_media\":640000}", "link_recipe": true } ] diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/items/potions.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/items/potions.json new file mode 100644 index 0000000000..a0db5dd70b --- /dev/null +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/items/potions.json @@ -0,0 +1,21 @@ +{ + "name": "hexcasting.entry.potions", + "category": "hexcasting:items", + "icon": "minecraft:potion{Potion:\"hexcasting:enlarge_grid\"}", + "sortnum": 8, + "advancement": "hexcasting:root", + "read_by_default": true, + "pages": [ + { + "type": "patchouli:text", + "text": "hexcasting.page.potions.1" + }, + { + "type": "patchouli:spotlight", + "title": "hexcasting.page.potions.effects.header", + "text": "hexcasting.page.potions.2", + "item": "minecraft:potion{Potion:\"hexcasting:enlarge_grid\"},minecraft:potion{Potion:\"hexcasting:enlarge_grid_long\"},minecraft:potion{Potion:\"hexcasting:enlarge_grid_strong\"},minecraft:potion{Potion:\"hexcasting:shrink_grid\"},minecraft:potion{Potion:\"hexcasting:shrink_grid_long\"},minecraft:potion{Potion:\"hexcasting:shrink_grid_strong\"}" + } + ] + } + \ No newline at end of file diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/blockworks.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/blockworks.json index 7a414d9eaa..f39e871d45 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/blockworks.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/blockworks.json @@ -74,7 +74,7 @@ "type": "hexcasting:pattern", "op_id": "hexcasting:ignite", "anchor": "hexcasting:ignite", - "input": "vector", + "input": "entity | vector", "output": "", "text": "hexcasting.page.blockworks.ignite" }, @@ -87,4 +87,4 @@ "text": "hexcasting.page.blockworks.extinguish" } ] -} +} \ No newline at end of file diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/flight.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/flight.json index f3f74392a6..4a56315415 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/flight.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/flight.json @@ -25,6 +25,15 @@ "output": "", "text": "hexcasting.page.flights.time.1" }, - "hexcasting.page.flights.time.2" + "hexcasting.page.flights.time.2", + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:flight/can_fly", + "anchor": "hexcasting:flight/can_fly", + "input": "entity", + "output": "boolean", + "text": "hexcasting.page.flights.can_fly.1" + }, + "hexcasting.page.flights.can_fly.2" ] } diff --git a/Common/src/main/resources/assets/hexcasting/sounds.json5 b/Common/src/main/resources/assets/hexcasting/sounds.json5 index 94b51cc8ee..bd0d4538a0 100644 --- a/Common/src/main/resources/assets/hexcasting/sounds.json5 +++ b/Common/src/main/resources/assets/hexcasting/sounds.json5 @@ -132,6 +132,12 @@ ], "subtitle": "hexcasting.subtitles.impetus.redstone.register" }, + "impetus.redstone.clear": { + "sounds": [ + "hexcasting:cast/fail" + ], + "subtitle": "hexcasting.subtitles.impetus.redstone.clear" + }, "lore_fragment.read": { "sounds": [ diff --git a/Common/src/main/resources/assets/hexcasting/textures/mob_effect/enlarge_grid.png b/Common/src/main/resources/assets/hexcasting/textures/mob_effect/enlarge_grid.png index 58a621a495..032d89263b 100644 Binary files a/Common/src/main/resources/assets/hexcasting/textures/mob_effect/enlarge_grid.png and b/Common/src/main/resources/assets/hexcasting/textures/mob_effect/enlarge_grid.png differ diff --git a/Common/src/main/resources/assets/hexcasting/textures/mob_effect/shrink_grid.png b/Common/src/main/resources/assets/hexcasting/textures/mob_effect/shrink_grid.png index cf592ff52f..21cc17bb2e 100644 Binary files a/Common/src/main/resources/assets/hexcasting/textures/mob_effect/shrink_grid.png and b/Common/src/main/resources/assets/hexcasting/textures/mob_effect/shrink_grid.png differ diff --git a/Fabric/build.gradle b/Fabric/build.gradle index aafe9a125d..9e60ab70d3 100644 --- a/Fabric/build.gradle +++ b/Fabric/build.gradle @@ -84,6 +84,8 @@ dependencies { modImplementation "at.petra-k.paucal:paucal-fabric-$minecraftVersion:$paucalVersion" modImplementation "vazkii.patchouli:Patchouli:$minecraftVersion-$patchouliVersion-FABRIC-SNAPSHOT" + modImplementation "com.samsthenerd.inline:inline-fabric:$minecraftVersion-$inlineVersion" + modImplementation "dev.onyxstudios.cardinal-components-api:cardinal-components-base:$cardinalComponentsVersion" // modImplementation "dev.onyxstudios.cardinal-components-api:cardinal-components-util:$cardinalComponentsVersion" modImplementation "dev.onyxstudios.cardinal-components-api:cardinal-components-entity:$cardinalComponentsVersion" diff --git a/Fabric/gradle.properties b/Fabric/gradle.properties index 403ee97acd..912673b4a7 100644 --- a/Fabric/gradle.properties +++ b/Fabric/gradle.properties @@ -11,7 +11,6 @@ emiVersion=1.0.4+1.20.1 #gravityApiVersion=1.0.6 trinketsVersion=3.7.0 -clothConfigVersion=11.1.106 modmenuVersion=7.0.1 # Optimizations diff --git a/Fabric/src/generated/resources/.cache/19f2b40f78e342d65a8cb499a41e3fcb2eadaca3 b/Fabric/src/generated/resources/.cache/19f2b40f78e342d65a8cb499a41e3fcb2eadaca3 index e5e85cf919..6b6009aeea 100644 --- a/Fabric/src/generated/resources/.cache/19f2b40f78e342d65a8cb499a41e3fcb2eadaca3 +++ b/Fabric/src/generated/resources/.cache/19f2b40f78e342d65a8cb499a41e3fcb2eadaca3 @@ -1,4 +1,4 @@ -// 1.20.1 2024-08-27T12:42:38.112410929 Hex Casting/Loot Tables +// 1.20.1 2024-11-19T11:30:28.198417333 Hex Casting/Loot Tables 1dd4268edf7d6fa247013ab45541c7bfb915eef8 data/hexcasting/loot_tables/blocks/amethyst_bricks_small.json 601384d888edab27efe4a33027bb557eb7cb6ca2 data/hexcasting/loot_tables/blocks/edified_log_purple.json 2c9af74a82ca462e5986354966d5a0a1fd5a2083 data/hexcasting/loot_tables/blocks/slate_tiles.json diff --git a/Fabric/src/generated/resources/.cache/2ba8da2cf2d44ff18dc72cc891b094eca6836a5c b/Fabric/src/generated/resources/.cache/2ba8da2cf2d44ff18dc72cc891b094eca6836a5c index d7f56c26ff..262e081e97 100644 --- a/Fabric/src/generated/resources/.cache/2ba8da2cf2d44ff18dc72cc891b094eca6836a5c +++ b/Fabric/src/generated/resources/.cache/2ba8da2cf2d44ff18dc72cc891b094eca6836a5c @@ -1,4 +1,4 @@ -// 1.20.1 2024-08-27T12:42:38.116625477 Hex Casting/Tags for minecraft:item +// 1.20.1 2024-11-19T11:30:28.203782887 Hex Casting/Tags for minecraft:item 5928bad07d3872bb60f29ef4f3c885c8e1967c20 data/hexcasting/tags/items/phial_base.json fdb48f194d7937ab6b423fa4b90a4d438bf6dd90 data/minecraft/tags/items/wooden_doors.json fdb48f194d7937ab6b423fa4b90a4d438bf6dd90 data/minecraft/tags/items/doors.json @@ -17,7 +17,7 @@ e5df19a1dc6eadf14cd9b0f0fe45a74330b745e9 data/hexcasting/tags/items/edified_plan 20183cd61968ff6548df2dde1100b6378d68d64b data/minecraft/tags/items/buttons.json 38d781b60c5c37dc025d4c7e9ec5aa680f2a5835 data/c/tags/items/gems.json c562b4be24d0b6b13f3c65599d3bfa3bf2c4ce21 data/minecraft/tags/items/logs_that_burn.json -24145229528668829a1bcecf18a6377ebd07ccf8 data/hexcasting/tags/items/grants_root_advancement.json +ef8ae066fea6277ba2ab43faf18757b88f7c4803 data/hexcasting/tags/items/grants_root_advancement.json 5f3b600b4fd98744bd08c993ce7bcb9c2f195cd2 data/minecraft/tags/items/leaves.json 9d18fb7a889031a704ca0e553600e1d6f8c3759d data/hexcasting/tags/items/directrices.json c562b4be24d0b6b13f3c65599d3bfa3bf2c4ce21 data/minecraft/tags/items/logs.json diff --git a/Fabric/src/generated/resources/.cache/3cb4ab563deee432e7d307024048f57946bafb1c b/Fabric/src/generated/resources/.cache/3cb4ab563deee432e7d307024048f57946bafb1c index 1e7f2dc25b..0177cee9a1 100644 --- a/Fabric/src/generated/resources/.cache/3cb4ab563deee432e7d307024048f57946bafb1c +++ b/Fabric/src/generated/resources/.cache/3cb4ab563deee432e7d307024048f57946bafb1c @@ -1,4 +1,4 @@ -// 1.20.1 2024-08-27T12:42:38.120411259 Hex Casting/Tags for hexcasting:action +// 1.20.1 2024-11-19T11:30:28.208319781 Hex Casting/Tags for hexcasting:action 6fe30f41e2bcd48589caab26d210a513dce1ab7c data/hexcasting/tags/action/per_world_pattern.json 6fe30f41e2bcd48589caab26d210a513dce1ab7c data/hexcasting/tags/action/requires_enlightenment.json 6fe30f41e2bcd48589caab26d210a513dce1ab7c data/hexcasting/tags/action/can_start_enlighten.json diff --git a/Fabric/src/generated/resources/.cache/812fdb58b7018b2d5c5af7da57a2b1857fa66794 b/Fabric/src/generated/resources/.cache/812fdb58b7018b2d5c5af7da57a2b1857fa66794 index e7b75bcd42..82cfeba7fa 100644 --- a/Fabric/src/generated/resources/.cache/812fdb58b7018b2d5c5af7da57a2b1857fa66794 +++ b/Fabric/src/generated/resources/.cache/812fdb58b7018b2d5c5af7da57a2b1857fa66794 @@ -1,4 +1,4 @@ -// 1.20.1 2024-08-27T12:42:38.115300691 Hex Casting/Tags for minecraft:block +// 1.20.1 2024-11-19T11:30:28.20228007 Hex Casting/Tags for minecraft:block c72a147bc65d26424df199388969ebd11119aed3 data/hexcasting/tags/blocks/brainswept_circle_components.json 357eddf3cee6f16725bed0701d57b2ca3097d74d data/minecraft/tags/blocks/mineable/shovel.json 20183cd61968ff6548df2dde1100b6378d68d64b data/minecraft/tags/blocks/buttons.json @@ -24,6 +24,7 @@ c562b4be24d0b6b13f3c65599d3bfa3bf2c4ce21 data/minecraft/tags/blocks/logs.json c562b4be24d0b6b13f3c65599d3bfa3bf2c4ce21 data/hexcasting/tags/blocks/edified_logs.json 5bbfd513fd2eb2090b0c2d1ec33504deb79d53b9 data/minecraft/tags/blocks/slabs.json e5df19a1dc6eadf14cd9b0f0fe45a74330b745e9 data/minecraft/tags/blocks/planks.json +4b84fc8b7976df220be382bdda66ecf25ceee559 data/create/tags/blocks/brittle.json 281cb08b9b68ef049820c4f3f36b40820044681e data/minecraft/tags/blocks/stairs.json bdb90cee0e88e02f0b98f12d5dd212adfaca9afd data/hexcasting/tags/blocks/impeti.json 9d18fb7a889031a704ca0e553600e1d6f8c3759d data/hexcasting/tags/blocks/directrices.json diff --git a/Fabric/src/generated/resources/.cache/c70ef2fe5da52437c1f53bcc9ea0e416f16bcc0b b/Fabric/src/generated/resources/.cache/c70ef2fe5da52437c1f53bcc9ea0e416f16bcc0b index 1a07bbdc44..f29d046638 100644 --- a/Fabric/src/generated/resources/.cache/c70ef2fe5da52437c1f53bcc9ea0e416f16bcc0b +++ b/Fabric/src/generated/resources/.cache/c70ef2fe5da52437c1f53bcc9ea0e416f16bcc0b @@ -1,4 +1,4 @@ -// 1.20.1 2024-08-27T12:42:38.11755991 Hex Casting/Recipes +// 1.20.1 2024-11-19T11:30:28.205075318 Hex Casting/Recipes f482a4349786388cc8f11d5550548f7d60265438 data/hexcasting/recipes/staff/mangrove.json b10d590e918e35b16578a8b739a1c4e7e2202e16 data/hexcasting/advancements/recipes/misc/dye_colorizer_cyan.json 7aa3bf4a3d6fb92743b29dfe89d50537fefc0db9 data/hexcasting/advancements/recipes/misc/pride_colorizer_intersex.json @@ -28,6 +28,7 @@ c3f7b03fe184ed5e54a8ae06d130adf507b7683d data/hexcasting/recipes/staff/bamboo.js fd2f25b0a71806c96af5a307fad76f66de6210a4 data/hexcasting/advancements/recipes/building_blocks/slate_block.json b1f8375aaf0d66035dee720ea59605f69fc0a154 data/hexcasting/recipes/edified_fence.json ae88fcdecbfbdd0a0fe778467421a3b32d7ed735 data/create/recipes/crushing/amethyst_cluster.json +6ed61e03c51dc653cd66e643a6d33fe9105ff171 data/hexcasting/advancements/recipes/brainsweep/brainsweep/directrix_boolean.json 8b7136c206b799a2e394aa02316b0509674ff64f data/hexcasting/advancements/recipes/tools/staff/bamboo.json fb852d8e4bcfa7b75f41a6ac7dc1e76b00d95fb1 data/hexcasting/advancements/recipes/misc/dye_colorizer_red.json 5401828f85e709b5817ecc8464dc63e536a730dc data/hexcasting/recipes/staff/cherry.json @@ -119,8 +120,8 @@ c2ef04b311251b4eb22320b2f5313c54533a9974 data/hexcasting/advancements/recipes/to 8f8773a541bc6a4a6c55a23f4f98b5da4f61a031 data/hexcasting/recipes/scroll_paper.json bc729ac7cf84d29a99cd34d50c152c0b9d20bd7a data/hexcasting/advancements/recipes/brainsweep/brainsweep/akashic_record.json 4a803e049915fd3c7144155ae3a1b05a917ea290 data/hexcasting/recipes/pride_colorizer_pansexual.json -0654e70ed1ed8be20ae3dd9f4955cd14f9fa40d0 data/hexcasting/advancements/recipes/tools/staff/jungle.json 8bea75fdc5e64c464dcf5f85166e767ff44e6dc2 data/hexcasting/advancements/recipes/misc/pride_colorizer_lesbian.json +0654e70ed1ed8be20ae3dd9f4955cd14f9fa40d0 data/hexcasting/advancements/recipes/tools/staff/jungle.json 5e98cec2084f0cfbb959c3ec39bd85a3369f443b data/hexcasting/advancements/recipes/tools/abacus.json 0b172aef920da7ba63fe152903ce005c1f5df5f1 data/hexcasting/recipes/staff/acacia.json 7e1a5a873d655e0efba80f22ae9b1de4f248e67a data/hexcasting/advancements/recipes/misc/decompose_quenched_shard/shard.json @@ -137,6 +138,7 @@ eb9ebf77f0daa32f665a60888fcda19c940f0b2f data/hexcasting/advancements/recipes/mi 7522be58b09554a3f1a54d5b2343c3eab01447a3 data/hexcasting/recipes/dye_colorizer_magenta.json ee5db13cbb33d9c62bcb1eb645e2c4bea97ad44a data/hexcasting/advancements/recipes/building_blocks/amethyst_dust_unpacking.json 203b7035125390abb4ed77b3a4dca8f8f8f57bc5 data/hexcasting/recipes/dye_colorizer_light_gray.json +00853ec1885c1f72674c07caf6fd04904e248f8f data/hexcasting/recipes/brainsweep/directrix_boolean.json f8ee073c1c03f1c11147e4801eeba1f86e5459ba data/hexcasting/recipes/dye_colorizer_blue.json a366ea3750adc0d336ab8f318c40baed3f9c3eb7 data/hexcasting/recipes/brainsweep/impetus_storedplayer.json 51b047601368a103be166d907633b196d2d8a4e8 data/hexcasting/recipes/compat/farmersdelight/cutting/edified_log.json diff --git a/Fabric/src/generated/resources/data/create/tags/blocks/brittle.json b/Fabric/src/generated/resources/data/create/tags/blocks/brittle.json new file mode 100644 index 0000000000..b9a7d8674d --- /dev/null +++ b/Fabric/src/generated/resources/data/create/tags/blocks/brittle.json @@ -0,0 +1,9 @@ +{ + "replace": false, + "values": [ + { + "id": "#hexcasting:slate", + "required": false + } + ] +} \ No newline at end of file diff --git a/Fabric/src/generated/resources/data/hexcasting/advancements/recipes/brainsweep/brainsweep/directrix_boolean.json b/Fabric/src/generated/resources/data/hexcasting/advancements/recipes/brainsweep/brainsweep/directrix_boolean.json new file mode 100644 index 0000000000..bc173bd32b --- /dev/null +++ b/Fabric/src/generated/resources/data/hexcasting/advancements/recipes/brainsweep/brainsweep/directrix_boolean.json @@ -0,0 +1,35 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "enlightenment": { + "conditions": { + "health_used": { + "min": 0.8 + }, + "mojang_i_am_begging_and_crying_please_add_an_entity_health_criterion": { + "max": 1.0, + "min": 2.2250738585072014E-308 + } + }, + "trigger": "hexcasting:overcast" + }, + "has_the_recipe": { + "conditions": { + "recipe": "hexcasting:brainsweep/directrix_boolean" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "enlightenment", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "hexcasting:brainsweep/directrix_boolean" + ] + }, + "sends_telemetry_event": true +} \ No newline at end of file diff --git a/Fabric/src/generated/resources/data/hexcasting/recipes/brainsweep/directrix_boolean.json b/Fabric/src/generated/resources/data/hexcasting/recipes/brainsweep/directrix_boolean.json new file mode 100644 index 0000000000..fd1c128f51 --- /dev/null +++ b/Fabric/src/generated/resources/data/hexcasting/recipes/brainsweep/directrix_boolean.json @@ -0,0 +1,21 @@ +{ + "type": "hexcasting:brainsweep", + "blockIn": { + "type": "block", + "block": "hexcasting:directrix/empty" + }, + "cost": 1000000, + "entityIn": { + "type": "villager", + "minLevel": 1, + "profession": "shepherd" + }, + "result": { + "name": "hexcasting:directrix/boolean", + "properties": { + "energized": "false", + "facing": "north", + "state": "neither" + } + } +} \ No newline at end of file diff --git a/Fabric/src/generated/resources/data/hexcasting/tags/items/grants_root_advancement.json b/Fabric/src/generated/resources/data/hexcasting/tags/items/grants_root_advancement.json index afdfee5143..76ab3105e8 100644 --- a/Fabric/src/generated/resources/data/hexcasting/tags/items/grants_root_advancement.json +++ b/Fabric/src/generated/resources/data/hexcasting/tags/items/grants_root_advancement.json @@ -3,6 +3,7 @@ "values": [ "hexcasting:amethyst_dust", "minecraft:amethyst_shard", - "hexcasting:charged_amethyst" + "hexcasting:charged_amethyst", + "hexcasting:creative_unlocker" ] } \ No newline at end of file diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java index 7622e7cf8f..a49ab760f3 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java @@ -18,6 +18,7 @@ import net.minecraft.util.Mth; import net.minecraft.world.level.Level; +import java.util.ArrayList; import java.util.List; import static at.petrak.hexcasting.api.mod.HexConfig.anyMatchResLoc; @@ -132,6 +133,8 @@ public static final class Client implements HexConfig.ClientConfigAccess, Config private boolean invertAbacusScrollDirection = DEFAULT_INVERT_SPELLBOOK_SCROLL; @ConfigEntry.Gui.Tooltip private double gridSnapThreshold = DEFAULT_GRID_SNAP_THRESHOLD; + @ConfigEntry.Gui.Tooltip + private boolean clickingTogglesDrawing = DEFAULT_CLICKING_TOGGLES_DRAWING; @Override public void validatePostLoad() throws ValidationException { @@ -157,6 +160,11 @@ public boolean invertAbacusScrollDirection() { public double gridSnapThreshold() { return gridSnapThreshold; } + + @Override + public boolean clickingTogglesDrawing() { + return clickingTogglesDrawing; + } } @Config(name = "server") @@ -192,7 +200,12 @@ public static final class Server implements HexConfig.ServerConfigAccess, Config // TODO: hook this up to the config, change Jankery, test, also test scroll injects on fabric @ConfigEntry.Gui.Tooltip - private List loreInjections = HexLootHandler.DEFAULT_LORE_INJECTS; + private List loreInjectionsRaw = HexLootHandler.DEFAULT_LORE_INJECTS + .stream() + .map(ResourceLocation::toString) + .toList(); + @ConfigEntry.Gui.Excluded + private transient List loreInjections; @ConfigEntry.Gui.Tooltip private double loreChance = HexLootHandler.DEFAULT_LORE_CHANCE; @@ -215,6 +228,16 @@ public void validatePostLoad() throws ValidationException { throw new ValidationException("Bad parsing of scroll injects", e); } + this.loreInjections = new ArrayList<>(); + try { + for (var table : this.loreInjectionsRaw) { + ResourceLocation loc = new ResourceLocation(table); + this.loreInjections.add(loc); + } + } catch (Exception e) { + throw new ValidationException("Bad parsing of lore injects", e); + } + this.loreChance = Mth.clamp(this.loreChance, 0.0, 1.0); } diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt index e7d3163458..19e676136c 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt @@ -132,7 +132,6 @@ object FabricHexInitializer : ModInitializer { HexAttributes.register(bind(BuiltInRegistries.ATTRIBUTE)) HexMobEffects.register(bind(BuiltInRegistries.MOB_EFFECT)) HexPotions.register(bind(BuiltInRegistries.POTION)) - HexPotions.addRecipes() HexRecipeStuffRegistry.registerSerializers(bind(BuiltInRegistries.RECIPE_SERIALIZER)) HexRecipeStuffRegistry.registerTypes(bind(BuiltInRegistries.RECIPE_TYPE)) diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/emi/PatternRendererEMI.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/emi/PatternRendererEMI.java index 3f27fff2ea..da699de7a9 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/emi/PatternRendererEMI.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/emi/PatternRendererEMI.java @@ -1,19 +1,18 @@ package at.petrak.hexcasting.fabric.interop.emi; -import at.petrak.hexcasting.api.casting.math.HexCoord; +import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.api.mod.HexTags; import at.petrak.hexcasting.api.utils.HexUtils; -import at.petrak.hexcasting.interop.utils.PatternDrawingUtil; -import at.petrak.hexcasting.interop.utils.PatternEntry; +import at.petrak.hexcasting.client.render.PatternColors; +import at.petrak.hexcasting.client.render.PatternRenderer; +import at.petrak.hexcasting.client.render.PatternSettings; +import at.petrak.hexcasting.client.render.PatternSettings.PositionSettings; +import at.petrak.hexcasting.client.render.PatternSettings.StrokeSettings; +import at.petrak.hexcasting.client.render.PatternSettings.ZappySettings; import at.petrak.hexcasting.xplat.IXplatAbstractions; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.datafixers.util.Pair; import dev.emi.emi.api.render.EmiRenderable; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.phys.Vec2; - -import java.util.List; public class PatternRendererEMI implements EmiRenderable { @@ -25,19 +24,21 @@ public class PatternRendererEMI implements EmiRenderable { private boolean strokeOrder; - private final List patterns; - private final List pathfinderDots; + private final HexPattern pat; + private PatternSettings patSets; public PatternRendererEMI(ResourceLocation pattern, int w, int h) { var regi = IXplatAbstractions.INSTANCE.getActionRegistry(); var entry = regi.get(pattern); this.strokeOrder = HexUtils.isOfTag(regi, pattern, HexTags.Actions.PER_WORLD_PATTERN); - var data = PatternDrawingUtil.loadPatterns(List.of(new Pair<>(entry.prototype(), HexCoord.getOrigin())), 0f, - 1f); - this.patterns = data.patterns(); - this.pathfinderDots = data.pathfinderDots(); + this.pat = entry.prototype(); this.width = w; this.height = h; + this.patSets = new PatternSettings("pattern_drawable_" + w + "_" + h, + new PositionSettings(width, height, 0, 0, + PatternSettings.AxisAlignment.CENTER_FIT, PatternSettings.AxisAlignment.CENTER_FIT, Math.max(width, height), 0, 0), + StrokeSettings.fromStroke(0.075 * Math.min(width, height)), + ZappySettings.READABLE); } public PatternRendererEMI shift(int x, int y) { @@ -47,6 +48,13 @@ public PatternRendererEMI shift(int x, int y) { } public PatternRendererEMI strokeOrder(boolean order) { + if(order != strokeOrder){ + patSets = new PatternSettings("pattern_drawable_" + width + "_" + height + (order ? "" : "nostroke"), + patSets.posSets, + patSets.strokeSets, + order ? ZappySettings.READABLE : ZappySettings.STATIC + ); + } strokeOrder = order; return this; } @@ -55,10 +63,11 @@ public PatternRendererEMI strokeOrder(boolean order) { public void render(GuiGraphics graphics, int x, int y, float delta) { var ps = graphics.pose(); ps.pushPose(); - ps.translate(xOffset + x - 0.5f + width / 2f, yOffset + y + 1 + height / 2f, 0); - ps.scale(width / 64f, height / 64f, 1f); - PatternDrawingUtil.drawPattern(graphics, 0, 0, this.patterns, this.pathfinderDots, this.strokeOrder, - 0xff_333030, 0xff_191818, 0xc8_0c0a0c, 0x80_666363); + ps.translate(xOffset + x, yOffset + y + 1, 0); + PatternRenderer.renderPattern(pat, graphics.pose(), patSets, + new PatternColors(0xc8_0c0a0c, 0xff_333030).withDotColors(0x80_666363, 0), + 0, 10 + ); ps.popPose(); } } diff --git a/Fabric/src/main/resources/fabric.mod.json b/Fabric/src/main/resources/fabric.mod.json index 4468af2380..85a8965ace 100644 --- a/Fabric/src/main/resources/fabric.mod.json +++ b/Fabric/src/main/resources/fabric.mod.json @@ -57,7 +57,8 @@ "cardinal-components-block": "~5.2.1", "paucal": ">=0.6.0-pre <0.7.0", "cloth-config": "11.1.*", - "patchouli": ">=1.20.1-80" + "patchouli": ">=1.20.1-80", + "inline": ">=1.20.1-1.0.1" }, "suggests": { "pehkui": ">=3.7.6", diff --git a/Forge/build.gradle b/Forge/build.gradle index c1942d6013..78be55d9aa 100644 --- a/Forge/build.gradle +++ b/Forge/build.gradle @@ -59,6 +59,8 @@ repositories { url = 'https://thedarkcolour.github.io/KotlinForForge/' content { includeGroup "thedarkcolour" } } + + maven { url "https://maven.shedaniel.me/" } } dependencies { @@ -80,6 +82,11 @@ dependencies { implementation fg.deobf("top.theillusivec4.caelus:caelus-forge:$caelusVersion") + implementation fg.deobf("com.samsthenerd.inline:inline-forge:$minecraftVersion-$inlineVersion") + + // needed for inline to run + runtimeOnly fg.deobf("me.shedaniel.cloth:cloth-config-forge:$clothConfigVersion") + // Optional interop compileOnly fg.deobf("mezz.jei:jei-$minecraftVersion-common-api:$jeiVersion") diff --git a/Forge/src/generated/resources/.cache/50b16d832771f8a8317f4d1b88c5121208c8c4f9 b/Forge/src/generated/resources/.cache/50b16d832771f8a8317f4d1b88c5121208c8c4f9 index 590a18fdb1..a8b2618d96 100644 --- a/Forge/src/generated/resources/.cache/50b16d832771f8a8317f4d1b88c5121208c8c4f9 +++ b/Forge/src/generated/resources/.cache/50b16d832771f8a8317f4d1b88c5121208c8c4f9 @@ -1,4 +1,5 @@ -// 1.20.1 2024-08-27T12:44:48.881057546 Tags for minecraft:block mod id vanilla +// 1.20.1 2024-11-19T11:29:21.178009391 Tags for minecraft:block mod id vanilla +ebad584d48ab5cb9c28edcfa78c87f9497f56cd0 data/create/tags/blocks/brittle.json c5aac196bf97183b43d794c6aac8e206f4b71e37 data/hexcasting/tags/blocks/brainswept_circle_components.json 0ac2f0afe13705ae24edcb339a5e1e2cdcfe51cf data/hexcasting/tags/blocks/cheap_to_break_block.json abaec2d0102fef5865ac638cf7c528a4d5b2a69b data/hexcasting/tags/blocks/directrices.json diff --git a/Forge/src/generated/resources/.cache/8c8364f4e83c409ec545b3c2adc7d52ce75bbb78 b/Forge/src/generated/resources/.cache/8c8364f4e83c409ec545b3c2adc7d52ce75bbb78 index e6f171cf36..664e17383b 100644 --- a/Forge/src/generated/resources/.cache/8c8364f4e83c409ec545b3c2adc7d52ce75bbb78 +++ b/Forge/src/generated/resources/.cache/8c8364f4e83c409ec545b3c2adc7d52ce75bbb78 @@ -1,11 +1,11 @@ -// 1.20.1 2023-12-24T17:58:56.8930247 Tags for minecraft:item mod id vanilla +// 1.20.1 2024-10-01T23:18:04.2255382 Tags for minecraft:item mod id vanilla 2f5c5bd21580004c998765215166adaa72e0d96b data/forge/tags/items/dusts/amethyst.json 873bab891e0827973ebfe9fedd352c95778d7fbe data/forge/tags/items/gems.json c5aac196bf97183b43d794c6aac8e206f4b71e37 data/hexcasting/tags/items/brainswept_circle_components.json abaec2d0102fef5865ac638cf7c528a4d5b2a69b data/hexcasting/tags/items/directrices.json 7e27f819889d2f0bca863b1cdb6d7d640ea21986 data/hexcasting/tags/items/edified_logs.json 86828f8056bfdfdd2aff10d7a9dbc6c269c25b8a data/hexcasting/tags/items/edified_planks.json -03bd3a58ae7367d6a21eb3acbbd70fe65a41b8f6 data/hexcasting/tags/items/grants_root_advancement.json +5b19052261719f95e2faedcb483d4c338e349524 data/hexcasting/tags/items/grants_root_advancement.json 7f71f33b0bc9fde24deef080ab64707df38adfd6 data/hexcasting/tags/items/impeti.json 7f7d5f6cc9b5cfb39d240fc901c4ae2d2037ec14 data/hexcasting/tags/items/phial_base.json d878c230cd5cb1b54fbe6e53f3ef71379d080c6f data/hexcasting/tags/items/seal_materials.json diff --git a/Forge/src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e b/Forge/src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e index 4750343789..82410eb0e6 100644 --- a/Forge/src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e +++ b/Forge/src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e @@ -1,8 +1,9 @@ -// 1.20.1 2024-06-13T19:17:41.8621305 Recipes +// 1.20.1 2024-11-02T15:23:10.977029966 Recipes 29056d8bbc023668564ee81f4eb8b2a11411cf4a data/create/recipes/crushing/amethyst_block.json 5bb0b70967a422bfd88c01fa1af64e9b98781fd1 data/create/recipes/crushing/amethyst_cluster.json bc729ac7cf84d29a99cd34d50c152c0b9d20bd7a data/hexcasting/advancements/recipes/brainsweep/brainsweep/akashic_record.json 36d26f34d0405ff2d1e728e5b5174502686e3590 data/hexcasting/advancements/recipes/brainsweep/brainsweep/budding_amethyst.json +6ed61e03c51dc653cd66e643a6d33fe9105ff171 data/hexcasting/advancements/recipes/brainsweep/brainsweep/directrix_boolean.json 23eff6111b0385b66d3ad5fbabfc625f426517a6 data/hexcasting/advancements/recipes/brainsweep/brainsweep/directrix_redstone.json 30352d8ad510768770bb1b2d378959b7a502f825 data/hexcasting/advancements/recipes/brainsweep/brainsweep/impetus_look.json 2003fed3aa4eb622b6b07a9e65946fb40be14420 data/hexcasting/advancements/recipes/brainsweep/brainsweep/impetus_rightclick.json @@ -116,6 +117,7 @@ c2a1ba123e94a114489b31063b17f7a0ec71678a data/hexcasting/recipes/ancient_scroll_ 482cc54e3ced75e57d265c855835fac91ea4ee1b data/hexcasting/recipes/artifact.json e0609202271e402d8ae58e4f8eaf11dcdda10a9e data/hexcasting/recipes/brainsweep/akashic_record.json 5a90084c03d6e8424872870c8b65f4771b447f03 data/hexcasting/recipes/brainsweep/budding_amethyst.json +00853ec1885c1f72674c07caf6fd04904e248f8f data/hexcasting/recipes/brainsweep/directrix_boolean.json b16ff5314d457bc7e9e224e102d1e04ce3a62361 data/hexcasting/recipes/brainsweep/directrix_redstone.json 92bdf87687d8823036fae6bd01782c653831286b data/hexcasting/recipes/brainsweep/impetus_look.json ed5c690324e3d9b55599f00f078ae225072a2e7f data/hexcasting/recipes/brainsweep/impetus_rightclick.json diff --git a/Forge/src/generated/resources/data/create/tags/blocks/brittle.json b/Forge/src/generated/resources/data/create/tags/blocks/brittle.json new file mode 100644 index 0000000000..fb66be6093 --- /dev/null +++ b/Forge/src/generated/resources/data/create/tags/blocks/brittle.json @@ -0,0 +1,8 @@ +{ + "values": [ + { + "id": "#hexcasting:slate", + "required": false + } + ] +} \ No newline at end of file diff --git a/Forge/src/generated/resources/data/hexcasting/advancements/recipes/brainsweep/brainsweep/directrix_boolean.json b/Forge/src/generated/resources/data/hexcasting/advancements/recipes/brainsweep/brainsweep/directrix_boolean.json new file mode 100644 index 0000000000..bc173bd32b --- /dev/null +++ b/Forge/src/generated/resources/data/hexcasting/advancements/recipes/brainsweep/brainsweep/directrix_boolean.json @@ -0,0 +1,35 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "enlightenment": { + "conditions": { + "health_used": { + "min": 0.8 + }, + "mojang_i_am_begging_and_crying_please_add_an_entity_health_criterion": { + "max": 1.0, + "min": 2.2250738585072014E-308 + } + }, + "trigger": "hexcasting:overcast" + }, + "has_the_recipe": { + "conditions": { + "recipe": "hexcasting:brainsweep/directrix_boolean" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "enlightenment", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "hexcasting:brainsweep/directrix_boolean" + ] + }, + "sends_telemetry_event": true +} \ No newline at end of file diff --git a/Forge/src/generated/resources/data/hexcasting/recipes/brainsweep/directrix_boolean.json b/Forge/src/generated/resources/data/hexcasting/recipes/brainsweep/directrix_boolean.json new file mode 100644 index 0000000000..fd1c128f51 --- /dev/null +++ b/Forge/src/generated/resources/data/hexcasting/recipes/brainsweep/directrix_boolean.json @@ -0,0 +1,21 @@ +{ + "type": "hexcasting:brainsweep", + "blockIn": { + "type": "block", + "block": "hexcasting:directrix/empty" + }, + "cost": 1000000, + "entityIn": { + "type": "villager", + "minLevel": 1, + "profession": "shepherd" + }, + "result": { + "name": "hexcasting:directrix/boolean", + "properties": { + "energized": "false", + "facing": "north", + "state": "neither" + } + } +} \ No newline at end of file diff --git a/Forge/src/generated/resources/data/hexcasting/tags/items/grants_root_advancement.json b/Forge/src/generated/resources/data/hexcasting/tags/items/grants_root_advancement.json index f5c26b0762..1f7070a707 100644 --- a/Forge/src/generated/resources/data/hexcasting/tags/items/grants_root_advancement.json +++ b/Forge/src/generated/resources/data/hexcasting/tags/items/grants_root_advancement.json @@ -2,6 +2,7 @@ "values": [ "hexcasting:amethyst_dust", "minecraft:amethyst_shard", - "hexcasting:charged_amethyst" + "hexcasting:charged_amethyst", + "hexcasting:creative_unlocker" ] } \ No newline at end of file diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java index 507d1fece7..bf219312fc 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java @@ -82,6 +82,7 @@ public static class Client implements HexConfig.ClientConfigAccess { private static ForgeConfigSpec.BooleanValue invertSpellbookScrollDirection; private static ForgeConfigSpec.BooleanValue invertAbacusScrollDirection; private static ForgeConfigSpec.DoubleValue gridSnapThreshold; + private static ForgeConfigSpec.BooleanValue clickingTogglesDrawing; public Client(ForgeConfigSpec.Builder builder) { ctrlTogglesOffStrokeOrder = builder.comment( @@ -98,6 +99,9 @@ public Client(ForgeConfigSpec.Builder builder) { "When using a staff, the distance from one dot you have to go to snap to the next dot, where 0.5 " + "means 50% of the way.") .defineInRange("gridSnapThreshold", DEFAULT_GRID_SNAP_THRESHOLD, 0.5, 1.0); + clickingTogglesDrawing = builder.comment( + "Whether you click to start and stop drawing instead of clicking and dragging") + .define("clickingTogglesDrawing", DEFAULT_CLICKING_TOGGLES_DRAWING); } @Override @@ -119,6 +123,11 @@ public boolean ctrlTogglesOffStrokeOrder() { public double gridSnapThreshold() { return gridSnapThreshold.get(); } + + @Override + public boolean clickingTogglesDrawing() { + return clickingTogglesDrawing.get(); + } } public static class Server implements HexConfig.ServerConfigAccess { diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java index 8fa6affec6..16bc2a0549 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java @@ -131,7 +131,6 @@ private static void initRegistry() { bind(Registries.ATTRIBUTE, HexAttributes::register); bind(Registries.MOB_EFFECT, HexMobEffects::register); bind(Registries.POTION, HexPotions::register); - HexPotions.addRecipes(); bind(Registries.PARTICLE_TYPE, HexParticles::registerParticles); diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/interop/jei/PatternDrawable.java b/Forge/src/main/java/at/petrak/hexcasting/forge/interop/jei/PatternDrawable.java index 62417e5e28..bf96781a00 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/interop/jei/PatternDrawable.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/interop/jei/PatternDrawable.java @@ -1,40 +1,40 @@ package at.petrak.hexcasting.forge.interop.jei; -import at.petrak.hexcasting.api.casting.math.HexCoord; +import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.api.mod.HexTags; import at.petrak.hexcasting.api.utils.HexUtils; -import at.petrak.hexcasting.interop.utils.PatternDrawingUtil; -import at.petrak.hexcasting.interop.utils.PatternEntry; +import at.petrak.hexcasting.client.render.PatternColors; +import at.petrak.hexcasting.client.render.PatternRenderer; +import at.petrak.hexcasting.client.render.PatternSettings; +import at.petrak.hexcasting.client.render.PatternSettings.PositionSettings; +import at.petrak.hexcasting.client.render.PatternSettings.StrokeSettings; +import at.petrak.hexcasting.client.render.PatternSettings.ZappySettings; import at.petrak.hexcasting.xplat.IXplatAbstractions; -import com.mojang.datafixers.util.Pair; import mezz.jei.api.gui.drawable.IDrawable; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.phys.Vec2; - -import java.util.List; public class PatternDrawable implements IDrawable { private final int width; private final int height; private boolean strokeOrder; + private final HexPattern pat; - private final List patterns; - private final List pathfinderDots; + private PatternSettings patSets; public PatternDrawable(ResourceLocation pattern, int w, int h) { var regi = IXplatAbstractions.INSTANCE.getActionRegistry(); var entry = regi.get(pattern); this.strokeOrder = !HexUtils.isOfTag(regi, pattern, HexTags.Actions.PER_WORLD_PATTERN); - var data = PatternDrawingUtil.loadPatterns( - List.of(new Pair<>(entry.prototype(), HexCoord.getOrigin())), - 0f, - 1f); - this.patterns = data.patterns(); - this.pathfinderDots = data.pathfinderDots(); + this.pat = entry.prototype(); this.width = w; this.height = h; + this.patSets = new PatternSettings("pattern_drawable_" + w + "_" + h, + new PositionSettings(width, height, 0, 0, + PatternSettings.AxisAlignment.CENTER_FIT, PatternSettings.AxisAlignment.CENTER_FIT, Math.max(width, height), 0, 0), + StrokeSettings.fromStroke(0.075 * Math.min(width, height)), + ZappySettings.READABLE); } @Override @@ -48,18 +48,26 @@ public int getHeight() { } public PatternDrawable strokeOrder(boolean order) { + if(order != strokeOrder){ + patSets = new PatternSettings("pattern_drawable_" + width + "_" + height + (order ? "" : "nostroke"), + patSets.posSets, + patSets.strokeSets, + order ? ZappySettings.READABLE : ZappySettings.STATIC + ); + } strokeOrder = order; return this; } @Override - public void draw(GuiGraphics guiGraphics, int xOffset, int yOffset) { - var ps = guiGraphics.pose(); + public void draw(GuiGraphics graphics, int x, int y) { + var ps = graphics.pose(); ps.pushPose(); - ps.translate(xOffset - 0.5f + width / 2f, yOffset + height / 2f, 0); - ps.scale(width / 64f, height / 64f, 1f); - PatternDrawingUtil.drawPattern(guiGraphics, 0, 0, this.patterns, this.pathfinderDots, this.strokeOrder, - 0xff_333030, 0xff_191818, 0xc8_0c0a0c, 0x80_666363); + ps.translate(x, y + 1, 0); + PatternRenderer.renderPattern(pat, graphics.pose(), patSets, + new PatternColors(0xc8_0c0a0c, 0xff_333030).withDotColors(0x80_666363, 0), + 0, 10 + ); ps.popPose(); } } diff --git a/Forge/src/main/resources/META-INF/mods.toml b/Forge/src/main/resources/META-INF/mods.toml index 252e3f9273..d3d478bb21 100644 --- a/Forge/src/main/resources/META-INF/mods.toml +++ b/Forge/src/main/resources/META-INF/mods.toml @@ -48,3 +48,10 @@ mandatory = true versionRange = "[3.1.0+1.20,)" ordering = "NONE" side = "BOTH" + +[[dependencies.hexcasting]] +modId = "inline" +mandatory = true +versionRange = "[1.20.1-1.0.1,)" +ordering = "NONE" +side = "BOTH" \ No newline at end of file diff --git a/doc/resources/assets/minecraft/textures/entities/villagers/sphepherd.png b/doc/resources/assets/minecraft/textures/entities/villagers/shepherd.png similarity index 100% rename from doc/resources/assets/minecraft/textures/entities/villagers/sphepherd.png rename to doc/resources/assets/minecraft/textures/entities/villagers/shepherd.png diff --git a/gradle.properties b/gradle.properties index 88d13e374a..015fb53ab5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,3 +17,6 @@ patchouliVersion=83 jeiVersion=15.0.0.12 pehkuiVersion=3.7.7 + +inlineVersion=1.0.1 +clothConfigVersion=11.1.106 \ No newline at end of file