From 369d689a04835f0b359c58a4f87a130f8a86f37f Mon Sep 17 00:00:00 2001 From: Skye Date: Sat, 2 Nov 2024 15:02:08 +0900 Subject: [PATCH] format everything --- .../hexcasting/annotations/SoftImplement.java | 24 +- .../java/at/petrak/hexcasting/api/HexAPI.java | 400 +++-- .../hexcasting/api/addldata/ADHexHolder.java | 16 +- .../hexcasting/api/addldata/ADIotaHolder.java | 43 +- .../api/addldata/ADMediaHolder.java | 181 +-- .../hexcasting/api/addldata/ADPigment.java | 57 +- .../api/addldata/ADVariantItem.java | 6 +- .../ItemDelegatingEntityIotaHolder.java | 129 +- .../hexcasting/api/addldata/package-info.java | 11 +- .../FailToCastGreatSpellTrigger.java | 65 +- .../advancements/HexAdvancementTriggers.java | 17 +- .../api/advancements/MinMaxLongs.java | 123 +- .../api/advancements/OvercastTrigger.java | 134 +- .../api/advancements/SpendMediaTrigger.java | 109 +- .../hexcasting/api/block/HexBlockEntity.java | 71 +- .../block/circle/BlockAbstractImpetus.java | 133 +- .../block/circle/BlockCircleComponent.java | 156 +- .../api/casting/ActionRegistryEntry.java | 11 +- .../hexcasting/api/casting/ActionUtils.kt | 446 ++--- .../hexcasting/api/casting/ParticleSpray.kt | 39 +- .../api/casting/PatternShapeMatch.java | 91 +- .../hexcasting/api/casting/RenderedSpell.kt | 10 +- .../hexcasting/api/casting/SpellList.kt | 145 +- .../api/casting/arithmetic/Arithmetic.java | 5 +- .../api/casting/arithmetic/IterPair.java | 8 +- .../casting/arithmetic/TripleIterable.java | 76 +- .../arithmetic/engine/ArithmeticEngine.java | 193 ++- .../casting/arithmetic/engine/HashCons.java | 1 - .../engine/InvalidOperatorException.java | 21 +- .../engine/NoOperatorCandidatesException.java | 63 +- .../casting/arithmetic/operator/Operator.kt | 51 +- .../arithmetic/operator/OperatorBasic.kt | 50 +- .../arithmetic/operator/OperatorBinary.java | 11 +- .../arithmetic/operator/OperatorUnary.java | 11 +- .../predicates/IotaMultiPredicate.java | 42 +- .../arithmetic/predicates/IotaPredicate.java | 20 +- .../api/casting/castables/Action.kt | 87 +- .../api/casting/castables/ConstMediaAction.kt | 64 +- .../api/casting/castables/OperationAction.kt | 25 +- .../api/casting/castables/SpecialHandler.java | 52 +- .../api/casting/castables/SpellAction.kt | 114 +- .../circles/BlockEntityAbstractImpetus.java | 841 +++++----- .../casting/circles/CircleExecutionState.java | 601 +++---- .../api/casting/circles/ICircleComponent.java | 298 ++-- .../hexcasting/api/casting/eval/CastResult.kt | 18 +- .../api/casting/eval/CastingEnvironment.java | 880 +++++----- .../eval/CastingEnvironmentComponent.java | 98 +- .../api/casting/eval/ExecutionClientView.kt | 17 +- .../api/casting/eval/MishapEnvironment.java | 57 +- .../api/casting/eval/OperationResult.kt | 12 +- .../api/casting/eval/ResolvedPattern.kt | 39 +- .../api/casting/eval/ResolvedPatternType.kt | 24 +- .../api/casting/eval/SpecialPatterns.java | 8 +- .../api/casting/eval/SpellCircleContext.kt | 102 +- .../api/casting/eval/env/CircleCastEnv.java | 358 ++-- .../api/casting/eval/env/CircleMishapEnv.java | 46 +- .../casting/eval/env/PackagedItemCastEnv.java | 135 +- .../casting/eval/env/PlayerBasedCastEnv.java | 491 +++--- .../eval/env/PlayerBasedMishapEnv.java | 91 +- .../api/casting/eval/env/StaffCastEnv.java | 261 ++- .../api/casting/eval/env/package-info.java | 2 +- .../casting/eval/sideeffects/EvalSound.java | 13 +- .../eval/sideeffects/OperatorSideEffect.kt | 109 +- .../api/casting/eval/vm/CastingImage.kt | 241 ++- .../api/casting/eval/vm/CastingVM.kt | 533 +++--- .../api/casting/eval/vm/ContinuationFrame.kt | 153 +- .../api/casting/eval/vm/FrameEvaluate.kt | 101 +- .../api/casting/eval/vm/FrameFinishEval.kt | 54 +- .../api/casting/eval/vm/FrameForEach.kt | 172 +- .../api/casting/eval/vm/FunctionalData.kt | 15 +- .../api/casting/eval/vm/SpellContinuation.kt | 70 +- .../api/casting/iota/BooleanIota.java | 89 +- .../api/casting/iota/ContinuationIota.java | 126 +- .../api/casting/iota/DoubleIota.java | 92 +- .../api/casting/iota/EntityIota.java | 154 +- .../api/casting/iota/GarbageIota.java | 97 +- .../hexcasting/api/casting/iota/Iota.java | 196 ++- .../hexcasting/api/casting/iota/IotaType.java | 368 ++--- .../hexcasting/api/casting/iota/ListIota.java | 219 +-- .../hexcasting/api/casting/iota/NullIota.java | 90 +- .../api/casting/iota/PatternIota.java | 307 ++-- .../hexcasting/api/casting/iota/Vec3Iota.java | 110 +- .../api/casting/math/EulerPathFinder.kt | 147 +- .../hexcasting/api/casting/math/HexAngle.kt | 12 +- .../hexcasting/api/casting/math/HexCoord.kt | 107 +- .../hexcasting/api/casting/math/HexDir.kt | 67 +- .../hexcasting/api/casting/math/HexPattern.kt | 325 ++-- .../hexcasting/api/casting/mishaps/Mishap.kt | 190 +-- .../mishaps/MishapAlreadyBrainswept.kt | 18 +- .../api/casting/mishaps/MishapBadBlock.kt | 38 +- .../casting/mishaps/MishapBadBrainsweep.kt | 20 +- .../api/casting/mishaps/MishapBadEntity.kt | 31 +- .../api/casting/mishaps/MishapBadItem.kt | 30 +- .../api/casting/mishaps/MishapBadLocation.kt | 14 +- .../casting/mishaps/MishapBadOffhandItem.kt | 33 +- .../casting/mishaps/MishapDisallowedSpell.kt | 15 +- .../api/casting/mishaps/MishapDivideByZero.kt | 165 +- .../casting/mishaps/MishapEntityTooFarAway.kt | 14 +- .../api/casting/mishaps/MishapEvalTooMuch.kt | 13 +- .../api/casting/mishaps/MishapImmuneEntity.kt | 14 +- .../mishaps/MishapInternalException.kt | 14 +- .../api/casting/mishaps/MishapInvalidIota.kt | 55 +- .../mishaps/MishapInvalidOperatorArgs.kt | 48 +- .../casting/mishaps/MishapInvalidPattern.kt | 15 +- .../mishaps/MishapInvalidSpellDatumType.kt | 22 +- .../mishaps/MishapLocationInWrongDimension.kt | 21 +- .../casting/mishaps/MishapNoAkashicRecord.kt | 14 +- .../casting/mishaps/MishapNotEnoughArgs.kt | 17 +- .../api/casting/mishaps/MishapOthersName.kt | 90 +- .../api/casting/mishaps/MishapStackSize.kt | 18 +- .../mishaps/MishapTooManyCloseParens.kt | 17 +- .../casting/mishaps/MishapUnenlightened.kt | 41 +- .../casting/mishaps/MishapUnescapedValue.kt | 46 +- .../circle/MishapBoolDirectrixEmptyStack.kt | 18 +- .../circle/MishapBoolDirectrixNotBool.kt | 20 +- .../mishaps/circle/MishapNoSpellCircle.kt | 51 +- .../api/client/ClientCastingStack.kt | 113 +- .../api/client/ClientRenderHelper.kt | 154 +- .../api/client/HexPatternRenderHolder.kt | 24 +- .../client/ScryingLensOverlayRegistry.java | 152 +- .../hexcasting/api/item/HexHolderItem.java | 22 +- .../hexcasting/api/item/IotaHolderItem.java | 183 +-- .../hexcasting/api/item/MediaHolderItem.java | 90 +- .../hexcasting/api/item/PigmentItem.java | 11 +- .../hexcasting/api/item/VariantItem.java | 37 +- .../api/misc/DiscoveryHandlers.java | 32 +- .../hexcasting/api/misc/MediaConstants.java | 10 +- .../at/petrak/hexcasting/api/misc/Result.java | 128 +- .../hexcasting/api/misc/TriPredicate.java | 6 +- .../hexcasting/api/mod/HexApiMessages.java | 66 +- .../petrak/hexcasting/api/mod/HexConfig.java | 244 +-- .../hexcasting/api/mod/HexStatistics.java | 42 +- .../at/petrak/hexcasting/api/mod/HexTags.java | 146 +- .../hexcasting/api/pigment/ColorProvider.java | 85 +- .../hexcasting/api/pigment/FrozenPigment.java | 71 +- .../hexcasting/api/player/AltioraAbility.java | 10 +- .../hexcasting/api/player/FlightAbility.java | 6 +- .../hexcasting/api/player/Sentinel.java | 7 +- .../petrak/hexcasting/api/utils/HexUtils.kt | 423 ++--- .../petrak/hexcasting/api/utils/MathUtils.kt | 54 +- .../hexcasting/api/utils/MediaHelper.kt | 118 +- .../at/petrak/hexcasting/api/utils/NBTDsl.kt | 468 +++--- .../petrak/hexcasting/api/utils/NBTHelper.kt | 274 +++- .../hexcasting/client/ClientTickCounter.java | 30 +- .../client/PatternShapeMatcher.java | 3 +- .../client/RegisterClientStuff.java | 511 +++--- .../client/ShiftScrollListener.java | 97 +- .../client/entity/WallScrollRenderer.java | 261 +-- .../hexcasting/client/gui/GuiSpellcasting.kt | 995 +++++------ .../client/gui/PatternTooltipComponent.java | 143 +- .../client/ktxt/ClientAccessorWrappers.kt | 5 +- .../hexcasting/client/model/AltioraLayer.java | 74 +- .../client/model/HexModelLayers.java | 49 +- .../client/model/HexRobesModels.java | 130 +- ...yOwnArmorModelWithBlackjackAndHookers.java | 146 +- .../client/particles/ConjureParticle.java | 216 +-- .../client/render/GaslightingTracker.java | 34 +- .../client/render/HexAdditionalRenderers.java | 379 +++-- .../client/render/HexPatternLike.java | 95 +- .../client/render/HexPatternPoints.java | 270 +-- .../client/render/PatternColors.java | 120 +- .../client/render/PatternRenderer.java | 346 ++-- .../client/render/PatternSettings.java | 351 ++-- .../client/render/PatternTextureManager.java | 286 ++-- .../hexcasting/client/render/RenderLib.kt | 763 ++++----- .../client/render/ScryingLensOverlays.java | 442 ++--- .../hexcasting/client/render/VCDrawHelper.kt | 269 +-- .../render/WorldlyPatternRenderHelpers.java | 322 ++-- .../BlockEntityAkashicBookshelfRenderer.java | 35 +- .../be/BlockEntityQuenchedAllayRenderer.java | 92 +- .../render/be/BlockEntitySlateRenderer.java | 28 +- .../render/shader/FakeBufferSource.java | 39 +- .../client/render/shader/HexRenderTypes.java | 75 +- .../client/render/shader/HexShaders.java | 31 +- .../client/sound/GridSoundInstance.kt | 96 +- .../common/blocks/BlockConjured.java | 212 +-- .../common/blocks/BlockConjuredLight.java | 116 +- .../common/blocks/BlockFlammable.java | 43 +- .../common/blocks/BlockQuenchedAllay.java | 67 +- .../blocks/akashic/AkashicFloodfiller.java | 101 +- .../blocks/akashic/BlockAkashicBookshelf.java | 138 +- .../blocks/akashic/BlockAkashicLigature.java | 6 +- .../blocks/akashic/BlockAkashicRecord.java | 97 +- .../akashic/BlockEntityAkashicBookshelf.java | 131 +- .../common/blocks/behavior/HexComposting.java | 22 +- .../blocks/behavior/HexStrippables.java | 21 +- .../blocks/circles/BlockEmptyImpetus.java | 101 +- .../blocks/circles/BlockEntitySlate.java | 54 +- .../common/blocks/circles/BlockSlate.java | 405 ++--- .../directrix/BlockBooleanDirectrix.java | 226 +-- .../directrix/BlockEmptyDirectrix.java | 109 +- .../directrix/BlockRedstoneDirectrix.java | 210 +-- .../impetuses/BlockEntityLookingImpetus.java | 135 +- .../impetuses/BlockEntityRedstoneImpetus.java | 246 +-- .../BlockEntityRightClickImpetus.java | 6 +- .../impetuses/BlockLookingImpetus.java | 52 +- .../impetuses/BlockRedstoneImpetus.java | 139 +- .../impetuses/BlockRightClickImpetus.java | 50 +- .../blocks/decoration/BlockAkashicLeaves.java | 33 +- .../blocks/decoration/BlockAkashicLog.java | 34 +- .../decoration/BlockAmethystDirectional.java | 48 +- .../common/blocks/decoration/BlockAxis.java | 6 +- .../blocks/decoration/BlockHexDoor.java | 35 +- .../blocks/decoration/BlockHexFence.java | 33 +- .../blocks/decoration/BlockHexFenceGate.java | 34 +- .../decoration/BlockHexPressurePlate.java | 34 +- .../blocks/decoration/BlockHexSlab.java | 34 +- .../blocks/decoration/BlockHexStairs.java | 33 +- .../blocks/decoration/BlockHexTrapdoor.java | 33 +- .../blocks/decoration/BlockHexWoodButton.java | 34 +- .../common/blocks/decoration/BlockSconce.java | 196 ++- .../blocks/entity/BlockEntityConjured.java | 188 ++- .../entity/BlockEntityQuenchedAllay.java | 32 +- .../casting/PatternRegistryManifest.java | 226 +-- .../common/casting/actions/OpEntityHeight.kt | 12 +- .../common/casting/actions/OpEntityLook.kt | 12 +- .../common/casting/actions/OpEntityPos.kt | 12 +- .../casting/actions/OpEntityVelocity.kt | 14 +- .../casting/actions/akashic/OpAkashicRead.kt | 24 +- .../casting/actions/akashic/OpAkashicWrite.kt | 90 +- .../casting/actions/circles/OpCircleBounds.kt | 21 +- .../casting/actions/circles/OpImpetusDir.kt | 17 +- .../casting/actions/circles/OpImpetusPos.kt | 11 +- .../common/casting/actions/eval/OpEval.kt | 51 +- .../casting/actions/eval/OpEvalBreakable.kt | 18 +- .../common/casting/actions/eval/OpForEach.kt | 27 +- .../common/casting/actions/eval/OpHalt.kt | 43 +- .../common/casting/actions/eval/OpThanos.kt | 21 +- .../common/casting/actions/lists/OpAppend.kt | 15 +- .../common/casting/actions/lists/OpConcat.kt | 15 +- .../common/casting/actions/lists/OpCons.kt | 13 +- .../casting/actions/lists/OpEmptyList.kt | 9 +- .../common/casting/actions/lists/OpIndex.kt | 15 +- .../common/casting/actions/lists/OpIndexOf.kt | 14 +- .../casting/actions/lists/OpLastNToList.kt | 33 +- .../casting/actions/lists/OpListSize.kt | 9 +- .../casting/actions/lists/OpModifyInPlace.kt | 15 +- .../common/casting/actions/lists/OpRemove.kt | 19 +- .../casting/actions/lists/OpReverski.kt | 13 +- .../casting/actions/lists/OpSingleton.kt | 9 +- .../common/casting/actions/lists/OpSlice.kt | 18 +- .../common/casting/actions/lists/OpSplat.kt | 8 +- .../common/casting/actions/lists/OpUnCons.kt | 17 +- .../casting/actions/local/OpPeekLocal.kt | 29 +- .../casting/actions/local/OpPushLocal.kt | 25 +- .../common/casting/actions/math/OpAbsLen.kt | 12 +- .../common/casting/actions/math/OpAdd.kt | 38 +- .../common/casting/actions/math/OpCeil.kt | 14 +- .../casting/actions/math/OpCoerceToAxial.kt | 27 +- .../casting/actions/math/OpConstructVec.kt | 15 +- .../casting/actions/math/OpDeconstructVec.kt | 11 +- .../common/casting/actions/math/OpDivCross.kt | 58 +- .../common/casting/actions/math/OpFloor.kt | 12 +- .../common/casting/actions/math/OpLog.kt | 18 +- .../common/casting/actions/math/OpModulo.kt | 19 +- .../common/casting/actions/math/OpMulDot.kt | 37 +- .../common/casting/actions/math/OpPowProj.kt | 69 +- .../common/casting/actions/math/OpRandom.kt | 10 +- .../common/casting/actions/math/OpSub.kt | 37 +- .../math/SpecialHandlerNumberLiteral.kt | 114 +- .../common/casting/actions/math/bit/OpAnd.kt | 28 +- .../common/casting/actions/math/bit/OpNot.kt | 10 +- .../common/casting/actions/math/bit/OpOr.kt | 28 +- .../casting/actions/math/bit/OpToSet.kt | 22 +- .../common/casting/actions/math/bit/OpXor.kt | 40 +- .../casting/actions/math/logic/OpBoolAnd.kt | 12 +- .../casting/actions/math/logic/OpBoolIf.kt | 14 +- .../casting/actions/math/logic/OpBoolNot.kt | 10 +- .../casting/actions/math/logic/OpBoolOr.kt | 12 +- .../actions/math/logic/OpBoolToNumber.kt | 10 +- .../casting/actions/math/logic/OpBoolXor.kt | 12 +- .../actions/math/logic/OpCoerceToBool.kt | 8 +- .../casting/actions/math/logic/OpCompare.kt | 18 +- .../casting/actions/math/logic/OpEquality.kt | 12 +- .../casting/actions/math/trig/OpArcCos.kt | 12 +- .../casting/actions/math/trig/OpArcSin.kt | 12 +- .../casting/actions/math/trig/OpArcTan.kt | 12 +- .../casting/actions/math/trig/OpArcTan2.kt | 14 +- .../common/casting/actions/math/trig/OpCos.kt | 12 +- .../common/casting/actions/math/trig/OpSin.kt | 12 +- .../common/casting/actions/math/trig/OpTan.kt | 15 +- .../actions/raycast/OpBlockAxisRaycast.kt | 48 +- .../casting/actions/raycast/OpBlockRaycast.kt | 58 +- .../actions/raycast/OpEntityRaycast.kt | 130 +- .../common/casting/actions/rw/OpRead.kt | 43 +- .../common/casting/actions/rw/OpReadable.kt | 23 +- .../casting/actions/rw/OpTheCoolerRead.kt | 27 +- .../casting/actions/rw/OpTheCoolerReadable.kt | 23 +- .../casting/actions/rw/OpTheCoolerWritable.kt | 21 +- .../casting/actions/rw/OpTheCoolerWrite.kt | 79 +- .../common/casting/actions/rw/OpWritable.kt | 22 +- .../common/casting/actions/rw/OpWrite.kt | 78 +- .../casting/actions/selectors/OpGetCaster.kt | 13 +- .../actions/selectors/OpGetEntitiesBy.kt | 69 +- .../actions/selectors/OpGetEntityAt.kt | 28 +- .../casting/actions/spells/OpAddMotion.kt | 92 +- .../common/casting/actions/spells/OpBeep.kt | 48 +- .../common/casting/actions/spells/OpBlink.kt | 88 +- .../casting/actions/spells/OpBreakBlock.kt | 68 +- .../casting/actions/spells/OpColorize.kt | 43 +- .../casting/actions/spells/OpConjureBlock.kt | 85 +- .../casting/actions/spells/OpCreateFluid.kt | 79 +- .../casting/actions/spells/OpCycleVariant.kt | 35 +- .../casting/actions/spells/OpDestroyFluid.kt | 207 ++- .../casting/actions/spells/OpEdifySapling.kt | 77 +- .../common/casting/actions/spells/OpErase.kt | 73 +- .../casting/actions/spells/OpExplode.kt | 83 +- .../casting/actions/spells/OpExtinguish.kt | 204 +-- .../common/casting/actions/spells/OpFlight.kt | 384 +++-- .../common/casting/actions/spells/OpIgnite.kt | 111 +- .../casting/actions/spells/OpMakeBattery.kt | 101 +- .../actions/spells/OpMakePackagedSpell.kt | 107 +- .../casting/actions/spells/OpPlaceBlock.kt | 159 +- .../casting/actions/spells/OpPotionEffect.kt | 75 +- .../common/casting/actions/spells/OpPrint.kt | 46 +- .../casting/actions/spells/OpRecharge.kt | 115 +- .../OpTheOnlyReasonAnyoneDownloadedPsi.kt | 48 +- .../casting/actions/spells/great/OpAltiora.kt | 119 +- .../actions/spells/great/OpBrainsweep.kt | 100 +- .../actions/spells/great/OpLightning.kt | 43 +- .../actions/spells/great/OpTeleport.kt | 233 +-- .../casting/actions/spells/great/OpWeather.kt | 49 +- .../spells/sentinel/OpCreateSentinel.kt | 43 +- .../spells/sentinel/OpDestroySentinel.kt | 39 +- .../spells/sentinel/OpGetSentinelPos.kt | 19 +- .../spells/sentinel/OpGetSentinelWayfind.kt | 21 +- .../OpAlwinfyHasAscendedToABeingOfPureMath.kt | 92 +- .../casting/actions/stack/OpDuplicateN.kt | 24 +- .../casting/actions/stack/OpFisherman.kt | 57 +- .../actions/stack/OpFishermanButItCopies.kt | 35 +- .../common/casting/actions/stack/OpMask.kt | 19 +- .../casting/actions/stack/OpStackSize.kt | 16 +- .../casting/actions/stack/OpTwiddling.kt | 10 +- .../actions/stack/SpecialHandlerMask.kt | 123 +- .../arithmetic/BitwiseSetArithmetic.kt | 52 +- .../casting/arithmetic/BoolArithmetic.kt | 87 +- .../casting/arithmetic/DoubleArithmetic.kt | 125 +- .../casting/arithmetic/ListArithmetic.kt | 72 +- .../casting/arithmetic/ListSetArithmetic.kt | 43 +- .../casting/arithmetic/Vec3Arithmetic.java | 43 +- .../arithmetic/operator/OperatorLog.kt | 19 +- .../arithmetic/operator/OperatorUtils.kt | 92 +- .../operator/list/OperatorAppend.kt | 18 +- .../arithmetic/operator/list/OperatorIndex.kt | 23 +- .../operator/list/OperatorIndexOf.kt | 18 +- .../operator/list/OperatorRemove.kt | 26 +- .../operator/list/OperatorReplace.kt | 27 +- .../arithmetic/operator/list/OperatorSlice.kt | 32 +- .../operator/list/OperatorUnCons.kt | 16 +- .../operator/list/OperatorUnappend.kt | 15 +- .../operator/list/OperatorUnique.kt | 27 +- .../arithmetic/operator/vec/OperatorPack.java | 19 +- .../operator/vec/OperatorUnpack.java | 10 +- .../operator/vec/OperatorVec3Delegating.java | 44 +- .../common/command/BrainsweepCommand.java | 60 +- .../command/ListPerWorldPatternsCommand.java | 309 ++-- .../common/command/PatternResLocArgument.java | 66 +- .../command/PatternTexturesCommand.java | 52 +- .../common/command/RecalcPatternsCommand.java | 29 +- .../common/entities/EntityWallScroll.java | 343 ++-- .../common/entities/HexEntities.java | 49 +- .../hexcasting/common/impl/HexAPIImpl.java | 246 ++- .../common/items/HexBaubleItem.java | 2 +- .../common/items/ItemJewelerHammer.java | 15 +- .../hexcasting/common/items/ItemLens.java | 102 +- .../common/items/ItemLoreFragment.java | 120 +- .../hexcasting/common/items/ItemStaff.java | 73 +- .../common/items/armor/ItemRobes.java | 17 +- .../items/magic/DebugUnlockerHolder.java | 98 +- .../common/items/magic/ItemArtifact.java | 42 +- .../items/magic/ItemCreativeUnlocker.java | 503 +++--- .../common/items/magic/ItemCypher.java | 42 +- .../common/items/magic/ItemMediaBattery.java | 34 +- .../common/items/magic/ItemMediaHolder.java | 207 +-- .../common/items/magic/ItemPackagedHex.java | 295 ++-- .../common/items/magic/ItemTrinket.java | 42 +- .../pigment/ItemAmethystAndCopperPigment.java | 45 +- .../common/items/pigment/ItemDyePigment.java | 51 +- .../items/pigment/ItemPridePigment.java | 98 +- .../common/items/pigment/ItemUUIDPigment.java | 105 +- .../common/items/storage/ItemAbacus.java | 93 +- .../common/items/storage/ItemFocus.java | 146 +- .../common/items/storage/ItemScroll.java | 264 +-- .../common/items/storage/ItemSlate.java | 200 +-- .../common/items/storage/ItemSpellbook.java | 481 +++--- .../common/items/storage/ItemThoughtKnot.java | 83 +- .../hexcasting/common/lib/HexAttributes.java | 66 +- .../common/lib/HexBlockEntities.java | 120 +- .../common/lib/HexBlockSetTypes.java | 27 +- .../hexcasting/common/lib/HexBlocks.java | 625 +++---- .../hexcasting/common/lib/HexCommands.java | 16 +- .../common/lib/HexConfiguredFeatures.java | 16 +- .../common/lib/HexCreativeTabs.java | 46 +- .../hexcasting/common/lib/HexDamageTypes.java | 19 +- .../common/lib/HexFeatureConfigs.java | 65 +- .../hexcasting/common/lib/HexItems.java | 431 ++--- .../common/lib/HexLootFunctions.java | 47 +- .../hexcasting/common/lib/HexMobEffects.java | 66 +- .../hexcasting/common/lib/HexParticles.java | 72 +- .../hexcasting/common/lib/HexPotions.java | 76 +- .../hexcasting/common/lib/HexRegistries.java | 22 +- .../hexcasting/common/lib/HexSounds.java | 77 +- .../hexcasting/common/lib/hex/HexActions.java | 1448 +++++++++++------ .../common/lib/hex/HexArithmetics.java | 81 +- .../common/lib/hex/HexContinuationTypes.java | 69 +- .../common/lib/hex/HexEvalSounds.java | 66 +- .../common/lib/hex/HexIotaTypes.java | 79 +- .../common/lib/hex/HexSpecialHandlers.java | 44 +- .../common/lib/hex/package-info.java | 6 +- .../loot/AddPerWorldPatternToScrollFunc.java | 95 +- .../common/loot/AmethystReducerFunc.java | 78 +- .../common/loot/HexLootHandler.java | 112 +- .../common/misc/AkashicTreeGrower.java | 26 +- .../common/misc/BrainsweepingEvents.java | 36 +- .../hexcasting/common/misc/HexMobEffect.java | 10 +- .../common/misc/PatternTooltip.java | 4 +- .../common/misc/PlayerPositionRecorder.java | 40 +- .../hexcasting/common/misc/RegisterMisc.java | 70 +- .../hexcasting/common/msgs/IMessage.java | 24 +- .../hexcasting/common/msgs/MsgBeepS2C.java | 98 +- .../hexcasting/common/msgs/MsgBlinkS2C.java | 64 +- .../common/msgs/MsgCastParticleS2C.java | 180 +- .../msgs/MsgClearSpiralPatternsS2C.java | 73 +- .../common/msgs/MsgNewSpellPatternC2S.java | 85 +- .../common/msgs/MsgNewSpellPatternS2C.java | 92 +- .../common/msgs/MsgNewSpiralPatternsS2C.java | 92 +- .../common/msgs/MsgNewWallScrollS2C.java | 94 +- .../common/msgs/MsgOpenSpellGuiS2C.java | 113 +- .../msgs/MsgRecalcWallScrollDisplayS2C.java | 71 +- .../common/msgs/MsgShiftScrollC2S.java | 288 ++-- .../particles/ConjureParticleOptions.java | 91 +- .../common/recipe/BrainsweepRecipe.java | 15 +- .../common/recipe/HexRecipeStuffRegistry.java | 100 +- .../common/recipe/RecipeSerializerBase.java | 38 +- .../common/recipe/SealSpellbookRecipe.java | 76 +- .../common/recipe/SealThingsRecipe.java | 213 ++- .../recipe/ingredient/StateIngredient.java | 36 +- .../ingredient/StateIngredientBlock.java | 122 +- .../ingredient/StateIngredientBlockState.java | 176 +- .../ingredient/StateIngredientBlocks.java | 144 +- .../ingredient/StateIngredientHelper.java | 263 ++- .../recipe/ingredient/StateIngredientTag.java | 22 +- .../StateIngredientTagExcluding.java | 137 +- .../brainsweep/BrainsweepeeIngredient.java | 128 +- .../brainsweep/EntityTagIngredient.java | 227 ++- .../brainsweep/EntityTypeIngredient.java | 168 +- .../brainsweep/VillagerIngredient.java | 464 +++--- .../hexcasting/datagen/HexAdvancements.java | 211 ++- .../hexcasting/datagen/HexLootTables.java | 300 ++-- .../datagen/IXplatConditionsBuilder.java | 4 +- .../hexcasting/datagen/IXplatIngredients.java | 28 +- .../datagen/recipe/HexplatRecipes.java | 1199 ++++++++------ .../builders/BrainsweepRecipeBuilder.java | 52 +- .../builders/CompatIngredientValue.java | 38 +- .../builders/CompatProcessingOutput.java | 27 +- .../builders/CreateCrushingRecipeBuilder.java | 274 ++-- .../FarmersDelightCuttingRecipeBuilder.java | 283 ++-- .../FarmersDelightToolIngredient.java | 2 +- .../recipe/builders/ItemProcessingOutput.java | 35 +- .../recipe/builders/ProcessingOutput.java | 2 +- .../datagen/tag/HexActionTagProvider.java | 84 +- .../datagen/tag/HexBlockTagProvider.java | 297 ++-- .../datagen/tag/HexDamageTypeTagProvider.java | 38 +- .../datagen/tag/HexItemTagProvider.java | 118 +- .../petrak/hexcasting/interop/HexInterop.java | 133 +- .../interop/inline/HexPatternMatcher.java | 152 +- .../hexcasting/interop/inline/InlineHex.java | 6 +- .../interop/inline/InlineHexClient.java | 8 +- .../interop/inline/InlinePatternData.java | 114 +- .../interop/inline/InlinePatternRenderer.java | 149 +- .../patchouli/AbstractPatternComponent.java | 163 +- .../patchouli/BrainsweepProcessor.java | 13 +- .../patchouli/CustomComponentTooltip.java | 71 +- .../patchouli/LookupPatternComponent.java | 69 +- .../patchouli/ManualPatternComponent.java | 80 +- .../patchouli/MultiCraftingProcessor.java | 148 +- .../interop/patchouli/PatchouliUtils.java | 114 +- .../interop/patchouli/PatternProcessor.java | 39 +- .../hexcasting/interop/pehkui/OpGetScale.kt | 12 +- .../hexcasting/interop/pehkui/OpSetScale.kt | 37 +- .../interop/pehkui/PehkuiInterop.java | 24 +- .../interop/utils/PatternDrawingUtil.java | 241 +-- .../interop/utils/PatternEntry.java | 5 +- .../utils/PhialRecipeStackBuilder.java | 92 +- .../hexcasting/ktxt/AccessorWrappers.kt | 40 +- .../mixin/MixinAbstractVillager.java | 14 +- .../hexcasting/mixin/MixinLivingEntity.java | 18 +- .../at/petrak/hexcasting/mixin/MixinMob.java | 28 +- .../petrak/hexcasting/mixin/MixinRaider.java | 14 +- .../hexcasting/mixin/MixinVillager.java | 28 +- .../petrak/hexcasting/mixin/MixinWitch.java | 16 +- .../mixin/accessor/AccessorAbstractArrow.java | 4 +- .../mixin/accessor/AccessorEntity.java | 4 +- .../mixin/accessor/AccessorLivingEntity.java | 32 +- .../mixin/accessor/AccessorLootTable.java | 19 +- .../mixin/accessor/AccessorPotionBrewing.java | 5 +- .../mixin/accessor/AccessorUseOnContext.java | 13 +- .../mixin/accessor/AccessorVillager.java | 8 +- .../accessor/CriteriaTriggersAccessor.java | 8 +- .../AccessorBlockEntityRenderDispatcher.java | 7 +- .../client/AccessorCompositeRenderType.java | 4 +- .../AccessorEmptyTextureStateShard.java | 7 +- .../accessor/client/AccessorMouseHandler.java | 8 +- .../client/AccessorRenderStateShard.java | 4 +- .../accessor/client/AccessorRenderType.java | 17 +- .../mixin/client/MixinClientLevel.java | 70 +- .../mixin/client/MixinPlayerRenderer.java | 19 +- .../server/ScrungledPatternsSave.java | 212 +-- .../xplat/IClientXplatAbstractions.java | 56 +- .../hexcasting/xplat/IForgeLikeBlock.java | 21 +- .../hexcasting/xplat/IXplatAbstractions.java | 195 ++- .../petrak/hexcasting/xplat/IXplatTags.java | 4 +- .../at/petrak/hexcasting/xplat/Platform.java | 3 +- Common/src/test/java/EulerPathFinderTest.kt | 18 +- ...WhatRangeDoTheNoisesOutputAnywaysTest.java | 53 +- .../fabric/FabricHexClientInitializer.kt | 88 +- .../hexcasting/fabric/FabricHexConfig.java | 537 +++--- .../hexcasting/fabric/FabricHexInitializer.kt | 357 ++-- .../hexcasting/fabric/cc/CCAltiora.java | 80 +- .../hexcasting/fabric/cc/CCBrainswept.java | 53 +- .../fabric/cc/CCClientCastingStack.java | 27 +- .../fabric/cc/CCFavoredPigment.java | 50 +- .../petrak/hexcasting/fabric/cc/CCFlight.java | 97 +- .../hexcasting/fabric/cc/CCPatterns.java | 66 +- .../hexcasting/fabric/cc/CCSentinel.java | 93 +- .../fabric/cc/CCStaffcastImage.java | 68 +- .../fabric/cc/HexCardinalComponents.java | 223 +-- .../fabric/cc/adimpl/CCEntityIotaHolder.java | 85 +- .../fabric/cc/adimpl/CCHexHolder.java | 98 +- .../fabric/cc/adimpl/CCIotaHolder.java | 3 +- .../fabric/cc/adimpl/CCItemIotaHolder.java | 107 +- .../fabric/cc/adimpl/CCMediaHolder.java | 243 ++- .../fabric/cc/adimpl/CCPigment.java | 43 +- .../fabric/cc/adimpl/CCVariantItem.java | 52 +- .../hexcasting/fabric/cc/package-info.java | 12 +- .../fabric/client/ExtendedTexture.java | 4 +- .../datagen/HexFabricConditionsBuilder.java | 157 +- .../datagen/HexFabricDataGenerators.java | 301 ++-- .../fabric/event/MouseScrollCallback.java | 29 +- .../event/VillagerConversionCallback.java | 21 +- .../fabric/interop/ModMenuInterop.java | 8 +- .../interop/emi/BrainsweepeeEmiStack.java | 161 +- .../interop/emi/EmiBrainsweepRecipe.java | 115 +- .../fabric/interop/emi/EmiEdifyRecipe.java | 152 +- .../fabric/interop/emi/EmiPhialRecipe.java | 143 +- .../fabric/interop/emi/HexEMIPlugin.java | 46 +- .../interop/emi/PatternRendererEMI.java | 108 +- .../interop/emi/TheCoolerSlotWidget.java | 102 +- .../interop/trinkets/LensTrinketRenderer.java | 75 +- .../interop/trinkets/TrinketsApiInterop.java | 72 +- .../fabric/loot/FabricHexLootModJankery.java | 81 +- .../fabric/mixin/FabricAxeItemMixin.java | 21 +- .../mixin/FabricBlockBehaviorMixin.java | 18 +- .../fabric/mixin/FabricClipContextMixin.java | 23 +- .../FabricEnchantmentTableBlockMixin.java | 19 +- .../fabric/mixin/FabricItemEntityMixin.java | 12 +- .../fabric/mixin/FabricLivingEntityMixin.java | 59 +- .../FabricMixinReloadableServerResources.java | 55 +- .../fabric/mixin/FabricMobMixin.java | 17 +- .../fabric/mixin/FabricPlayerMixin.java | 18 +- .../FabricVillagerTurnIntoWitchMixin.java | 18 +- .../client/FabricAbstractTextureMixin.java | 36 +- .../client/FabricLevelRendererMixin.java | 43 +- .../mixin/client/FabricMixinGameRenderer.java | 41 +- .../mixin/client/FabricModelManagerMixin.java | 36 +- .../mixin/client/FabricMouseHandlerMixin.java | 19 +- .../client/FabricParticleEngineMixin.java | 22 +- .../client/FabricPlayerRendererMixin.java | 20 +- .../fabric/network/FabricPacketHandler.java | 86 +- .../FabricModConditionalIngredient.java | 198 +-- .../recipe/FabricUnsealedIngredient.java | 173 +- .../fabric/storage/FabricImpetusStorage.kt | 110 +- .../fabric/xplat/FabricClientXplatImpl.java | 114 +- .../fabric/xplat/FabricXplatImpl.java | 899 +++++----- .../forge/ForgeHexClientInitializer.java | 222 +-- .../hexcasting/forge/ForgeHexConfig.java | 491 +++--- .../hexcasting/forge/ForgeHexInitializer.java | 460 +++--- .../hexcasting/forge/cap/CapSyncers.java | 100 +- .../forge/cap/ForgeCapabilityHandler.java | 398 +++-- .../forge/cap/ForgeImpetusCapability.java | 88 +- .../hexcasting/forge/cap/HexCapabilities.java | 27 +- .../cap/adimpl/CapClientCastingStack.java | 28 +- .../forge/cap/adimpl/CapEntityIotaHolder.java | 65 +- .../forge/cap/adimpl/CapItemHexHolder.java | 68 +- .../forge/cap/adimpl/CapItemIotaHolder.java | 60 +- .../forge/cap/adimpl/CapItemMediaHolder.java | 97 +- .../forge/cap/adimpl/CapItemPigment.java | 14 +- .../forge/cap/adimpl/CapItemVariantItem.java | 27 +- .../forge/cap/adimpl/CapStaticIotaHolder.java | 43 +- .../cap/adimpl/CapStaticMediaHolder.java | 108 +- .../datagen/ForgeHexConditionsBuilder.java | 83 +- .../forge/datagen/ForgeHexDataGenerators.java | 310 ++-- .../forge/datagen/ForgeHexLootModGen.java | 57 +- .../forge/datagen/TagsProviderEFHSetter.java | 2 +- .../xplat/HexBlockStatesAndModels.java | 986 ++++++----- .../forge/datagen/xplat/HexItemModels.java | 580 ++++--- .../interop/curios/CuriosApiInterop.java | 113 +- .../forge/interop/curios/CuriosRenderers.java | 28 +- .../interop/curios/LensCurioRenderer.java | 90 +- .../interop/jei/BrainsweepRecipeCategory.java | 142 +- .../interop/jei/EdifyRecipeCategory.java | 97 +- .../forge/interop/jei/HexJEIPlugin.java | 112 +- .../forge/interop/jei/PatternDrawable.java | 110 +- .../interop/jei/PhialRecipeCategory.java | 88 +- .../lib/ForgeHexArgumentTypeRegistry.java | 39 +- .../forge/lib/ForgeHexLootMods.java | 16 +- .../forge/loot/ForgeHexAmethystLootMod.java | 68 +- .../forge/loot/ForgeHexLoreLootMod.java | 51 +- .../forge/loot/ForgeHexScrollLootMod.java | 57 +- .../mixin/ForgeAccessorBuiltInRegistries.java | 25 +- .../forge/mixin/ForgeMixinBlockColors.java | 8 +- .../ForgeMixinCursedRecipeSerializerBase.java | 15 +- .../forge/mixin/ForgeMixinItemColors.java | 9 +- .../forge/mixin/ForgeMixinTagsProvider.java | 63 +- .../forge/network/ForgePacketHandler.java | 188 ++- .../forge/network/MsgAltioraUpdateAck.java | 80 +- .../forge/network/MsgBrainsweepAck.java | 84 +- .../forge/network/MsgPigmentUpdateAck.java | 72 +- .../network/MsgSentinelStatusUpdateAck.java | 95 +- .../recipe/ForgeModConditionalIngredient.java | 200 +-- .../forge/recipe/ForgeUnsealedIngredient.java | 161 +- .../forge/xplat/ForgeClientXplatImpl.java | 81 +- .../forge/xplat/ForgeXplatImpl.java | 1010 ++++++------ 623 files changed, 33405 insertions(+), 30190 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/annotations/SoftImplement.java b/Common/src/main/java/at/petrak/hexcasting/annotations/SoftImplement.java index c90949187f..de6da6fb94 100644 --- a/Common/src/main/java/at/petrak/hexcasting/annotations/SoftImplement.java +++ b/Common/src/main/java/at/petrak/hexcasting/annotations/SoftImplement.java @@ -9,22 +9,18 @@ // yoinky sploinky /** - * A purely-documentative annotation. - * This annotation is used by developers in xplat code. The annotated method is intended - * to "soft implement" a certain method in a loader specific interface that cannot be - * named in xplat code and thus cannot be checked with @Override. - * In this context, "soft implement" means to implement the method by matching the signature - * with the intended interface method. - * Examples of interfaces that we would use this for is IForgeItem or FabricItem. - *

- * The intent is that we audit such sites every major Minecraft version or so, to ensure - * that they still properly override the intended target. + * A purely-documentative annotation. This annotation is used by developers in xplat code. The + * annotated method is intended to "soft implement" a certain method in a loader specific interface + * that cannot be named in xplat code and thus cannot be checked with @Override. In this context, + * "soft implement" means to implement the method by matching the signature with the intended + * interface method. Examples of interfaces that we would use this for is IForgeItem or FabricItem. + * + *

The intent is that we audit such sites every major Minecraft version or so, to ensure that + * they still properly override the intended target. */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) public @interface SoftImplement { - /** - * What interface we're soft implementing - */ - String value(); + /** What interface we're soft implementing */ + String value(); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java b/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java index 8ddde5669b..eaf3fb5c8b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java @@ -7,6 +7,8 @@ import at.petrak.hexcasting.api.player.Sentinel; import at.petrak.hexcasting.xplat.IXplatAbstractions; import com.google.common.base.Suppliers; +import java.util.function.Consumer; +import java.util.function.Supplier; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; @@ -16,7 +18,6 @@ import net.minecraft.sounds.SoundEvents; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ArmorItem; @@ -29,206 +30,201 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.function.Consumer; -import java.util.function.Supplier; - public interface HexAPI { - String MOD_ID = "hexcasting"; - Logger LOGGER = LogManager.getLogger(MOD_ID); - - Supplier INSTANCE = Suppliers.memoize(() -> { - try { - return (HexAPI) Class.forName("at.petrak.hexcasting.common.impl.HexAPIImpl") - .getDeclaredConstructor().newInstance(); - } catch (ReflectiveOperationException e) { - LogManager.getLogger().warn("Unable to find HexAPIImpl, using a dummy"); - return new HexAPI() { - }; - } - }); - - /** - * Return the localization key for the given action. - *

- * Note we're allowed to have action resource keys on the client, just no actual actions. - *

- * Special handlers should be calling {@link SpecialHandler#getName()} - */ - default String getActionI18nKey(ResourceKey action) { - return "hexcasting.action.%s".formatted(action.location().toString()); - } - - default String getSpecialHandlerI18nKey(ResourceKey> action) { - return "hexcasting.special.%s".formatted(action.location().toString()); - } - - /** - * Currently introspection/retrospection/consideration are hardcoded, but at least their names won't be - */ - default String getRawHookI18nKey(ResourceLocation name) { - return "hexcasting.rawhook.%s".formatted(name); - } - - default Component getActionI18n(ResourceKey key, boolean isGreat) { - return Component.translatable(getActionI18nKey(key)) - .withStyle(isGreat ? ChatFormatting.GOLD : ChatFormatting.LIGHT_PURPLE); - } - - default Component getSpecialHandlerI18n(ResourceKey> key) { - return Component.translatable(getSpecialHandlerI18nKey(key)) - .withStyle(ChatFormatting.LIGHT_PURPLE); - } - - default Component getRawHookI18n(ResourceLocation name) { - return Component.translatable(getRawHookI18nKey(name)).withStyle(ChatFormatting.LIGHT_PURPLE); - } - - /** - * Register an entity with the given ID to have its velocity as perceived by OpEntityVelocity be different - * than it's "normal" velocity - */ - // Should be OK to use the type directly as the key as they're singleton identity objects - default void registerSpecialVelocityGetter(EntityType key, EntityVelocityGetter getter) { - } - - /** - * If the entity has had a special getter registered with {@link HexAPI#registerSpecialVelocityGetter} then - * return that, otherwise return its normal delta movement - */ - default Vec3 getEntityVelocitySpecial(Entity entity) { - return entity.getDeltaMovement(); - } - - @FunctionalInterface - interface EntityVelocityGetter { - Vec3 getVelocity(T entity); - } - - /** - * Register an entity type to have a custom behavior when getting brainswept. - *

- * This knocks out the normal behavior; if you want that behavior you should call - */ - default void registerCustomBrainsweepingBehavior(EntityType key, Consumer hook) { - } - - /** - * The default behavior when an entity gets brainswept. - *

- * Something registered with {@link HexAPI#registerCustomBrainsweepingBehavior} doesn't call this automatically; - * you can use this to add things on top of the default behavior - */ - default Consumer defaultBrainsweepingBehavior() { - return mob -> { - }; - } - - /** - * If something special's been returned with {@link HexAPI#registerCustomBrainsweepingBehavior}, return that, - * otherwise return the default behavior - */ - default Consumer getBrainsweepBehavior(EntityType mobType) { - return mob -> { - }; - } - - /** - * Brainsweep (flay the mind of) the given mob. - *

- * This ignores the unbrainsweepable tag. - */ - default void brainsweep(Mob mob) { - var type = (EntityType) mob.getType(); - var behavior = this.getBrainsweepBehavior(type); - var erasedBehavior = (Consumer) behavior; - erasedBehavior.accept(mob); - - IXplatAbstractions.INSTANCE.setBrainsweepAddlData(mob); - } - - default boolean isBrainswept(Mob mob) { - return IXplatAbstractions.INSTANCE.isBrainswept(mob); - } - - // - @Nullable - default Sentinel getSentinel(ServerPlayer player) { - return null; - } - - @Nullable - default ADMediaHolder findMediaHolder(ItemStack stack) { - return null; - } - - default FrozenPigment getColorizer(Player player) { - return FrozenPigment.DEFAULT.get(); - } - - ArmorMaterial DUMMY_ARMOR_MATERIAL = new ArmorMaterial() { - @Override - public int getDurabilityForType(ArmorItem.Type type) { - return 0; - } - - @Override - public int getDefenseForType(ArmorItem.Type type) { - return 0; - } - - @Override - public int getEnchantmentValue() { - return 0; - } - - @NotNull - @Override - public SoundEvent getEquipSound() { - return SoundEvents.ARMOR_EQUIP_LEATHER; - } - - @NotNull - @Override - public Ingredient getRepairIngredient() { - return Ingredient.EMPTY; - } - - @Override - public String getName() { - return "missingno"; - } - - @Override - public float getToughness() { - return 0; - } - - @Override - public float getKnockbackResistance() { - return 0; - } - }; - - default ArmorMaterial robesMaterial() { - return DUMMY_ARMOR_MATERIAL; - } - - /** - * Location in the userdata of the ravenmind - */ - String RAVENMIND_USERDATA = modLoc("ravenmind").toString(); - /** - * Location in the userdata of the number of ops executed - */ - String OP_COUNT_USERDATA = modLoc("op_count").toString(); - - String MARKED_MOVED_USERDATA = modLoc("impulsed").toString(); - - static HexAPI instance() { - return INSTANCE.get(); - } - - static ResourceLocation modLoc(String s) { - return new ResourceLocation(MOD_ID, s); - } + String MOD_ID = "hexcasting"; + Logger LOGGER = LogManager.getLogger(MOD_ID); + + Supplier INSTANCE = + Suppliers.memoize( + () -> { + try { + return (HexAPI) + Class.forName("at.petrak.hexcasting.common.impl.HexAPIImpl") + .getDeclaredConstructor() + .newInstance(); + } catch (ReflectiveOperationException e) { + LogManager.getLogger().warn("Unable to find HexAPIImpl, using a dummy"); + return new HexAPI() {}; + } + }); + + /** + * Return the localization key for the given action. + * + *

Note we're allowed to have action resource keys on the client, just no actual + * actions. + * + *

Special handlers should be calling {@link SpecialHandler#getName()} + */ + default String getActionI18nKey(ResourceKey action) { + return "hexcasting.action.%s".formatted(action.location().toString()); + } + + default String getSpecialHandlerI18nKey(ResourceKey> action) { + return "hexcasting.special.%s".formatted(action.location().toString()); + } + + /** + * Currently introspection/retrospection/consideration are hardcoded, but at least their names + * won't be + */ + default String getRawHookI18nKey(ResourceLocation name) { + return "hexcasting.rawhook.%s".formatted(name); + } + + default Component getActionI18n(ResourceKey key, boolean isGreat) { + return Component.translatable(getActionI18nKey(key)) + .withStyle(isGreat ? ChatFormatting.GOLD : ChatFormatting.LIGHT_PURPLE); + } + + default Component getSpecialHandlerI18n(ResourceKey> key) { + return Component.translatable(getSpecialHandlerI18nKey(key)) + .withStyle(ChatFormatting.LIGHT_PURPLE); + } + + default Component getRawHookI18n(ResourceLocation name) { + return Component.translatable(getRawHookI18nKey(name)).withStyle(ChatFormatting.LIGHT_PURPLE); + } + + /** + * Register an entity with the given ID to have its velocity as perceived by OpEntityVelocity be + * different than it's "normal" velocity + */ + // Should be OK to use the type directly as the key as they're singleton identity objects + default void registerSpecialVelocityGetter( + EntityType key, EntityVelocityGetter getter) {} + + /** + * If the entity has had a special getter registered with {@link + * HexAPI#registerSpecialVelocityGetter} then return that, otherwise return its normal delta + * movement + */ + default Vec3 getEntityVelocitySpecial(Entity entity) { + return entity.getDeltaMovement(); + } + + @FunctionalInterface + interface EntityVelocityGetter { + Vec3 getVelocity(T entity); + } + + /** + * Register an entity type to have a custom behavior when getting brainswept. + * + *

This knocks out the normal behavior; if you want that behavior you should call + */ + default void registerCustomBrainsweepingBehavior( + EntityType key, Consumer hook) {} + + /** + * The default behavior when an entity gets brainswept. + * + *

Something registered with {@link HexAPI#registerCustomBrainsweepingBehavior} doesn't call + * this automatically; you can use this to add things on top of the default behavior + */ + default Consumer defaultBrainsweepingBehavior() { + return mob -> {}; + } + + /** + * If something special's been returned with {@link HexAPI#registerCustomBrainsweepingBehavior}, + * return that, otherwise return the default behavior + */ + default Consumer getBrainsweepBehavior(EntityType mobType) { + return mob -> {}; + } + + /** + * Brainsweep (flay the mind of) the given mob. + * + *

This ignores the unbrainsweepable tag. + */ + default void brainsweep(Mob mob) { + var type = (EntityType) mob.getType(); + var behavior = this.getBrainsweepBehavior(type); + var erasedBehavior = (Consumer) behavior; + erasedBehavior.accept(mob); + + IXplatAbstractions.INSTANCE.setBrainsweepAddlData(mob); + } + + default boolean isBrainswept(Mob mob) { + return IXplatAbstractions.INSTANCE.isBrainswept(mob); + } + + // + @Nullable default Sentinel getSentinel(ServerPlayer player) { + return null; + } + + @Nullable default ADMediaHolder findMediaHolder(ItemStack stack) { + return null; + } + + default FrozenPigment getColorizer(Player player) { + return FrozenPigment.DEFAULT.get(); + } + + ArmorMaterial DUMMY_ARMOR_MATERIAL = + new ArmorMaterial() { + @Override + public int getDurabilityForType(ArmorItem.Type type) { + return 0; + } + + @Override + public int getDefenseForType(ArmorItem.Type type) { + return 0; + } + + @Override + public int getEnchantmentValue() { + return 0; + } + + @NotNull @Override + public SoundEvent getEquipSound() { + return SoundEvents.ARMOR_EQUIP_LEATHER; + } + + @NotNull @Override + public Ingredient getRepairIngredient() { + return Ingredient.EMPTY; + } + + @Override + public String getName() { + return "missingno"; + } + + @Override + public float getToughness() { + return 0; + } + + @Override + public float getKnockbackResistance() { + return 0; + } + }; + + default ArmorMaterial robesMaterial() { + return DUMMY_ARMOR_MATERIAL; + } + + /** Location in the userdata of the ravenmind */ + String RAVENMIND_USERDATA = modLoc("ravenmind").toString(); + + /** Location in the userdata of the number of ops executed */ + String OP_COUNT_USERDATA = modLoc("op_count").toString(); + + String MARKED_MOVED_USERDATA = modLoc("impulsed").toString(); + + static HexAPI instance() { + return INSTANCE.get(); + } + + static ResourceLocation modLoc(String s) { + return new ResourceLocation(MOD_ID, s); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADHexHolder.java b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADHexHolder.java index 15defd673b..654086587a 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADHexHolder.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADHexHolder.java @@ -2,23 +2,21 @@ import at.petrak.hexcasting.api.casting.iota.Iota; import at.petrak.hexcasting.api.pigment.FrozenPigment; +import java.util.List; import net.minecraft.server.level.ServerLevel; import org.jetbrains.annotations.Nullable; -import java.util.List; - public interface ADHexHolder { - boolean canDrawMediaFromInventory(); + boolean canDrawMediaFromInventory(); - boolean hasHex(); + boolean hasHex(); - @Nullable - List getHex(ServerLevel level); + @Nullable List getHex(ServerLevel level); - void writeHex(List patterns, @Nullable FrozenPigment pigment, long media); + void writeHex(List patterns, @Nullable FrozenPigment pigment, long media); - void clearHex(); + void clearHex(); - @Nullable FrozenPigment getPigment(); + @Nullable FrozenPigment getPigment(); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADIotaHolder.java b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADIotaHolder.java index 7b8fcc1c95..1cf5910a67 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADIotaHolder.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADIotaHolder.java @@ -7,31 +7,28 @@ import org.jetbrains.annotations.Nullable; public interface ADIotaHolder { - @Nullable - CompoundTag readIotaTag(); + @Nullable CompoundTag readIotaTag(); - @Nullable - default Iota readIota(ServerLevel world) { - var tag = readIotaTag(); - if (tag != null) { - return IotaType.deserialize(tag, world); - } else { - return null; - } - } + @Nullable default Iota readIota(ServerLevel world) { + var tag = readIotaTag(); + if (tag != null) { + return IotaType.deserialize(tag, world); + } else { + return null; + } + } - @Nullable - default Iota emptyIota() { - return null; - } + @Nullable default Iota emptyIota() { + return null; + } - /** - * @return if the writing succeeded/would succeed - */ - boolean writeIota(@Nullable Iota iota, boolean simulate); + /** + * @return if the writing succeeded/would succeed + */ + boolean writeIota(@Nullable Iota iota, boolean simulate); - /** - * @return whether it is possible to write to this IotaHolder - */ - boolean writeable(); + /** + * @return whether it is possible to write to this IotaHolder + */ + boolean writeable(); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADMediaHolder.java b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADMediaHolder.java index 547f6afc31..82d886dc41 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADMediaHolder.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADMediaHolder.java @@ -4,108 +4,103 @@ public interface ADMediaHolder { - /** - * Use {@code withdrawMedia(-1, true)} - * - * @see ADMediaHolder#withdrawMedia(long, boolean) - */ - @ApiStatus.OverrideOnly - long getMedia(); + /** + * Use {@code withdrawMedia(-1, true)} + * + * @see ADMediaHolder#withdrawMedia(long, boolean) + */ + @ApiStatus.OverrideOnly + long getMedia(); - /** - * Use {@code withdrawMedia(-1, true) + insertMedia(-1, true)} where possible - * - * @see ADMediaHolder#insertMedia(long, boolean) - * @see ADMediaHolder#withdrawMedia(long, boolean) - */ - @ApiStatus.OverrideOnly - long getMaxMedia(); + /** + * Use {@code withdrawMedia(-1, true) + insertMedia(-1, true)} where possible + * + * @see ADMediaHolder#insertMedia(long, boolean) + * @see ADMediaHolder#withdrawMedia(long, boolean) + */ + @ApiStatus.OverrideOnly + long getMaxMedia(); - /** - * Use {@code insertMedia(media - withdrawMedia(-1, true), false)} where possible - * - * @see ADMediaHolder#insertMedia(long, boolean) - * @see ADMediaHolder#withdrawMedia(long, boolean) - */ - @ApiStatus.OverrideOnly - void setMedia(long media); + /** + * Use {@code insertMedia(media - withdrawMedia(-1, true), false)} where possible + * + * @see ADMediaHolder#insertMedia(long, boolean) + * @see ADMediaHolder#withdrawMedia(long, boolean) + */ + @ApiStatus.OverrideOnly + void setMedia(long media); - /** - * Whether this media holder can have media inserted into it. - */ - boolean canRecharge(); + /** Whether this media holder can have media inserted into it. */ + boolean canRecharge(); - /** - * Whether this media holder can be extracted from. - */ - boolean canProvide(); + /** Whether this media holder can be extracted from. */ + boolean canProvide(); - /** - * The priority for this media holder to be selected when casting a hex. Higher priorities are taken first. - *

- * By default, - * * Charged Amethyst has priority 1000 - * * Amethyst Shards have priority 2000 - * * Amethyst Dust has priority 3000 - * * Items which hold media have priority 4000 - */ - int getConsumptionPriority(); + /** + * The priority for this media holder to be selected when casting a hex. Higher priorities are + * taken first. + * + *

By default, * Charged Amethyst has priority 1000 * Amethyst Shards have priority 2000 * + * Amethyst Dust has priority 3000 * Items which hold media have priority 4000 + */ + int getConsumptionPriority(); - /** - * Whether the media inside this media holder may be used to construct a battery. - */ - boolean canConstructBattery(); + /** Whether the media inside this media holder may be used to construct a battery. */ + boolean canConstructBattery(); - /** - * Withdraws media from the holder. Returns the amount of media extracted, which may be less or more than the cost. - *

- * Even if {@link ADMediaHolder#canProvide} is false, you can still withdraw media this way. - *

- * Withdrawing a negative amount will act as though you attempted to withdraw as much media as the holder contains. - */ - default long withdrawMedia(long cost, boolean simulate) { - var mediaHere = getMedia(); - if (cost < 0) { - cost = mediaHere; - } - if (!simulate) { - var mediaLeft = mediaHere - cost; - setMedia(mediaLeft); - } - return Math.min(cost, mediaHere); - } + /** + * Withdraws media from the holder. Returns the amount of media extracted, which may be less or + * more than the cost. + * + *

Even if {@link ADMediaHolder#canProvide} is false, you can still withdraw media this way. + * + *

Withdrawing a negative amount will act as though you attempted to withdraw as much media as + * the holder contains. + */ + default long withdrawMedia(long cost, boolean simulate) { + var mediaHere = getMedia(); + if (cost < 0) { + cost = mediaHere; + } + if (!simulate) { + var mediaLeft = mediaHere - cost; + setMedia(mediaLeft); + } + return Math.min(cost, mediaHere); + } - /** - * Inserts media into the holder. Returns the amount of media inserted, which may be less than the requested amount. - *

- * Even if {@link ADMediaHolder#canRecharge} is false, you can still insert media this way. - *

- * Inserting a negative amount will act as though you attempted to insert exactly as much media as the holder was - * missing. - */ - default long insertMedia(long amount, boolean simulate) { - var mediaHere = getMedia(); - long emptySpace = getMaxMedia() - mediaHere; - if (emptySpace <= 0) { - return 0; - } - if (amount < 0) { - amount = emptySpace; - } + /** + * Inserts media into the holder. Returns the amount of media inserted, which may be less than the + * requested amount. + * + *

Even if {@link ADMediaHolder#canRecharge} is false, you can still insert media this way. + * + *

Inserting a negative amount will act as though you attempted to insert exactly as much media + * as the holder was missing. + */ + default long insertMedia(long amount, boolean simulate) { + var mediaHere = getMedia(); + long emptySpace = getMaxMedia() - mediaHere; + if (emptySpace <= 0) { + return 0; + } + if (amount < 0) { + amount = emptySpace; + } - long inserting = Math.min(amount, emptySpace); + long inserting = Math.min(amount, emptySpace); - if (!simulate) { - var newMedia = mediaHere + inserting; - setMedia(newMedia); - } - return inserting; - } + if (!simulate) { + var newMedia = mediaHere + inserting; + setMedia(newMedia); + } + return inserting; + } - int QUENCHED_ALLAY_PRIORITY = 800; - int QUENCHED_SHARD_PRIORITY = 900; - int CHARGED_AMETHYST_PRIORITY = 1000; - int AMETHYST_SHARD_PRIORITY = 2000; - int AMETHYST_DUST_PRIORITY = 3000; - int BATTERY_PRIORITY = 4000; + int QUENCHED_ALLAY_PRIORITY = 800; + int QUENCHED_SHARD_PRIORITY = 900; + int CHARGED_AMETHYST_PRIORITY = 1000; + int AMETHYST_SHARD_PRIORITY = 2000; + int AMETHYST_DUST_PRIORITY = 3000; + int BATTERY_PRIORITY = 4000; } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADPigment.java b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADPigment.java index 86984439c9..e5ed045b68 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADPigment.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADPigment.java @@ -1,38 +1,37 @@ package at.petrak.hexcasting.api.addldata; import at.petrak.hexcasting.api.pigment.ColorProvider; +import java.util.UUID; import net.minecraft.util.FastColor; import net.minecraft.util.Mth; import net.minecraft.world.phys.Vec3; -import java.util.UUID; - public interface ADPigment { - ColorProvider provideColor(UUID owner); - - static int morphBetweenColors(int[] colors, Vec3 gradientDir, float time, Vec3 position) { - float fIdx = Mth.positiveModulo(time + (float) gradientDir.dot(position), 1f) * colors.length; - - int baseIdx = Mth.floor(fIdx); - float tRaw = fIdx - baseIdx; - float t = tRaw < 0.5 ? 4 * tRaw * tRaw * tRaw : (float) (1 - Math.pow(-2 * tRaw + 2, 3) / 2); - int start = colors[baseIdx % colors.length]; - int end = colors[(baseIdx + 1) % colors.length]; - - var r1 = FastColor.ARGB32.red(start); - var g1 = FastColor.ARGB32.green(start); - var b1 = FastColor.ARGB32.blue(start); - var a1 = FastColor.ARGB32.alpha(start); - var r2 = FastColor.ARGB32.red(end); - var g2 = FastColor.ARGB32.green(end); - var b2 = FastColor.ARGB32.blue(end); - var a2 = FastColor.ARGB32.alpha(end); - - var r = Mth.lerp(t, r1, r2); - var g = Mth.lerp(t, g1, g2); - var b = Mth.lerp(t, b1, b2); - var a = Mth.lerp(t, a1, a2); - - return FastColor.ARGB32.color((int) a, (int) r, (int) g, (int) b); - } + ColorProvider provideColor(UUID owner); + + static int morphBetweenColors(int[] colors, Vec3 gradientDir, float time, Vec3 position) { + float fIdx = Mth.positiveModulo(time + (float) gradientDir.dot(position), 1f) * colors.length; + + int baseIdx = Mth.floor(fIdx); + float tRaw = fIdx - baseIdx; + float t = tRaw < 0.5 ? 4 * tRaw * tRaw * tRaw : (float) (1 - Math.pow(-2 * tRaw + 2, 3) / 2); + int start = colors[baseIdx % colors.length]; + int end = colors[(baseIdx + 1) % colors.length]; + + var r1 = FastColor.ARGB32.red(start); + var g1 = FastColor.ARGB32.green(start); + var b1 = FastColor.ARGB32.blue(start); + var a1 = FastColor.ARGB32.alpha(start); + var r2 = FastColor.ARGB32.red(end); + var g2 = FastColor.ARGB32.green(end); + var b2 = FastColor.ARGB32.blue(end); + var a2 = FastColor.ARGB32.alpha(end); + + var r = Mth.lerp(t, r1, r2); + var g = Mth.lerp(t, g1, g2); + var b = Mth.lerp(t, b1, b2); + var a = Mth.lerp(t, a1, a2); + + return FastColor.ARGB32.color((int) a, (int) r, (int) g, (int) b); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADVariantItem.java b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADVariantItem.java index 6b1a59f76d..925caddc01 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADVariantItem.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ADVariantItem.java @@ -1,9 +1,9 @@ package at.petrak.hexcasting.api.addldata; public interface ADVariantItem { - int numVariants(); - int getVariant(); + int numVariants(); + int getVariant(); - void setVariant(int variant); + void setVariant(int variant); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ItemDelegatingEntityIotaHolder.java b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ItemDelegatingEntityIotaHolder.java index 5af67a99ab..cbf4578f95 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/addldata/ItemDelegatingEntityIotaHolder.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/addldata/ItemDelegatingEntityIotaHolder.java @@ -3,6 +3,8 @@ import at.petrak.hexcasting.api.casting.iota.Iota; import at.petrak.hexcasting.common.entities.EntityWallScroll; import at.petrak.hexcasting.xplat.IXplatAbstractions; +import java.util.function.Consumer; +import java.util.function.Supplier; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.decoration.ItemFrame; @@ -10,80 +12,79 @@ import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.Nullable; -import java.util.function.Consumer; -import java.util.function.Supplier; - public abstract class ItemDelegatingEntityIotaHolder implements ADIotaHolder { - private final Supplier stackSupplier; + private final Supplier stackSupplier; - private final Consumer save; + private final Consumer save; - public ItemDelegatingEntityIotaHolder(Supplier stackSupplier, Consumer save) { - this.stackSupplier = stackSupplier; - this.save = save; - } + public ItemDelegatingEntityIotaHolder( + Supplier stackSupplier, Consumer save) { + this.stackSupplier = stackSupplier; + this.save = save; + } - @Override - public @Nullable CompoundTag readIotaTag() { - var delegate = IXplatAbstractions.INSTANCE.findDataHolder(this.stackSupplier.get()); - return delegate == null ? null : delegate.readIotaTag(); - } + @Override + public @Nullable CompoundTag readIotaTag() { + var delegate = IXplatAbstractions.INSTANCE.findDataHolder(this.stackSupplier.get()); + return delegate == null ? null : delegate.readIotaTag(); + } - @Override - public boolean writeable() { - var delegate = IXplatAbstractions.INSTANCE.findDataHolder(this.stackSupplier.get()); - return delegate != null && delegate.writeable(); - } + @Override + public boolean writeable() { + var delegate = IXplatAbstractions.INSTANCE.findDataHolder(this.stackSupplier.get()); + return delegate != null && delegate.writeable(); + } - @Override - public boolean writeIota(@Nullable Iota datum, boolean simulate) { - var stacc = this.stackSupplier.get(); - var delegate = IXplatAbstractions.INSTANCE.findDataHolder(stacc); - var success = delegate != null && delegate.writeIota(datum, simulate); - if (success && !simulate) { - this.save.accept(stacc); - } - return success; - } + @Override + public boolean writeIota(@Nullable Iota datum, boolean simulate) { + var stacc = this.stackSupplier.get(); + var delegate = IXplatAbstractions.INSTANCE.findDataHolder(stacc); + var success = delegate != null && delegate.writeIota(datum, simulate); + if (success && !simulate) { + this.save.accept(stacc); + } + return success; + } - @Override - public @Nullable Iota readIota(ServerLevel world) { - var delegate = IXplatAbstractions.INSTANCE.findDataHolder(this.stackSupplier.get()); - return delegate == null ? null : delegate.readIota(world); - } + @Override + public @Nullable Iota readIota(ServerLevel world) { + var delegate = IXplatAbstractions.INSTANCE.findDataHolder(this.stackSupplier.get()); + return delegate == null ? null : delegate.readIota(world); + } - @Override - public @Nullable Iota emptyIota() { - var delegate = IXplatAbstractions.INSTANCE.findDataHolder(this.stackSupplier.get()); - return delegate == null ? null : delegate.emptyIota(); - } + @Override + public @Nullable Iota emptyIota() { + var delegate = IXplatAbstractions.INSTANCE.findDataHolder(this.stackSupplier.get()); + return delegate == null ? null : delegate.emptyIota(); + } - public static class ToItemEntity extends ItemDelegatingEntityIotaHolder { - public ToItemEntity(ItemEntity entity) { - super(entity::getItem, stack -> { - // https://github.com/VazkiiMods/Botania/blob/e6d095ff5010074b45408d6cce8ee1e328af3383/Xplat/src/main/java/vazkii/botania/common/helper/EntityHelper.java#L16 - entity.setItem(ItemStack.EMPTY); - entity.setItem(stack); - entity.setUnlimitedLifetime(); - }); - } - } + public static class ToItemEntity extends ItemDelegatingEntityIotaHolder { + public ToItemEntity(ItemEntity entity) { + super( + entity::getItem, + stack -> { + // https://github.com/VazkiiMods/Botania/blob/e6d095ff5010074b45408d6cce8ee1e328af3383/Xplat/src/main/java/vazkii/botania/common/helper/EntityHelper.java#L16 + entity.setItem(ItemStack.EMPTY); + entity.setItem(stack); + entity.setUnlimitedLifetime(); + }); + } + } - public static class ToItemFrame extends ItemDelegatingEntityIotaHolder { - public ToItemFrame(ItemFrame entity) { - super(entity::getItem, entity::setItem); - } - } + public static class ToItemFrame extends ItemDelegatingEntityIotaHolder { + public ToItemFrame(ItemFrame entity) { + super(entity::getItem, entity::setItem); + } + } - public static class ToWallScroll extends ItemDelegatingEntityIotaHolder { - public ToWallScroll(EntityWallScroll entity) { - super(() -> entity.scroll.copy(), stack -> { - }); - } + public static class ToWallScroll extends ItemDelegatingEntityIotaHolder { + public ToWallScroll(EntityWallScroll entity) { + super(() -> entity.scroll.copy(), stack -> {}); + } - @Override - public boolean writeIota(@Nullable Iota datum, boolean simulate) { - return false; - } - } + @Override + public boolean writeIota(@Nullable Iota datum, boolean simulate) { + return false; + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/addldata/package-info.java b/Common/src/main/java/at/petrak/hexcasting/api/addldata/package-info.java index a11787c85a..0e6736a833 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/addldata/package-info.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/addldata/package-info.java @@ -1,10 +1,11 @@ /** * An "Additional Data," or AD, is what I am calling the abstraction over capabilities on Forge and * cardinal components on Fabric. - *

- * An {@code ADFooBar} in this package will be implemented by a {@code CCFooBar} on Fabric. - * On Forge, there are a set of private records that implement them. - *

- * The point is, this provides an interface for interacting with however whatever platform sticks extra info on stuff. + * + *

An {@code ADFooBar} in this package will be implemented by a {@code CCFooBar} on Fabric. On + * Forge, there are a set of private records that implement them. + * + *

The point is, this provides an interface for interacting with however whatever platform sticks + * extra info on stuff. */ package at.petrak.hexcasting.api.addldata; diff --git a/Common/src/main/java/at/petrak/hexcasting/api/advancements/FailToCastGreatSpellTrigger.java b/Common/src/main/java/at/petrak/hexcasting/api/advancements/FailToCastGreatSpellTrigger.java index fe85fec5b3..05e169f172 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/advancements/FailToCastGreatSpellTrigger.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/advancements/FailToCastGreatSpellTrigger.java @@ -5,35 +5,38 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; -public class FailToCastGreatSpellTrigger extends SimpleCriterionTrigger { - private static final ResourceLocation ID = new ResourceLocation("hexcasting", "fail_to_cast_great_spell"); - - @Override - public ResourceLocation getId() { - return ID; - } - - @Override - protected Instance createInstance(JsonObject json, ContextAwarePredicate predicate, DeserializationContext context) { - return new Instance(predicate); - } - - public void trigger(ServerPlayer player) { - super.trigger(player, e -> true); - } - - public static class Instance extends AbstractCriterionTriggerInstance { - public Instance(ContextAwarePredicate predicate) { - super(ID, predicate); - } - - @Override - public ResourceLocation getCriterion() { - return ID; - } - - public JsonObject serializeToJson(SerializationContext pConditions) { - return new JsonObject(); - } - } +public class FailToCastGreatSpellTrigger + extends SimpleCriterionTrigger { + private static final ResourceLocation ID = + new ResourceLocation("hexcasting", "fail_to_cast_great_spell"); + + @Override + public ResourceLocation getId() { + return ID; + } + + @Override + protected Instance createInstance( + JsonObject json, ContextAwarePredicate predicate, DeserializationContext context) { + return new Instance(predicate); + } + + public void trigger(ServerPlayer player) { + super.trigger(player, e -> true); + } + + public static class Instance extends AbstractCriterionTriggerInstance { + public Instance(ContextAwarePredicate predicate) { + super(ID, predicate); + } + + @Override + public ResourceLocation getCriterion() { + return ID; + } + + public JsonObject serializeToJson(SerializationContext pConditions) { + return new JsonObject(); + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/advancements/HexAdvancementTriggers.java b/Common/src/main/java/at/petrak/hexcasting/api/advancements/HexAdvancementTriggers.java index 16af02ab39..979edd4ca1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/advancements/HexAdvancementTriggers.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/advancements/HexAdvancementTriggers.java @@ -3,13 +3,14 @@ import at.petrak.hexcasting.mixin.accessor.CriteriaTriggersAccessor; public class HexAdvancementTriggers { - public static final OvercastTrigger OVERCAST_TRIGGER = new OvercastTrigger(); - public static final SpendMediaTrigger SPEND_MEDIA_TRIGGER = new SpendMediaTrigger(); - public static final FailToCastGreatSpellTrigger FAIL_GREAT_SPELL_TRIGGER = new FailToCastGreatSpellTrigger(); + public static final OvercastTrigger OVERCAST_TRIGGER = new OvercastTrigger(); + public static final SpendMediaTrigger SPEND_MEDIA_TRIGGER = new SpendMediaTrigger(); + public static final FailToCastGreatSpellTrigger FAIL_GREAT_SPELL_TRIGGER = + new FailToCastGreatSpellTrigger(); - public static void registerTriggers() { - CriteriaTriggersAccessor.hex$register(OVERCAST_TRIGGER); - CriteriaTriggersAccessor.hex$register(SPEND_MEDIA_TRIGGER); - CriteriaTriggersAccessor.hex$register(FAIL_GREAT_SPELL_TRIGGER); - } + public static void registerTriggers() { + CriteriaTriggersAccessor.hex$register(OVERCAST_TRIGGER); + CriteriaTriggersAccessor.hex$register(SPEND_MEDIA_TRIGGER); + CriteriaTriggersAccessor.hex$register(FAIL_GREAT_SPELL_TRIGGER); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/advancements/MinMaxLongs.java b/Common/src/main/java/at/petrak/hexcasting/api/advancements/MinMaxLongs.java index 0be502c86e..7f64df6b03 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/advancements/MinMaxLongs.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/advancements/MinMaxLongs.java @@ -4,82 +4,81 @@ import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.BuiltInExceptionProvider; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import net.minecraft.advancements.critereon.MinMaxBounds; -import net.minecraft.util.GsonHelper; - -import javax.annotation.Nullable; import java.util.Objects; import java.util.function.Function; +import javax.annotation.Nullable; +import net.minecraft.advancements.critereon.MinMaxBounds; +import net.minecraft.util.GsonHelper; public class MinMaxLongs extends MinMaxBounds { - public static final MinMaxLongs ANY = new MinMaxLongs(null, null); - @Nullable - private final Long minSq; - @Nullable - private final Long maxSq; + public static final MinMaxLongs ANY = new MinMaxLongs(null, null); + @Nullable private final Long minSq; + @Nullable private final Long maxSq; - private static MinMaxLongs create(StringReader reader, @Nullable Long min, @Nullable Long max) throws CommandSyntaxException { - if (min != null && max != null && min > max) { - throw ERROR_SWAPPED.createWithContext(reader); - } else { - return new MinMaxLongs(min, max); - } - } + private static MinMaxLongs create(StringReader reader, @Nullable Long min, @Nullable Long max) + throws CommandSyntaxException { + if (min != null && max != null && min > max) { + throw ERROR_SWAPPED.createWithContext(reader); + } else { + return new MinMaxLongs(min, max); + } + } - @Nullable - private static Long squareOpt(@Nullable Long l) { - return l == null ? null : l * l; - } + @Nullable private static Long squareOpt(@Nullable Long l) { + return l == null ? null : l * l; + } - private MinMaxLongs(@Nullable Long min, @Nullable Long max) { - super(min, max); - this.minSq = squareOpt(min); - this.maxSq = squareOpt(max); - } + private MinMaxLongs(@Nullable Long min, @Nullable Long max) { + super(min, max); + this.minSq = squareOpt(min); + this.maxSq = squareOpt(max); + } - public static MinMaxLongs exactly(long l) { - return new MinMaxLongs(l, l); - } + public static MinMaxLongs exactly(long l) { + return new MinMaxLongs(l, l); + } - public static MinMaxLongs between(long min, long max) { - return new MinMaxLongs(min, max); - } + public static MinMaxLongs between(long min, long max) { + return new MinMaxLongs(min, max); + } - public static MinMaxLongs atLeast(long min) { - return new MinMaxLongs(min, null); - } + public static MinMaxLongs atLeast(long min) { + return new MinMaxLongs(min, null); + } - public static MinMaxLongs atMost(long max) { - return new MinMaxLongs(null, max); - } + public static MinMaxLongs atMost(long max) { + return new MinMaxLongs(null, max); + } - public boolean matches(long l) { - if (this.min != null && this.min > l) { - return false; - } else { - return this.max == null || this.max >= l; - } - } + public boolean matches(long l) { + if (this.min != null && this.min > l) { + return false; + } else { + return this.max == null || this.max >= l; + } + } - public boolean matchesSqr(long l) { - if (this.minSq != null && this.minSq > l) { - return false; - } else { - return this.maxSq == null || this.maxSq >= l; - } - } + public boolean matchesSqr(long l) { + if (this.minSq != null && this.minSq > l) { + return false; + } else { + return this.maxSq == null || this.maxSq >= l; + } + } - public static MinMaxLongs fromJson(@Nullable JsonElement json) { - return fromJson(json, ANY, GsonHelper::convertToLong, MinMaxLongs::new); - } + public static MinMaxLongs fromJson(@Nullable JsonElement json) { + return fromJson(json, ANY, GsonHelper::convertToLong, MinMaxLongs::new); + } - public static MinMaxLongs fromReader(StringReader reader) throws CommandSyntaxException { - return fromReader(reader, (l) -> l); - } + public static MinMaxLongs fromReader(StringReader reader) throws CommandSyntaxException { + return fromReader(reader, (l) -> l); + } - public static MinMaxLongs fromReader(StringReader reader, Function map) throws CommandSyntaxException { - BuiltInExceptionProvider builtInExceptions = CommandSyntaxException.BUILT_IN_EXCEPTIONS; - Objects.requireNonNull(builtInExceptions); - return fromReader(reader, MinMaxLongs::create, Long::parseLong, builtInExceptions::readerInvalidInt, map); - } + public static MinMaxLongs fromReader(StringReader reader, Function map) + throws CommandSyntaxException { + BuiltInExceptionProvider builtInExceptions = CommandSyntaxException.BUILT_IN_EXCEPTIONS; + Objects.requireNonNull(builtInExceptions); + return fromReader( + reader, MinMaxLongs::create, Long::parseLong, builtInExceptions::readerInvalidInt, map); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/advancements/OvercastTrigger.java b/Common/src/main/java/at/petrak/hexcasting/api/advancements/OvercastTrigger.java index b1661e869e..2401158ebf 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/advancements/OvercastTrigger.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/advancements/OvercastTrigger.java @@ -10,77 +10,83 @@ // https://github.com/VazkiiMods/Botania/blob/b8706e2e0bba20f67f1e103559a4ce39d63d48f9/src/main/java/vazkii/botania/common/advancements/CorporeaRequestTrigger.java public class OvercastTrigger extends SimpleCriterionTrigger { - private static final ResourceLocation ID = new ResourceLocation("hexcasting", "overcast"); + private static final ResourceLocation ID = new ResourceLocation("hexcasting", "overcast"); - private static final String TAG_MEDIA_GENERATED = "media_generated"; - private static final String TAG_HEALTH_USED = "health_used"; - // HEY KIDS DID YOYU KNOW THERE'S NOT A CRITERIA FOR HOW MUCH ***HEALTH*** AN ENTITY HAS - private static final String TAG_HEALTH_LEFT = - "mojang_i_am_begging_and_crying_please_add_an_entity_health_criterion"; + private static final String TAG_MEDIA_GENERATED = "media_generated"; + private static final String TAG_HEALTH_USED = "health_used"; + // HEY KIDS DID YOYU KNOW THERE'S NOT A CRITERIA FOR HOW MUCH ***HEALTH*** AN ENTITY HAS + private static final String TAG_HEALTH_LEFT = + "mojang_i_am_begging_and_crying_please_add_an_entity_health_criterion"; - @Override - public ResourceLocation getId() { - return ID; - } + @Override + public ResourceLocation getId() { + return ID; + } - @Override - protected Instance createInstance(JsonObject json, ContextAwarePredicate predicate, - DeserializationContext pContext) { - return new Instance(predicate, - MinMaxBounds.Ints.fromJson(json.get(TAG_MEDIA_GENERATED)), - MinMaxBounds.Doubles.fromJson(json.get(TAG_HEALTH_USED)), - MinMaxBounds.Doubles.fromJson(json.get(TAG_HEALTH_LEFT))); - } + @Override + protected Instance createInstance( + JsonObject json, ContextAwarePredicate predicate, DeserializationContext pContext) { + return new Instance( + predicate, + MinMaxBounds.Ints.fromJson(json.get(TAG_MEDIA_GENERATED)), + MinMaxBounds.Doubles.fromJson(json.get(TAG_HEALTH_USED)), + MinMaxBounds.Doubles.fromJson(json.get(TAG_HEALTH_LEFT))); + } - public void trigger(ServerPlayer player, int mediaGenerated) { - super.trigger(player, inst -> { - var mediaToHealth = HexConfig.common().mediaToHealthRate(); - var healthUsed = mediaGenerated / mediaToHealth; - return inst.test(mediaGenerated, healthUsed / player.getMaxHealth(), player.getHealth()); - }); - } + public void trigger(ServerPlayer player, int mediaGenerated) { + super.trigger( + player, + inst -> { + var mediaToHealth = HexConfig.common().mediaToHealthRate(); + var healthUsed = mediaGenerated / mediaToHealth; + return inst.test(mediaGenerated, healthUsed / player.getMaxHealth(), player.getHealth()); + }); + } - public static class Instance extends AbstractCriterionTriggerInstance { - protected final MinMaxBounds.Ints mediaGenerated; - // This is the *proporttion* of the health bar. - protected final MinMaxBounds.Doubles healthUsed; - // DID YOU KNOW THERES ONE TO CHECK THE WORLD TIME, BUT NOT THE HEALTH!? - protected final MinMaxBounds.Doubles healthLeft; + public static class Instance extends AbstractCriterionTriggerInstance { + protected final MinMaxBounds.Ints mediaGenerated; + // This is the *proporttion* of the health bar. + protected final MinMaxBounds.Doubles healthUsed; + // DID YOU KNOW THERES ONE TO CHECK THE WORLD TIME, BUT NOT THE HEALTH!? + protected final MinMaxBounds.Doubles healthLeft; - public Instance(ContextAwarePredicate predicate, MinMaxBounds.Ints mediaGenerated, - MinMaxBounds.Doubles healthUsed, MinMaxBounds.Doubles healthLeft) { - super(OvercastTrigger.ID, predicate); - this.mediaGenerated = mediaGenerated; - this.healthUsed = healthUsed; - // DID YOU KNOW THERE'S ONE TO CHECK THE FUCKING C A T T Y P E BUT NOT THE HEALTH - this.healthLeft = healthLeft; - } + public Instance( + ContextAwarePredicate predicate, + MinMaxBounds.Ints mediaGenerated, + MinMaxBounds.Doubles healthUsed, + MinMaxBounds.Doubles healthLeft) { + super(OvercastTrigger.ID, predicate); + this.mediaGenerated = mediaGenerated; + this.healthUsed = healthUsed; + // DID YOU KNOW THERE'S ONE TO CHECK THE FUCKING C A T T Y P E BUT NOT THE HEALTH + this.healthLeft = healthLeft; + } - @Override - public ResourceLocation getCriterion() { - return ID; - } + @Override + public ResourceLocation getCriterion() { + return ID; + } - @Override - public JsonObject serializeToJson(SerializationContext ctx) { - JsonObject json = super.serializeToJson(ctx); - if (!this.mediaGenerated.isAny()) { - json.add(TAG_MEDIA_GENERATED, this.mediaGenerated.serializeToJson()); - } - if (!this.healthUsed.isAny()) { - json.add(TAG_HEALTH_USED, this.healthUsed.serializeToJson()); - } - if (!this.healthLeft.isAny()) { - json.add(TAG_HEALTH_LEFT, this.healthLeft.serializeToJson()); - } - return json; - } + @Override + public JsonObject serializeToJson(SerializationContext ctx) { + JsonObject json = super.serializeToJson(ctx); + if (!this.mediaGenerated.isAny()) { + json.add(TAG_MEDIA_GENERATED, this.mediaGenerated.serializeToJson()); + } + if (!this.healthUsed.isAny()) { + json.add(TAG_HEALTH_USED, this.healthUsed.serializeToJson()); + } + if (!this.healthLeft.isAny()) { + json.add(TAG_HEALTH_LEFT, this.healthLeft.serializeToJson()); + } + return json; + } - private boolean test(int mediaGeneratedIn, double healthUsedIn, float healthLeftIn) { - return this.mediaGenerated.matches(mediaGeneratedIn) - && this.healthUsed.matches(healthUsedIn) - // DID YOU KNOW ALL THE ENEITYT PREDICATES ARE HARD-CODED AND YOU CANT MAKE NEW ONES - && this.healthLeft.matches(healthLeftIn); - } - } + private boolean test(int mediaGeneratedIn, double healthUsedIn, float healthLeftIn) { + return this.mediaGenerated.matches(mediaGeneratedIn) + && this.healthUsed.matches(healthUsedIn) + // DID YOU KNOW ALL THE ENEITYT PREDICATES ARE HARD-CODED AND YOU CANT MAKE NEW ONES + && this.healthLeft.matches(healthLeftIn); + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/advancements/SpendMediaTrigger.java b/Common/src/main/java/at/petrak/hexcasting/api/advancements/SpendMediaTrigger.java index d610807f59..07bc8cfbf0 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/advancements/SpendMediaTrigger.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/advancements/SpendMediaTrigger.java @@ -6,58 +6,59 @@ import net.minecraft.server.level.ServerPlayer; public class SpendMediaTrigger extends SimpleCriterionTrigger { - private static final ResourceLocation ID = new ResourceLocation("hexcasting", "spend_media"); - - private static final String TAG_MEDIA_SPENT = "media_spent"; - private static final String TAG_MEDIA_WASTED = "media_wasted"; - - @Override - public ResourceLocation getId() { - return ID; - } - - @Override - protected Instance createInstance(JsonObject json, ContextAwarePredicate predicate, - DeserializationContext context) { - return new Instance(predicate, - MinMaxLongs.fromJson(json.get(TAG_MEDIA_SPENT)), - MinMaxLongs.fromJson(json.get(TAG_MEDIA_WASTED))); - } - - public void trigger(ServerPlayer player, long mediaSpent, long mediaWasted) { - super.trigger(player, inst -> inst.test(mediaSpent, mediaWasted)); - } - - public static class Instance extends AbstractCriterionTriggerInstance { - protected final MinMaxLongs mediaSpent; - protected final MinMaxLongs mediaWasted; - - public Instance(ContextAwarePredicate predicate, MinMaxLongs mediaSpent, - MinMaxLongs mediaWasted) { - super(SpendMediaTrigger.ID, predicate); - this.mediaSpent = mediaSpent; - this.mediaWasted = mediaWasted; - } - - @Override - public ResourceLocation getCriterion() { - return ID; - } - - @Override - public JsonObject serializeToJson(SerializationContext ctx) { - JsonObject json = super.serializeToJson(ctx); - if (!this.mediaSpent.isAny()) { - json.add(TAG_MEDIA_SPENT, this.mediaSpent.serializeToJson()); - } - if (!this.mediaWasted.isAny()) { - json.add(TAG_MEDIA_WASTED, this.mediaWasted.serializeToJson()); - } - return json; - } - - private boolean test(long mediaSpentIn, long mediaWastedIn) { - return this.mediaSpent.matches(mediaSpentIn) && this.mediaWasted.matches(mediaWastedIn); - } - } + private static final ResourceLocation ID = new ResourceLocation("hexcasting", "spend_media"); + + private static final String TAG_MEDIA_SPENT = "media_spent"; + private static final String TAG_MEDIA_WASTED = "media_wasted"; + + @Override + public ResourceLocation getId() { + return ID; + } + + @Override + protected Instance createInstance( + JsonObject json, ContextAwarePredicate predicate, DeserializationContext context) { + return new Instance( + predicate, + MinMaxLongs.fromJson(json.get(TAG_MEDIA_SPENT)), + MinMaxLongs.fromJson(json.get(TAG_MEDIA_WASTED))); + } + + public void trigger(ServerPlayer player, long mediaSpent, long mediaWasted) { + super.trigger(player, inst -> inst.test(mediaSpent, mediaWasted)); + } + + public static class Instance extends AbstractCriterionTriggerInstance { + protected final MinMaxLongs mediaSpent; + protected final MinMaxLongs mediaWasted; + + public Instance( + ContextAwarePredicate predicate, MinMaxLongs mediaSpent, MinMaxLongs mediaWasted) { + super(SpendMediaTrigger.ID, predicate); + this.mediaSpent = mediaSpent; + this.mediaWasted = mediaWasted; + } + + @Override + public ResourceLocation getCriterion() { + return ID; + } + + @Override + public JsonObject serializeToJson(SerializationContext ctx) { + JsonObject json = super.serializeToJson(ctx); + if (!this.mediaSpent.isAny()) { + json.add(TAG_MEDIA_SPENT, this.mediaSpent.serializeToJson()); + } + if (!this.mediaWasted.isAny()) { + json.add(TAG_MEDIA_WASTED, this.mediaWasted.serializeToJson()); + } + return json; + } + + private boolean test(long mediaSpentIn, long mediaWastedIn) { + return this.mediaSpent.matches(mediaSpentIn) && this.mediaWasted.matches(mediaWastedIn); + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/block/HexBlockEntity.java b/Common/src/main/java/at/petrak/hexcasting/api/block/HexBlockEntity.java index e531ddb12d..e77702a7d6 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/block/HexBlockEntity.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/block/HexBlockEntity.java @@ -10,40 +10,39 @@ import net.minecraft.world.level.block.state.BlockState; public abstract class HexBlockEntity extends BlockEntity { - public HexBlockEntity(BlockEntityType pType, BlockPos pWorldPosition, BlockState pBlockState) { - super(pType, pWorldPosition, pBlockState); - } - - protected abstract void saveModData(CompoundTag tag); - - protected abstract void loadModData(CompoundTag tag); - - @Override - protected void saveAdditional(CompoundTag pTag) { - this.saveModData(pTag); - } - - @Override - public void load(CompoundTag pTag) { - super.load(pTag); - this.loadModData(pTag); - } - - @Override - public CompoundTag getUpdateTag() { - CompoundTag tag = new CompoundTag(); - this.saveModData(tag); - return tag; - } - - @Override - public Packet getUpdatePacket() { - return ClientboundBlockEntityDataPacket.create(this); - } - - public void sync() { - this.setChanged(); - this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); - } - + public HexBlockEntity(BlockEntityType pType, BlockPos pWorldPosition, BlockState pBlockState) { + super(pType, pWorldPosition, pBlockState); + } + + protected abstract void saveModData(CompoundTag tag); + + protected abstract void loadModData(CompoundTag tag); + + @Override + protected void saveAdditional(CompoundTag pTag) { + this.saveModData(pTag); + } + + @Override + public void load(CompoundTag pTag) { + super.load(pTag); + this.loadModData(pTag); + } + + @Override + public CompoundTag getUpdateTag() { + CompoundTag tag = new CompoundTag(); + this.saveModData(tag); + return tag; + } + + @Override + public Packet getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + + public void sync() { + this.setChanged(); + this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockAbstractImpetus.java b/Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockAbstractImpetus.java index d5197fce68..e1d7413559 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockAbstractImpetus.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockAbstractImpetus.java @@ -3,6 +3,7 @@ import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus; import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv; import at.petrak.hexcasting.api.casting.eval.vm.CastingImage; +import java.util.EnumSet; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.server.level.ServerLevel; @@ -18,81 +19,87 @@ import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.DirectionProperty; -import java.util.EnumSet; - // Facing dir is the direction it starts searching for slates in to start public abstract class BlockAbstractImpetus extends BlockCircleComponent implements EntityBlock { - public static final DirectionProperty FACING = BlockStateProperties.FACING; + public static final DirectionProperty FACING = BlockStateProperties.FACING; - public BlockAbstractImpetus(Properties p_49795_) { - super(p_49795_); - this.registerDefaultState( - this.stateDefinition.any().setValue(ENERGIZED, false).setValue(FACING, Direction.NORTH)); - } + public BlockAbstractImpetus(Properties p_49795_) { + super(p_49795_); + this.registerDefaultState( + this.stateDefinition.any().setValue(ENERGIZED, false).setValue(FACING, Direction.NORTH)); + } - @Override - public ControlFlow acceptControlFlow(CastingImage imageIn, CircleCastEnv env, Direction enterDir, BlockPos pos, - BlockState bs, ServerLevel world) { - return new ControlFlow.Stop(); - } + @Override + public ControlFlow acceptControlFlow( + CastingImage imageIn, + CircleCastEnv env, + Direction enterDir, + BlockPos pos, + BlockState bs, + ServerLevel world) { + return new ControlFlow.Stop(); + } - @Override - public boolean canEnterFromDirection(Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) { - // FACING is the direction media EXITS from, so we can't have media entering in that direction - // so, flip it - return enterDir != bs.getValue(FACING).getOpposite(); - } + @Override + public boolean canEnterFromDirection( + Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) { + // FACING is the direction media EXITS from, so we can't have media entering in that direction + // so, flip it + return enterDir != bs.getValue(FACING).getOpposite(); + } - @Override - public EnumSet possibleExitDirections(BlockPos pos, BlockState bs, Level world) { - return EnumSet.of(bs.getValue(FACING)); - } + @Override + public EnumSet possibleExitDirections(BlockPos pos, BlockState bs, Level world) { + return EnumSet.of(bs.getValue(FACING)); + } - @Override - public Direction normalDir(BlockPos pos, BlockState bs, Level world, int recursionLeft) { - return normalDirOfOther(pos.relative(bs.getValue(FACING)), world, recursionLeft); - } + @Override + public Direction normalDir(BlockPos pos, BlockState bs, Level world, int recursionLeft) { + return normalDirOfOther(pos.relative(bs.getValue(FACING)), world, recursionLeft); + } - @Override - public float particleHeight(BlockPos pos, BlockState bs, Level world) { - return 0.5f; - } + @Override + public float particleHeight(BlockPos pos, BlockState bs, Level world) { + return 0.5f; + } - @Override - public void tick(BlockState pState, ServerLevel pLevel, BlockPos pPos, RandomSource pRandom) { - if (pLevel.getBlockEntity(pPos) instanceof BlockEntityAbstractImpetus tile && pState.getValue(ENERGIZED)) { - tile.tickExecution(); - } - } + @Override + public void tick(BlockState pState, ServerLevel pLevel, BlockPos pPos, RandomSource pRandom) { + if (pLevel.getBlockEntity(pPos) instanceof BlockEntityAbstractImpetus tile + && pState.getValue(ENERGIZED)) { + tile.tickExecution(); + } + } - @Override - public void onRemove(BlockState pState, Level pLevel, BlockPos pPos, BlockState pNewState, boolean pIsMoving) { - if (!pNewState.is(pState.getBlock()) - && pLevel.getBlockEntity(pPos) instanceof BlockEntityAbstractImpetus impetus) { - impetus.endExecution(); // TODO: Determine if this was important - // TODO: Fix this, it should make all the glowy circle components stop glowing. - } - super.onRemove(pState, pLevel, pPos, pNewState, pIsMoving); - } + @Override + public void onRemove( + BlockState pState, Level pLevel, BlockPos pPos, BlockState pNewState, boolean pIsMoving) { + if (!pNewState.is(pState.getBlock()) + && pLevel.getBlockEntity(pPos) instanceof BlockEntityAbstractImpetus impetus) { + impetus.endExecution(); // TODO: Determine if this was important + // TODO: Fix this, it should make all the glowy circle components stop glowing. + } + super.onRemove(pState, pLevel, pPos, pNewState, pIsMoving); + } - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(FACING); - } + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(FACING); + } - @Override - public BlockState getStateForPlacement(BlockPlaceContext pContext) { - return BlockCircleComponent.placeStateDirAndSneak(this.defaultBlockState(), pContext); - } + @Override + public BlockState getStateForPlacement(BlockPlaceContext pContext) { + return BlockCircleComponent.placeStateDirAndSneak(this.defaultBlockState(), pContext); + } - @Override - public BlockState rotate(BlockState pState, Rotation pRot) { - return pState.setValue(FACING, pRot.rotate(pState.getValue(FACING))); - } + @Override + public BlockState rotate(BlockState pState, Rotation pRot) { + return pState.setValue(FACING, pRot.rotate(pState.getValue(FACING))); + } - @Override - public BlockState mirror(BlockState pState, Mirror pMirror) { - return pState.rotate(pMirror.getRotation(pState.getValue(FACING))); - } + @Override + public BlockState mirror(BlockState pState, Mirror pMirror) { + return pState.rotate(pMirror.getRotation(pState.getValue(FACING))); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockCircleComponent.java b/Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockCircleComponent.java index f2c1458c7b..35eac540f1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockCircleComponent.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockCircleComponent.java @@ -14,82 +14,82 @@ // Convenience impl of ICircleComponent public abstract class BlockCircleComponent extends Block implements ICircleComponent { - public static final BooleanProperty ENERGIZED = BooleanProperty.create("energized"); - - public BlockCircleComponent(Properties p_49795_) { - super(p_49795_); - } - - @Override - public BlockState startEnergized(BlockPos pos, BlockState bs, Level world) { - var newState = bs.setValue(ENERGIZED, true); - world.setBlockAndUpdate(pos, newState); - - return newState; - } - - @Override - public boolean isEnergized(BlockPos pos, BlockState bs, Level world) { - return bs.getValue(ENERGIZED); - } - - @Override - public BlockState endEnergized(BlockPos pos, BlockState bs, Level world) { - var newState = bs.setValue(ENERGIZED, false); - world.setBlockAndUpdate(pos, newState); - return newState; - } - - /** - * Which direction points "up" or "out" for this block? - * This is used for {@link ICircleComponent#canEnterFromDirection(Direction, BlockPos, BlockState, ServerLevel)} - * as well as particles. - */ - public Direction normalDir(BlockPos pos, BlockState bs, Level world) { - return normalDir(pos, bs, world, 16); - } - - abstract public Direction normalDir(BlockPos pos, BlockState bs, Level world, int recursionLeft); - - public static Direction normalDirOfOther(BlockPos other, Level world, int recursionLeft) { - if (recursionLeft <= 0) { - return Direction.UP; - } - - var stateThere = world.getBlockState(other); - if (stateThere.getBlock() instanceof BlockCircleComponent bcc) { - return bcc.normalDir(other, stateThere, world, recursionLeft - 1); - } else { - return Direction.UP; - } - } - - /** - * How many blocks in the {@link BlockCircleComponent#normalDir(BlockPos, BlockState, Level)} from the center - * particles should be spawned in - */ - abstract public float particleHeight(BlockPos pos, BlockState bs, Level world); - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { - pBuilder.add(ENERGIZED); - } - - @Override - public boolean hasAnalogOutputSignal(BlockState pState) { - return true; - } - - @Override - public int getAnalogOutputSignal(BlockState pState, Level pLevel, BlockPos pPos) { - return pState.getValue(ENERGIZED) ? 15 : 0; - } - - public static BlockState placeStateDirAndSneak(BlockState stock, BlockPlaceContext ctx) { - var dir = ctx.getNearestLookingDirection(); - if (ctx.getPlayer() != null && ctx.getPlayer().isDiscrete()) { - dir = dir.getOpposite(); - } - return stock.setValue(BlockStateProperties.FACING, dir); - } + public static final BooleanProperty ENERGIZED = BooleanProperty.create("energized"); + + public BlockCircleComponent(Properties p_49795_) { + super(p_49795_); + } + + @Override + public BlockState startEnergized(BlockPos pos, BlockState bs, Level world) { + var newState = bs.setValue(ENERGIZED, true); + world.setBlockAndUpdate(pos, newState); + + return newState; + } + + @Override + public boolean isEnergized(BlockPos pos, BlockState bs, Level world) { + return bs.getValue(ENERGIZED); + } + + @Override + public BlockState endEnergized(BlockPos pos, BlockState bs, Level world) { + var newState = bs.setValue(ENERGIZED, false); + world.setBlockAndUpdate(pos, newState); + return newState; + } + + /** + * Which direction points "up" or "out" for this block? This is used for {@link + * ICircleComponent#canEnterFromDirection(Direction, BlockPos, BlockState, ServerLevel)} as well + * as particles. + */ + public Direction normalDir(BlockPos pos, BlockState bs, Level world) { + return normalDir(pos, bs, world, 16); + } + + public abstract Direction normalDir(BlockPos pos, BlockState bs, Level world, int recursionLeft); + + public static Direction normalDirOfOther(BlockPos other, Level world, int recursionLeft) { + if (recursionLeft <= 0) { + return Direction.UP; + } + + var stateThere = world.getBlockState(other); + if (stateThere.getBlock() instanceof BlockCircleComponent bcc) { + return bcc.normalDir(other, stateThere, world, recursionLeft - 1); + } else { + return Direction.UP; + } + } + + /** + * How many blocks in the {@link BlockCircleComponent#normalDir(BlockPos, BlockState, Level)} from + * the center particles should be spawned in + */ + public abstract float particleHeight(BlockPos pos, BlockState bs, Level world); + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { + pBuilder.add(ENERGIZED); + } + + @Override + public boolean hasAnalogOutputSignal(BlockState pState) { + return true; + } + + @Override + public int getAnalogOutputSignal(BlockState pState, Level pLevel, BlockPos pPos) { + return pState.getValue(ENERGIZED) ? 15 : 0; + } + + public static BlockState placeStateDirAndSneak(BlockState stock, BlockPlaceContext ctx) { + var dir = ctx.getNearestLookingDirection(); + if (ctx.getPlayer() != null && ctx.getPlayer().isDiscrete()) { + dir = dir.getOpposite(); + } + return stock.setValue(BlockStateProperties.FACING, dir); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionRegistryEntry.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionRegistryEntry.java index dbc5dc060b..d7bb7876b6 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionRegistryEntry.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionRegistryEntry.java @@ -6,10 +6,9 @@ /** * A bit of wrapper information around an action to go in the registry. * - * @param prototype The pattern associated with this action. The start dir acts as the "canonical" start direction - * for display in the book. For per-world patterns, the angle signature is the *shape* of the pattern - * but probably not the pattern itself. - * @param action The action itself + * @param prototype The pattern associated with this action. The start dir acts as the "canonical" + * start direction for display in the book. For per-world patterns, the angle signature is the + * *shape* of the pattern but probably not the pattern itself. + * @param action The action itself */ -public record ActionRegistryEntry(HexPattern prototype, Action action) { -} +public record ActionRegistryEntry(HexPattern prototype, Action action) {} 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..1ebb1cf06d 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 @@ -8,6 +8,10 @@ import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidIota import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs import at.petrak.hexcasting.api.utils.asTranslatedComponent import com.mojang.datafixers.util.Either +import java.util.function.DoubleUnaryOperator +import kotlin.math.abs +import kotlin.math.roundToInt +import kotlin.math.roundToLong import net.minecraft.core.BlockPos import net.minecraft.server.level.ServerPlayer import net.minecraft.world.entity.Entity @@ -17,273 +21,281 @@ import net.minecraft.world.entity.decoration.ArmorStand import net.minecraft.world.entity.item.ItemEntity import net.minecraft.world.phys.Vec3 import org.joml.Vector3f -import java.util.function.DoubleUnaryOperator -import kotlin.math.abs -import kotlin.math.roundToInt -import kotlin.math.roundToLong fun List.getDouble(idx: Int, argc: Int = 0): Double { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is DoubleIota) { - return x.double - } else { - // TODO: I'm not sure this calculation is correct - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "double") - } + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is DoubleIota) { + return x.double + } else { + // TODO: I'm not sure this calculation is correct + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "double") + } } fun List.getEntity(idx: Int, argc: Int = 0): Entity { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is EntityIota) { - return x.entity - } else { - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "entity") - } + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is EntityIota) { + return x.entity + } else { + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "entity") + } } fun List.getList(idx: Int, argc: Int = 0): SpellList { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is ListIota) { - return x.list - } else { - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "list") - } + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is ListIota) { + return x.list + } else { + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "list") + } } fun List.getPattern(idx: Int, argc: Int = 0): HexPattern { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is PatternIota) { - return x.pattern - } else { - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "pattern") - } + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is PatternIota) { + return x.pattern + } else { + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "pattern") + } } fun List.getVec3(idx: Int, argc: Int = 0): Vec3 { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is Vec3Iota) { - return x.vec3 - } else { - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "vector") - } + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is Vec3Iota) { + return x.vec3 + } else { + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "vector") + } } fun List.getBool(idx: Int, argc: Int = 0): Boolean { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is BooleanIota) { - return x.bool - } else { - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "boolean") - } + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is BooleanIota) { + return x.bool + } else { + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "boolean") + } } // Helpers fun List.getItemEntity(idx: Int, argc: Int = 0): ItemEntity { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is EntityIota) { - val e = x.entity - if (e is ItemEntity) - return e - } - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "entity.item") + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is EntityIota) { + val e = x.entity + if (e is ItemEntity) return e + } + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "entity.item") } fun List.getPlayer(idx: Int, argc: Int = 0): ServerPlayer { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is EntityIota) { - val e = x.entity - if (e is ServerPlayer) - return e - } - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "entity.player") + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is EntityIota) { + val e = x.entity + if (e is ServerPlayer) return e + } + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "entity.player") } fun List.getMob(idx: Int, argc: Int = 0): Mob { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is EntityIota) { - val e = x.entity - if (e is Mob) - return e - } - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "entity.mob") + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is EntityIota) { + val e = x.entity + if (e is Mob) return e + } + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "entity.mob") } fun List.getLivingEntityButNotArmorStand(idx: Int, argc: Int = 0): LivingEntity { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is EntityIota) { - val e = x.entity - if (e is LivingEntity && e !is ArmorStand) - return e - } - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "entity.living") + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is EntityIota) { + val e = x.entity + if (e is LivingEntity && e !is ArmorStand) return e + } + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "entity.living") } fun List.getPositiveDouble(idx: Int, argc: Int = 0): Double { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is DoubleIota) { - val double = x.double - if (0 <= double) { - return double - } - } - throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "double.positive") + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is DoubleIota) { + val double = x.double + if (0 <= double) { + return double + } + } + throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "double.positive") } fun List.getPositiveDoubleUnder(idx: Int, max: Double, argc: Int = 0): Double { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is DoubleIota) { - val double = x.double - if (0.0 <= double && double < max) { - return double - } - } - throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "double.positive.less", max) + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is DoubleIota) { + val double = x.double + if (0.0 <= double && double < max) { + return double + } + } + throw MishapInvalidIota.of( + x, + if (argc == 0) idx else argc - (idx + 1), + "double.positive.less", + max + ) } fun List.getPositiveDoubleUnderInclusive(idx: Int, max: Double, argc: Int = 0): Double { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is DoubleIota) { - val double = x.double - if (double in 0.0..max) { - return double - } - } - throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "double.positive.less.equal", max) + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is DoubleIota) { + val double = x.double + if (double in 0.0..max) { + return double + } + } + throw MishapInvalidIota.of( + x, + if (argc == 0) idx else argc - (idx + 1), + "double.positive.less.equal", + max + ) } fun List.getDoubleBetween(idx: Int, min: Double, max: Double, argc: Int = 0): Double { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is DoubleIota) { - val double = x.double - if (double in min..max) { - return double - } - } - throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "double.between", min, max) + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is DoubleIota) { + val double = x.double + if (double in min..max) { + return double + } + } + throw MishapInvalidIota.of( + x, + if (argc == 0) idx else argc - (idx + 1), + "double.between", + min, + max + ) } fun List.getInt(idx: Int, argc: Int = 0): Int { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is DoubleIota) { - val double = x.double - val rounded = double.roundToInt() - if (abs(double - rounded) <= DoubleIota.TOLERANCE) { - return rounded - } - } - throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int") + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is DoubleIota) { + val double = x.double + val rounded = double.roundToInt() + if (abs(double - rounded) <= DoubleIota.TOLERANCE) { + return rounded + } + } + throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int") } fun List.getLong(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) { - return rounded - } - } - throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int") // shh we're lying + 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) { + return rounded + } + } + throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int") // shh we're lying } fun List.getPositiveInt(idx: Int, argc: Int = 0): Int { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is DoubleIota) { - val double = x.double - val rounded = double.roundToInt() - if (abs(double - rounded) <= DoubleIota.TOLERANCE && rounded >= 0) { - return rounded - } - } - throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int.positive") + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is DoubleIota) { + val double = x.double + val rounded = double.roundToInt() + 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) { - val double = x.double - val rounded = double.roundToInt() - if (abs(double - rounded) <= DoubleIota.TOLERANCE && rounded in 0 until max) { - return rounded - } - } - throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int.positive.less", max) + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is DoubleIota) { + val double = x.double + val rounded = double.roundToInt() + if (abs(double - rounded) <= DoubleIota.TOLERANCE && rounded in 0 until max) { + return rounded + } + } + throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int.positive.less", max) } fun List.getPositiveIntUnderInclusive(idx: Int, max: Int, argc: Int = 0): Int { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is DoubleIota) { - val double = x.double - val rounded = double.roundToInt() - if (abs(double - rounded) <= DoubleIota.TOLERANCE && rounded in 0..max) { - return rounded - } - } - throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int.positive.less.equal", max) + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is DoubleIota) { + val double = x.double + val rounded = double.roundToInt() + if (abs(double - rounded) <= DoubleIota.TOLERANCE && rounded in 0..max) { + return rounded + } + } + throw MishapInvalidIota.of( + x, + if (argc == 0) idx else argc - (idx + 1), + "int.positive.less.equal", + max + ) } fun List.getIntBetween(idx: Int, min: Int, max: Int, argc: Int = 0): Int { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is DoubleIota) { - val double = x.double - val rounded = double.roundToInt() - if (abs(double - rounded) <= DoubleIota.TOLERANCE && rounded in min..max) { - return rounded - } - } - throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int.between", min, max) + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is DoubleIota) { + val double = x.double + val rounded = double.roundToInt() + if (abs(double - rounded) <= DoubleIota.TOLERANCE && rounded in min..max) { + return rounded + } + } + throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int.between", min, max) } fun List.getBlockPos(idx: Int, argc: Int = 0): BlockPos { - val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (x is Vec3Iota) { - return BlockPos.containing(x.vec3) - } + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (x is Vec3Iota) { + return BlockPos.containing(x.vec3) + } - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "vector") + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "vector") } fun List.getNumOrVec(idx: Int, argc: Int = 0): Either { - val datum = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - return when (datum) { - is DoubleIota -> Either.left(datum.double) - is Vec3Iota -> Either.right(datum.vec3) - else -> throw MishapInvalidIota.of( - datum, - if (argc == 0) idx else argc - (idx + 1), - "numvec" - ) - } + val datum = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + return when (datum) { + is DoubleIota -> Either.left(datum.double) + is Vec3Iota -> Either.right(datum.vec3) + else -> throw MishapInvalidIota.of(datum, if (argc == 0) idx else argc - (idx + 1), "numvec") + } } fun List.getLongOrList(idx: Int, argc: Int = 0): Either { - val datum = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } - if (datum is DoubleIota) { - val double = datum.double - val rounded = double.roundToLong() - if (abs(double - rounded) <= DoubleIota.TOLERANCE) { - return Either.left(rounded) - } - } else if (datum is ListIota) { - return Either.right(datum.list) - } - throw MishapInvalidIota.of( - datum, - if (argc == 0) idx else argc - (idx + 1), - "numlist" - ) + val datum = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + if (datum is DoubleIota) { + val double = datum.double + val rounded = double.roundToLong() + if (abs(double - rounded) <= DoubleIota.TOLERANCE) { + return Either.left(rounded) + } + } else if (datum is ListIota) { + return Either.right(datum.list) + } + throw MishapInvalidIota.of(datum, if (argc == 0) idx else argc - (idx + 1), "numlist") } fun evaluatable(datum: Iota, reverseIdx: Int): Either = - when (datum) { - is ListIota -> Either.right(datum.list) - else -> if (datum.executable()) Either.left(datum) else throw MishapInvalidIota( - datum, - reverseIdx, - "hexcasting.mishap.invalid_value.evaluatable".asTranslatedComponent - ) - } + when (datum) { + is ListIota -> Either.right(datum.list) + else -> + if (datum.executable()) Either.left(datum) + else + throw MishapInvalidIota( + datum, + reverseIdx, + "hexcasting.mishap.invalid_value.evaluatable".asTranslatedComponent + ) + } fun Iota?.orNull() = this ?: NullIota() @@ -291,21 +303,33 @@ fun Iota?.orNull() = this ?: NullIota() // there should probably be some way to abstract function application over lists, vecs, and numbers, // and i bet it's fucking monads fun aplKinnie(operatee: Either, fn: DoubleUnaryOperator): Iota = - operatee.map( - { num -> DoubleIota(fn.applyAsDouble(num)) }, - { vec -> Vec3Iota(Vec3(fn.applyAsDouble(vec.x), fn.applyAsDouble(vec.y), fn.applyAsDouble(vec.z))) } - ) - -inline val Boolean.asActionResult get() = listOf(BooleanIota(this)) -inline val Double.asActionResult get() = listOf(DoubleIota(this)) -inline val Number.asActionResult get() = listOf(DoubleIota(this.toDouble())) - -inline val SpellList.asActionResult get() = listOf(ListIota(this)) -inline val List.asActionResult get() = listOf(ListIota(this)) - -inline val BlockPos.asActionResult get() = listOf(Vec3Iota(Vec3.atCenterOf(this))) -inline val Vector3f.asActionResult get() = listOf(Vec3Iota(Vec3(this))) -inline val Vec3.asActionResult get() = listOf(Vec3Iota(this)) - -inline val Entity?.asActionResult get() = listOf(if (this == null) NullIota() else EntityIota(this)) -inline val HexPattern.asActionResult get() = listOf(PatternIota(this)) + operatee.map( + { num -> DoubleIota(fn.applyAsDouble(num)) }, + { vec -> + Vec3Iota(Vec3(fn.applyAsDouble(vec.x), fn.applyAsDouble(vec.y), fn.applyAsDouble(vec.z))) + } + ) + +inline val Boolean.asActionResult + get() = listOf(BooleanIota(this)) +inline val Double.asActionResult + get() = listOf(DoubleIota(this)) +inline val Number.asActionResult + get() = listOf(DoubleIota(this.toDouble())) + +inline val SpellList.asActionResult + get() = listOf(ListIota(this)) +inline val List.asActionResult + get() = listOf(ListIota(this)) + +inline val BlockPos.asActionResult + get() = listOf(Vec3Iota(Vec3.atCenterOf(this))) +inline val Vector3f.asActionResult + get() = listOf(Vec3Iota(Vec3(this))) +inline val Vec3.asActionResult + get() = listOf(Vec3Iota(this)) + +inline val Entity?.asActionResult + get() = listOf(if (this == null) NullIota() else EntityIota(this)) +inline val HexPattern.asActionResult + get() = listOf(PatternIota(this)) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/ParticleSpray.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/ParticleSpray.kt index 88129e6bf8..dc7e8e0963 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/ParticleSpray.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/ParticleSpray.kt @@ -10,20 +10,31 @@ import net.minecraft.world.phys.Vec3 * @param fuzziness the radius of the sphere the particle might happen in (pos) * @param spread the max angle in radians the particle can move in, in relation to vel */ -data class ParticleSpray(val pos: Vec3, val vel: Vec3, val fuzziness: Double, val spread: Double, val count: Int = 20) { - companion object { - @JvmStatic - fun burst(pos: Vec3, size: Double, count: Int = 20): ParticleSpray { - return ParticleSpray(pos, Vec3(size, 0.0, 0.0), 0.0, 3.14, count) - } +data class ParticleSpray( + val pos: Vec3, + val vel: Vec3, + val fuzziness: Double, + val spread: Double, + val count: Int = 20 +) { + companion object { + @JvmStatic + fun burst(pos: Vec3, size: Double, count: Int = 20): ParticleSpray { + return ParticleSpray(pos, Vec3(size, 0.0, 0.0), 0.0, 3.14, count) + } - @JvmStatic - fun cloud(pos: Vec3, size: Double, count: Int = 20): ParticleSpray { - return ParticleSpray(pos, Vec3(0.0, 0.001, 0.0), size, 0.0, count) - } - } + @JvmStatic + fun cloud(pos: Vec3, size: Double, count: Int = 20): ParticleSpray { + return ParticleSpray(pos, Vec3(0.0, 0.001, 0.0), size, 0.0, count) + } + } - fun sprayParticles(world: ServerLevel, color: FrozenPigment) { - IXplatAbstractions.INSTANCE.sendPacketNear(this.pos, 128.0, world, MsgCastParticleS2C(this, color)) - } + fun sprayParticles(world: ServerLevel, color: FrozenPigment) { + IXplatAbstractions.INSTANCE.sendPacketNear( + this.pos, + 128.0, + world, + MsgCastParticleS2C(this, color) + ) + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/PatternShapeMatch.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/PatternShapeMatch.java index b72d349895..b66f025a4b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/PatternShapeMatch.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/PatternShapeMatch.java @@ -3,55 +3,46 @@ import at.petrak.hexcasting.api.casting.castables.SpecialHandler; import net.minecraft.resources.ResourceKey; -/** - * Possible things we find when trying to match a pattern's shape. - */ +/** Possible things we find when trying to match a pattern's shape. */ public abstract sealed class PatternShapeMatch { - /** - * I've never met that pattern in my life - */ - public static final class Nothing extends PatternShapeMatch { - } - - /** - * The shape exactly matches a pattern that isn't altered per world - */ - public static final class Normal extends PatternShapeMatch { - public final ResourceKey key; - - public Normal(ResourceKey key) { - this.key = key; - } - } - - /** - * The pattern is the right shape to be one of the per-world patterns. - *

- * On the server, {@link PerWorld#certain} means whether this is an exact match, or if it's just the - * right shape. (In other words it should only actually be casted if it is true.) - *

- * On the client, it is always false. - */ - public static final class PerWorld extends PatternShapeMatch { - public final ResourceKey key; - public final boolean certain; - - public PerWorld(ResourceKey key, boolean certain) { - this.key = key; - this.certain = certain; - } - } - - /** - * The shape matches a special handler - */ - public static final class Special extends PatternShapeMatch { - public final ResourceKey> key; - public final SpecialHandler handler; - - public Special(ResourceKey> key, SpecialHandler handler) { - this.key = key; - this.handler = handler; - } - } + /** I've never met that pattern in my life */ + public static final class Nothing extends PatternShapeMatch {} + + /** The shape exactly matches a pattern that isn't altered per world */ + public static final class Normal extends PatternShapeMatch { + public final ResourceKey key; + + public Normal(ResourceKey key) { + this.key = key; + } + } + + /** + * The pattern is the right shape to be one of the per-world patterns. + * + *

On the server, {@link PerWorld#certain} means whether this is an exact match, or if it's + * just the right shape. (In other words it should only actually be casted if it is true.) + * + *

On the client, it is always false. + */ + public static final class PerWorld extends PatternShapeMatch { + public final ResourceKey key; + public final boolean certain; + + public PerWorld(ResourceKey key, boolean certain) { + this.key = key; + this.certain = certain; + } + } + + /** The shape matches a special handler */ + public static final class Special extends PatternShapeMatch { + public final ResourceKey> key; + public final SpecialHandler handler; + + public Special(ResourceKey> key, SpecialHandler handler) { + this.key = key; + this.handler = handler; + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/RenderedSpell.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/RenderedSpell.kt index 20ea047d91..d4dfc938b4 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/RenderedSpell.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/RenderedSpell.kt @@ -4,10 +4,10 @@ import at.petrak.hexcasting.api.casting.eval.CastingEnvironment import at.petrak.hexcasting.api.casting.eval.vm.CastingImage interface RenderedSpell { - fun cast(env: CastingEnvironment) + fun cast(env: CastingEnvironment) - fun cast(env: CastingEnvironment, image: CastingImage): CastingImage? { - cast(env) - return null - } + fun cast(env: CastingEnvironment, image: CastingImage): CastingImage? { + cast(env) + return null + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/SpellList.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/SpellList.kt index 2e76c18c76..b5468251e5 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/SpellList.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/SpellList.kt @@ -9,85 +9,86 @@ import at.petrak.hexcasting.api.casting.iota.Iota */ sealed class SpellList : Iterable { - abstract val nonEmpty: Boolean - abstract val car: Iota - abstract val cdr: SpellList + abstract val nonEmpty: Boolean + abstract val car: Iota + abstract val cdr: SpellList - class LPair(override val car: Iota, override val cdr: SpellList) : SpellList() { - override val nonEmpty = true - } + class LPair(override val car: Iota, override val cdr: SpellList) : SpellList() { + override val nonEmpty = true + } - class LList(val idx: Int, val list: List) : SpellList() { - override val nonEmpty: Boolean - get() = idx < list.size - override val car: Iota - get() = list[idx] - override val cdr: SpellList - get() = LList(idx + 1, list) + class LList(val idx: Int, val list: List) : SpellList() { + override val nonEmpty: Boolean + get() = idx < list.size - constructor(list: List) : this(0, list) - } + override val car: Iota + get() = list[idx] - fun modifyAt(startIdx: Int, modify: (SpellList) -> SpellList): SpellList { - val stack = mutableListOf() - val ptr = iterator() - var idx = startIdx - if (idx < 0) { - return this - } - while (idx > 0) { - if (!ptr.hasNext()) { - return this - } - idx-- - stack.add(ptr.next()) - } - var value = modify(ptr.list) - for (datum in stack.asReversed()) { - value = LPair(datum, value) - } - return value - } + override val cdr: SpellList + get() = LList(idx + 1, list) - fun getAt(startIdx: Int): Iota { - var ptr = this - var idx = startIdx - if (idx < 0) { - throw ArrayIndexOutOfBoundsException() - } - while (idx > 0) { - when (ptr) { - is LPair -> ptr = ptr.cdr - is LList -> return ptr.list[ptr.idx + idx] - } - idx-- - } - return ptr.car - } + constructor(list: List) : this(0, list) + } - override fun toString() = toList().toString() + fun modifyAt(startIdx: Int, modify: (SpellList) -> SpellList): SpellList { + val stack = mutableListOf() + val ptr = iterator() + var idx = startIdx + if (idx < 0) { + return this + } + while (idx > 0) { + if (!ptr.hasNext()) { + return this + } + idx-- + stack.add(ptr.next()) + } + var value = modify(ptr.list) + for (datum in stack.asReversed()) { + value = LPair(datum, value) + } + return value + } - override fun iterator() = SpellListIterator(this) + fun getAt(startIdx: Int): Iota { + var ptr = this + var idx = startIdx + if (idx < 0) { + throw ArrayIndexOutOfBoundsException() + } + while (idx > 0) { + when (ptr) { + is LPair -> ptr = ptr.cdr + is LList -> return ptr.list[ptr.idx + idx] + } + idx-- + } + return ptr.car + } - /** - * Note this is O(n), probably. - */ - fun size(): Int { - var size = 0 - var ptr = this - while (ptr.nonEmpty) { - ptr = ptr.cdr - size++ - } - return size - } + override fun toString() = toList().toString() - class SpellListIterator(var list: SpellList) : Iterator { - override fun hasNext() = list.nonEmpty - override operator fun next(): Iota { - val car = list.car - list = list.cdr - return car - } - } + override fun iterator() = SpellListIterator(this) + + /** Note this is O(n), probably. */ + fun size(): Int { + var size = 0 + var ptr = this + while (ptr.nonEmpty) { + ptr = ptr.cdr + size++ + } + return size + } + + class SpellListIterator(var list: SpellList) : Iterator { + override fun hasNext() = list.nonEmpty + + override operator fun next(): Iota { + val car = list.car + list = list.cdr + return car + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/Arithmetic.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/Arithmetic.java index 10f9f9cd6e..3ae9067de3 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/Arithmetic.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/Arithmetic.java @@ -5,8 +5,8 @@ import at.petrak.hexcasting.api.casting.math.HexPattern; /** - * This is the interface to implement if you want to override the behaviour of an Operator pattern like ADD, SUB, etc. for some type/s of - * iotas for which that Operator pattern is not yet defined. + * This is the interface to implement if you want to override the behaviour of an Operator pattern + * like ADD, SUB, etc. for some type/s of iotas for which that Operator pattern is not yet defined. */ public interface Arithmetic { String arithName(); @@ -42,7 +42,6 @@ public interface Arithmetic { HexPattern LOG = HexPattern.fromAngles("eqaqe", HexDir.NORTH_WEST); HexPattern MOD = HexPattern.fromAngles("addwaad", HexDir.NORTH_EAST); - // Vecs HexPattern PACK = HexPattern.fromAngles("eqqqqq", HexDir.EAST); HexPattern UNPACK = HexPattern.fromAngles("qeeeee", HexDir.EAST); diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IterPair.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IterPair.java index 64ff91fb25..9771e027ac 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IterPair.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/IterPair.java @@ -7,15 +7,19 @@ public record IterPair(T left, T right) implements Iterable { public Iterator iterator() { return new Iterator() { int ix; + @Override public boolean hasNext() { return ix < 2; } + @Override public T next() { switch (ix++) { - case 0: return left; - case 1: return right; + case 0: + return left; + case 1: + return right; } return null; } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/TripleIterable.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/TripleIterable.java index e066a106c1..45eb510eab 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/TripleIterable.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/TripleIterable.java @@ -1,43 +1,45 @@ package at.petrak.hexcasting.api.casting.arithmetic; +import java.util.Iterator; import org.apache.commons.lang3.function.TriFunction; import org.jetbrains.annotations.NotNull; -import java.util.Iterator; - -public class TripleIterable implements Iterable { - private final Iterable iterableA; - private final Iterable iterableB; - private final Iterable iterableC; - - private final TriFunction map; - - public TripleIterable(Iterable iterableA, Iterable iterableB, Iterable iterableC, TriFunction map) { - this.iterableA = iterableA; - this.iterableB = iterableB; - this.iterableC = iterableC; - this.map = map; - } - - @NotNull - @Override - public Iterator iterator() { - return new TripleIterator(); - } - - class TripleIterator implements Iterator { - private final Iterator iteratorA = iterableA.iterator(); - private final Iterator iteratorB = iterableB.iterator(); - private final Iterator iteratorC = iterableC.iterator(); - - @Override - public boolean hasNext() { - return iteratorA.hasNext() && iteratorB.hasNext() && iteratorC.hasNext(); - } - - @Override - public D next() { - return map.apply(iteratorA.next(), iteratorB.next(), iteratorC.next()); - } - } +public class TripleIterable implements Iterable { + private final Iterable iterableA; + private final Iterable iterableB; + private final Iterable iterableC; + + private final TriFunction map; + + public TripleIterable( + Iterable iterableA, + Iterable iterableB, + Iterable iterableC, + TriFunction map) { + this.iterableA = iterableA; + this.iterableB = iterableB; + this.iterableC = iterableC; + this.map = map; + } + + @NotNull @Override + public Iterator iterator() { + return new TripleIterator(); + } + + class TripleIterator implements Iterator { + private final Iterator iteratorA = iterableA.iterator(); + private final Iterator iteratorB = iterableB.iterator(); + private final Iterator iteratorC = iterableC.iterator(); + + @Override + public boolean hasNext() { + return iteratorA.hasNext() && iteratorB.hasNext() && iteratorC.hasNext(); + } + + @Override + public D next() { + return map.apply(iteratorA.next(), iteratorB.next(), iteratorC.next()); + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/ArithmeticEngine.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/ArithmeticEngine.java index 2f7e1fe7dc..7e5a0a931e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/ArithmeticEngine.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/ArithmeticEngine.java @@ -10,101 +10,124 @@ import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.api.casting.mishaps.Mishap; import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs; - import java.util.*; /** - * This is the class responsible for managing the various Arithmetics that are in use, deciding based on the current - * stack which Operator should be called, etc. + * This is the class responsible for managing the various Arithmetics that are in use, deciding + * based on the current stack which Operator should be called, etc. */ public class ArithmeticEngine { - /** - * Data structure for mapping the pattern that gets drawn on the stack to the list of Operators that - * are overloading that pattern. - * @param pattern The pattern that the caster will need to draw to cast one of these operators. - * @param arity The number of arguments that all of these operators must consume from the stack. - * @param operators The list of all operators that overload this pattern. - */ - private record OpCandidates(HexPattern pattern, int arity, List operators) { - public void addOp(Operator next) { - if (next.arity != arity) { - throw new IllegalArgumentException("Operators exist of differing arity! The pattern " + pattern - + " already had arity " + arity + " when the operator with arity " + next.arity + ", " + next + " was added."); - } - operators.add(next); - } - } + /** + * Data structure for mapping the pattern that gets drawn on the stack to the list of Operators + * that are overloading that pattern. + * + * @param pattern The pattern that the caster will need to draw to cast one of these operators. + * @param arity The number of arguments that all of these operators must consume from the stack. + * @param operators The list of all operators that overload this pattern. + */ + private record OpCandidates(HexPattern pattern, int arity, List operators) { + public void addOp(Operator next) { + if (next.arity != arity) { + throw new IllegalArgumentException( + "Operators exist of differing arity! The pattern " + + pattern + + " already had arity " + + arity + + " when the operator with arity " + + next.arity + + ", " + + next + + " was added."); + } + operators.add(next); + } + } - public final Arithmetic[] arithmetics; - private final Map operators = new HashMap<>(); + public final Arithmetic[] arithmetics; + private final Map operators = new HashMap<>(); - /** - * A cache mapping specific sets of Pattern, IotaType, IotaType, ..., IotaType to Operators so that the Operators don't need to be - * queried for what types they accept every time they are used. - */ - private final Map cache = new HashMap<>(); + /** + * A cache mapping specific sets of Pattern, IotaType, IotaType, ..., IotaType to Operators so + * that the Operators don't need to be queried for what types they accept every time they are + * used. + */ + private final Map cache = new HashMap<>(); - public ArithmeticEngine(List arithmetics) { - this.arithmetics = arithmetics.toArray(new Arithmetic[0]); - for (var arith : arithmetics) { - for (var op : arith.opTypes()) { - operators.compute(op, ($, info) -> { - var operator = arith.getOperator(op); - if (info == null) { - info = new OpCandidates(op, operator.arity, new ArrayList<>()); - } - info.addOp(operator); - return info; - }); - } - } - } + public ArithmeticEngine(List arithmetics) { + this.arithmetics = arithmetics.toArray(new Arithmetic[0]); + for (var arith : arithmetics) { + for (var op : arith.opTypes()) { + operators.compute( + op, + ($, info) -> { + var operator = arith.getOperator(op); + if (info == null) { + info = new OpCandidates(op, operator.arity, new ArrayList<>()); + } + info.addOp(operator); + return info; + }); + } + } + } - public Iterable operatorSyms() { - return operators.keySet(); - } + public Iterable operatorSyms() { + return operators.keySet(); + } - /** - * Runs one of the contained Operators assigned to the given pattern, modifying the passed stack of iotas. - * @param pattern The pattern that was drawn, used to determine which operators are candidates. - * @param env The casting environment. - * @param image The casting image. - * @param continuation The current continuation. - * @return The iotas to be added to the stack. - * @throws Mishap mishaps if invalid input to the operators is given by the caster. - */ - public OperationResult run(HexPattern pattern, CastingEnvironment env, CastingImage image, SpellContinuation continuation) throws Mishap { - var stackList = image.getStack(); - var stack = new Stack(); - stack.addAll(stackList); - var startingLength = stackList.size(); + /** + * Runs one of the contained Operators assigned to the given pattern, modifying the passed stack + * of iotas. + * + * @param pattern The pattern that was drawn, used to determine which operators are candidates. + * @param env The casting environment. + * @param image The casting image. + * @param continuation The current continuation. + * @return The iotas to be added to the stack. + * @throws Mishap mishaps if invalid input to the operators is given by the caster. + */ + public OperationResult run( + HexPattern pattern, + CastingEnvironment env, + CastingImage image, + SpellContinuation continuation) + throws Mishap { + var stackList = image.getStack(); + var stack = new Stack(); + stack.addAll(stackList); + var startingLength = stackList.size(); - var candidates = operators.get(pattern); - if (candidates == null) - throw new InvalidOperatorException("the pattern " + pattern + " is not an operator."); // - HashCons hash = new HashCons.Pattern(pattern); - var args = new ArrayList(candidates.arity()); - for (var i = 0; i < candidates.arity(); i++) { - if (stack.isEmpty()) { - throw new MishapNotEnoughArgs(candidates.arity, startingLength); - } - var iota = stack.pop(); - hash = new HashCons.Pair(iota.getType(), hash); - args.add(iota); - } - Collections.reverse(args); - var op = resolveCandidates(args, hash, candidates); - return op.operate(env, image, continuation); - } + var candidates = operators.get(pattern); + if (candidates == null) + throw new InvalidOperatorException("the pattern " + pattern + " is not an operator."); // + HashCons hash = new HashCons.Pattern(pattern); + var args = new ArrayList(candidates.arity()); + for (var i = 0; i < candidates.arity(); i++) { + if (stack.isEmpty()) { + throw new MishapNotEnoughArgs(candidates.arity, startingLength); + } + var iota = stack.pop(); + hash = new HashCons.Pair(iota.getType(), hash); + args.add(iota); + } + Collections.reverse(args); + var op = resolveCandidates(args, hash, candidates); + return op.operate(env, image, continuation); + } - private Operator resolveCandidates(List args, HashCons hash, OpCandidates candidates) { - return cache.computeIfAbsent(hash, $ -> { - for (var op : candidates.operators()) { - if (op.accepts.test(args)) { - return op; - } - } - throw new NoOperatorCandidatesException(candidates.pattern(), args, "No implementation candidates for op " + candidates.pattern() + " on args: " + args); - }); - } + private Operator resolveCandidates(List args, HashCons hash, OpCandidates candidates) { + return cache.computeIfAbsent( + hash, + $ -> { + for (var op : candidates.operators()) { + if (op.accepts.test(args)) { + return op; + } + } + throw new NoOperatorCandidatesException( + candidates.pattern(), + args, + "No implementation candidates for op " + candidates.pattern() + " on args: " + args); + }); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/HashCons.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/HashCons.java index c7566e783b..4c77496844 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/HashCons.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/HashCons.java @@ -1,6 +1,5 @@ package at.petrak.hexcasting.api.casting.arithmetic.engine; - import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.casting.math.HexPattern; diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/InvalidOperatorException.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/InvalidOperatorException.java index 1d826da67e..49a8de1e15 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/InvalidOperatorException.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/InvalidOperatorException.java @@ -1,18 +1,17 @@ package at.petrak.hexcasting.api.casting.arithmetic.engine; public class InvalidOperatorException extends RuntimeException { - public InvalidOperatorException() { - } + public InvalidOperatorException() {} - public InvalidOperatorException(String s) { - super(s); - } + public InvalidOperatorException(String s) { + super(s); + } - public InvalidOperatorException(String message, Throwable cause) { - super(message, cause); - } + public InvalidOperatorException(String message, Throwable cause) { + super(message, cause); + } - public InvalidOperatorException(Throwable cause) { - super(cause); - } + public InvalidOperatorException(Throwable cause) { + super(cause); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/NoOperatorCandidatesException.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/NoOperatorCandidatesException.java index 5b742fc1f2..6a42efa5db 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/NoOperatorCandidatesException.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/NoOperatorCandidatesException.java @@ -2,39 +2,38 @@ import at.petrak.hexcasting.api.casting.iota.Iota; import at.petrak.hexcasting.api.casting.math.HexPattern; - import java.util.List; public class NoOperatorCandidatesException extends RuntimeException { - HexPattern pattern; - List args; - - public NoOperatorCandidatesException(HexPattern pattern, List args) { - this.pattern = pattern; - this.args = args; - } - - public NoOperatorCandidatesException(HexPattern pattern, List args, String s) { - super(s); - this.pattern = pattern; - this.args = args; - } - - public HexPattern getPattern() { - return pattern; - } - - public HexPattern setPattern(HexPattern pattern) { - this.pattern = pattern; - return this.pattern; - } - - public List getArgs() { - return args; - } - - public List setArgs(List args) { - this.args = args; - return this.args; - } + HexPattern pattern; + List args; + + public NoOperatorCandidatesException(HexPattern pattern, List args) { + this.pattern = pattern; + this.args = args; + } + + public NoOperatorCandidatesException(HexPattern pattern, List args, String s) { + super(s); + this.pattern = pattern; + this.args = args; + } + + public HexPattern getPattern() { + return pattern; + } + + public HexPattern setPattern(HexPattern pattern) { + this.pattern = pattern; + return this.pattern; + } + + public List getArgs() { + return args; + } + + public List setArgs(List args) { + this.args = args; + return this.args; + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/Operator.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/Operator.kt index 8804e0d628..22dc727f18 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/Operator.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/Operator.kt @@ -8,36 +8,33 @@ import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.IotaType import at.petrak.hexcasting.api.casting.mishaps.Mishap -import at.petrak.hexcasting.common.lib.hex.HexEvalSounds -import java.util.function.Consumer abstract class Operator /** - * @param arity The number of arguments from the stack that this Operator requires; all Operators with the same pattern must have arity. - * @param accepts A function that should return true if the passed list of Iotas satisfies this Operator's type constraints, and false otherwise. + * @param arity The number of arguments from the stack that this Operator requires; all Operators + * with the same pattern must have arity. + * @param accepts A function that should return true if the passed list of Iotas satisfies this + * Operator's type constraints, and false otherwise. */ - ( - @JvmField - val arity: Int, - @JvmField - val accepts: IotaMultiPredicate -) { +(@JvmField val arity: Int, @JvmField val accepts: IotaMultiPredicate) { - /** - * Functionally update the image. Return the image and any side effects. - */ - @Throws(Mishap::class) - abstract fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult + /** Functionally update the image. Return the image and any side effects. */ + @Throws(Mishap::class) + abstract fun operate( + env: CastingEnvironment, + image: CastingImage, + continuation: SpellContinuation + ): OperationResult - - companion object { - /** - * A helper method to take an iota that you know is of iotaType and returning it as an iota of that type. - */ - @JvmStatic - fun downcast(iota: Iota, iotaType: IotaType): T { - check(iota.type === iotaType) { "Attempting to downcast $iota to type: $iotaType" } - return iota as T - } - } -} \ No newline at end of file + companion object { + /** + * A helper method to take an iota that you know is of iotaType and returning it as an iota of + * that type. + */ + @JvmStatic + fun downcast(iota: Iota, iotaType: IotaType): T { + check(iota.type === iotaType) { "Attempting to downcast $iota to type: $iotaType" } + return iota as T + } + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorBasic.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorBasic.kt index 1d43c890ce..edccc43d9f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorBasic.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorBasic.kt @@ -12,28 +12,34 @@ import java.util.function.Consumer abstract class OperatorBasic(arity: Int, accepts: IotaMultiPredicate) : Operator(arity, accepts) { - @Throws(Mishap::class) - override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { - val stack = image.stack.toMutableList() - val args = stack.takeLast(arity) - repeat(arity) { stack.removeLast() } + @Throws(Mishap::class) + override fun operate( + env: CastingEnvironment, + image: CastingImage, + continuation: SpellContinuation + ): OperationResult { + val stack = image.stack.toMutableList() + val args = stack.takeLast(arity) + repeat(arity) { stack.removeLast() } - val ret = apply(args, env) - ret.forEach(Consumer { e: Iota -> stack.add(e) }) + val ret = apply(args, env) + ret.forEach(Consumer { e: Iota -> stack.add(e) }) - val image2 = image.copy(stack = stack, opsConsumed = image.opsConsumed + 1) - return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) - } + val image2 = image.copy(stack = stack, opsConsumed = image.opsConsumed + 1) + return OperationResult(image2, listOf(), continuation, HexEvalSounds.NORMAL_EXECUTE) + } - /** - * / ** - * The method called when this Operator is actually acting on the stack, for real. - * @param iotas An iterable of iotas with [Operator.arity] elements that satisfied [Operator.accepts]. - * @param env The casting environment, to make use of if this operator needs it. - * @return the iotas that this operator will return to the stack (with the first element of the returned iterable being placed deepest into the stack, and the last element on top of the stack). - * @throws Mishap if the Operator mishaps for any reason it will be passed up the chain. - */ - @Throws(Mishap::class) - abstract fun apply(iotas: Iterable, env: CastingEnvironment): Iterable - -} \ No newline at end of file + /** + * / ** The method called when this Operator is actually acting on the stack, for real. + * + * @param iotas An iterable of iotas with [Operator.arity] elements that satisfied + * [Operator.accepts]. + * @param env The casting environment, to make use of if this operator needs it. + * @return the iotas that this operator will return to the stack (with the first element of the + * returned iterable being placed deepest into the stack, and the last element on top of the + * stack). + * @throws Mishap if the Operator mishaps for any reason it will be passed up the chain. + */ + @Throws(Mishap::class) + abstract fun apply(iotas: Iterable, env: CastingEnvironment): Iterable +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorBinary.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorBinary.java index 02914dc4d1..abb25e75e8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorBinary.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorBinary.java @@ -1,17 +1,13 @@ package at.petrak.hexcasting.api.casting.arithmetic.operator; - import at.petrak.hexcasting.api.casting.arithmetic.predicates.IotaMultiPredicate; import at.petrak.hexcasting.api.casting.eval.CastingEnvironment; import at.petrak.hexcasting.api.casting.iota.Iota; -import org.jetbrains.annotations.NotNull; - import java.util.List; import java.util.function.BinaryOperator; +import org.jetbrains.annotations.NotNull; -/** - * A helper class for defining {@link Operator}s of two iotas. - */ +/** A helper class for defining {@link Operator}s of two iotas. */ public class OperatorBinary extends OperatorBasic { public BinaryOperator inner; @@ -21,7 +17,8 @@ public OperatorBinary(IotaMultiPredicate accepts, BinaryOperator inner) { } @Override - public @NotNull Iterable apply(Iterable iotas, @NotNull CastingEnvironment env) { + public @NotNull Iterable apply( + Iterable iotas, @NotNull CastingEnvironment env) { var it = iotas.iterator(); return List.of(inner.apply(it.next(), it.next())); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorUnary.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorUnary.java index 9797b83b1d..fd6eb3bcae 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorUnary.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/operator/OperatorUnary.java @@ -1,17 +1,13 @@ package at.petrak.hexcasting.api.casting.arithmetic.operator; - import at.petrak.hexcasting.api.casting.arithmetic.predicates.IotaMultiPredicate; import at.petrak.hexcasting.api.casting.eval.CastingEnvironment; import at.petrak.hexcasting.api.casting.iota.Iota; -import org.jetbrains.annotations.NotNull; - import java.util.List; import java.util.function.UnaryOperator; +import org.jetbrains.annotations.NotNull; -/** - * A helper class for defining {@link Operator}s of one iota. - */ +/** A helper class for defining {@link Operator}s of one iota. */ public class OperatorUnary extends OperatorBasic { public UnaryOperator inner; @@ -21,7 +17,8 @@ public OperatorUnary(IotaMultiPredicate accepts, UnaryOperator inner) { } @Override - public @NotNull Iterable apply(Iterable iotas, @NotNull CastingEnvironment env) { + public @NotNull Iterable apply( + Iterable iotas, @NotNull CastingEnvironment env) { return List.of(inner.apply(iotas.iterator().next())); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/predicates/IotaMultiPredicate.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/predicates/IotaMultiPredicate.java index 63a64b5a02..837a355bab 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/predicates/IotaMultiPredicate.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/predicates/IotaMultiPredicate.java @@ -3,40 +3,48 @@ import at.petrak.hexcasting.api.casting.iota.Iota; /** - * Used to determine whether a given set of iotas on the stack are acceptable types for - * the operator that is storing this IotaMultiPredicate. + * Used to determine whether a given set of iotas on the stack are acceptable types for the operator + * that is storing this IotaMultiPredicate. */ @FunctionalInterface public interface IotaMultiPredicate { boolean test(Iterable iotas); /** - * The resulting IotaMultiPredicate only returns true if all iotas passed into test match the type dictated by child. + * The resulting IotaMultiPredicate only returns true if all iotas passed into test match the type + * dictated by child. */ static IotaMultiPredicate all(IotaPredicate child) { return new All(child); } + /** - * The resulting IotaMultiPredicate returns true if two iotas are passed, the first matching first, and the second matching second. + * The resulting IotaMultiPredicate returns true if two iotas are passed, the first matching + * first, and the second matching second. */ static IotaMultiPredicate pair(IotaPredicate first, IotaPredicate second) { return new Pair(first, second); } + /** - * The resulting IotaMultiPredicate returns true if three iotas are passed, the first matching first, the second matching second, and the third matching third. + * The resulting IotaMultiPredicate returns true if three iotas are passed, the first matching + * first, the second matching second, and the third matching third. */ static IotaMultiPredicate triple(IotaPredicate first, IotaPredicate second, IotaPredicate third) { return new Triple(first, second, third); } + /** - * The resulting IotaMultiPredicate returns true if at least one iota passed matches needs, and the rest match fallback. + * The resulting IotaMultiPredicate returns true if at least one iota passed matches needs, and + * the rest match fallback. */ static IotaMultiPredicate any(IotaPredicate needs, IotaPredicate fallback) { return new Any(needs, fallback); } /** - * The resulting IotaMultiPredicate returns true if either the first returns true or the second returns true. + * The resulting IotaMultiPredicate returns true if either the first returns true or the second + * returns true. */ static IotaMultiPredicate either(IotaMultiPredicate first, IotaMultiPredicate second) { return new Either(first, second); @@ -46,16 +54,29 @@ record Pair(IotaPredicate first, IotaPredicate second) implements IotaMultiPredi @Override public boolean test(Iterable iotas) { var it = iotas.iterator(); - return it.hasNext() && first.test(it.next()) && it.hasNext() && second.test(it.next()) && !it.hasNext(); + return it.hasNext() + && first.test(it.next()) + && it.hasNext() + && second.test(it.next()) + && !it.hasNext(); } } - record Triple(IotaPredicate first, IotaPredicate second, IotaPredicate third) implements IotaMultiPredicate { + + record Triple(IotaPredicate first, IotaPredicate second, IotaPredicate third) + implements IotaMultiPredicate { @Override public boolean test(Iterable iotas) { var it = iotas.iterator(); - return it.hasNext() && first.test(it.next()) && it.hasNext() && second.test(it.next()) && it.hasNext() && third.test(it.next()) && !it.hasNext(); + return it.hasNext() + && first.test(it.next()) + && it.hasNext() + && second.test(it.next()) + && it.hasNext() + && third.test(it.next()) + && !it.hasNext(); } } + record Any(IotaPredicate needs, IotaPredicate fallback) implements IotaMultiPredicate { @Override public boolean test(Iterable iotas) { @@ -70,6 +91,7 @@ public boolean test(Iterable iotas) { return ok; } } + record All(IotaPredicate inner) implements IotaMultiPredicate { @Override public boolean test(Iterable iotas) { diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/predicates/IotaPredicate.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/predicates/IotaPredicate.java index 41d0648145..60b3208f84 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/predicates/IotaPredicate.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/predicates/IotaPredicate.java @@ -2,19 +2,20 @@ import at.petrak.hexcasting.api.casting.iota.Iota; import at.petrak.hexcasting.api.casting.iota.IotaType; - import java.util.List; /** - * Used to determine whether a given iota is an acceptable type for the operator that is storing this. It must be strictly a function - * of the passed Iota's IotaType, or the caching done by ArithmeticEngine will be invalid. + * Used to determine whether a given iota is an acceptable type for the operator that is storing + * this. It must be strictly a function of the passed Iota's IotaType, or the caching done by + * ArithmeticEngine will be invalid. */ @FunctionalInterface public interface IotaPredicate { boolean test(Iota iota); /** - * The resulting IotaPredicate returns true if the given iota matches either the left or right predicates. + * The resulting IotaPredicate returns true if the given iota matches either the left or right + * predicates. */ static IotaPredicate or(IotaPredicate left, IotaPredicate right) { return new Or(left, right); @@ -28,9 +29,7 @@ static IotaPredicate any(List any) { return new Any(any.toArray(IotaPredicate[]::new)); } - /** - * The resulting IotaPredicate returns true if the given iota's type is type. - */ + /** The resulting IotaPredicate returns true if the given iota's type is type. */ static IotaPredicate ofType(IotaType type) { return new OfType(type); } @@ -47,8 +46,7 @@ record Any(IotaPredicate[] any) implements IotaPredicate { @Override public boolean test(Iota iota) { for (var i : any) { - if (i.test(iota)) - return true; + if (i.test(iota)) return true; } return false; } @@ -61,8 +59,6 @@ public boolean test(Iota iota) { } } - /** - * This IotaPredicate returns true for all iotas. - */ + /** This IotaPredicate returns true for all iotas. */ IotaPredicate TRUE = iota -> true; } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt index e60ff2dfe3..f2b818491f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/Action.kt @@ -5,60 +5,61 @@ 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.iota.Iota -import net.minecraft.world.phys.Vec3 import java.text.DecimalFormat +import net.minecraft.world.phys.Vec3 /** - * Manipulates the stack in some way, usually by popping some number of values off the stack - * and pushing one new value. - * For a more "traditional" pop arguments, push return experience, see [ConstMediaAction]. + * Manipulates the stack in some way, usually by popping some number of values off the stack and + * pushing one new value. For a more "traditional" pop arguments, push return experience, see + * [ConstMediaAction]. * - * Instances of this can exist on the client, but they should NEVER be used there. They only - * exist on the client because Minecraft's registry system demands they do; any information - * the client needs about them is stored elsewhere. (For example, their canonical stroke order - * is stored in [ActionRegistryEntry], and their localization key is gotten from the resource key - * via [at.petrak.hexcasting.api.HexAPI.getActionI18nKey].) + * Instances of this can exist on the client, but they should NEVER be used there. They only exist + * on the client because Minecraft's registry system demands they do; any information the client + * needs about them is stored elsewhere. (For example, their canonical stroke order is stored in + * [ActionRegistryEntry], and their localization key is gotten from the resource key via + * [at.petrak.hexcasting.api.HexAPI.getActionI18nKey].) * * Each action is a singleton */ interface Action { - /** - * Functionally update the image. Return the image and any side effects. - * - * This is a very low-level function -- the implementor is responsible for a lot. For example, - * remember to increment the op count, sil vous plait. - * - * A particle effect at the cast site and various messages and advancements are done automagically. - * - * The userdata tag is copied for you, so you don't need to worry about mutation messing up things - * behind the scenes. - */ - fun operate( - env: CastingEnvironment, - image: CastingImage, - continuation: SpellContinuation - ): OperationResult + /** + * Functionally update the image. Return the image and any side effects. + * + * This is a very low-level function -- the implementor is responsible for a lot. For + * example, remember to increment the op count, sil vous plait. + * + * A particle effect at the cast site and various messages and advancements are done + * automagically. + * + * The userdata tag is copied for you, so you don't need to worry about mutation messing up things + * behind the scenes. + */ + fun operate( + env: CastingEnvironment, + image: CastingImage, + continuation: SpellContinuation + ): OperationResult - companion object { - // I see why vazkii did this: you can't raycast out to infinity! - const val RAYCAST_DISTANCE: Double = 32.0 + companion object { + // I see why vazkii did this: you can't raycast out to infinity! + const val RAYCAST_DISTANCE: Double = 32.0 - // TODO: currently, this means you can't raycast in a very long spell circle, or out of your local ambit into - // your sentinel's. - @JvmStatic - fun raycastEnd(origin: Vec3, look: Vec3): Vec3 = - origin.add(look.normalize().scale(RAYCAST_DISTANCE)) + // TODO: currently, this means you can't raycast in a very long spell circle, or out of your + // local ambit into + // your sentinel's. + @JvmStatic + fun raycastEnd(origin: Vec3, look: Vec3): Vec3 = + origin.add(look.normalize().scale(RAYCAST_DISTANCE)) - @JvmStatic - fun makeConstantOp(x: Iota): Action = object : ConstMediaAction { - override val argc: Int - get() = 0 + @JvmStatic + fun makeConstantOp(x: Iota): Action = + object : ConstMediaAction { + override val argc: Int + get() = 0 - override fun execute(args: List, env: CastingEnvironment): List = - listOf(x) - } + override fun execute(args: List, env: CastingEnvironment): List = listOf(x) + } - public val DOUBLE_FORMATTER = DecimalFormat("####.####") - } + public val DOUBLE_FORMATTER = DecimalFormat("####.####") + } } - 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..9f5563fdb1 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 @@ -9,36 +9,38 @@ import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs import at.petrak.hexcasting.common.lib.hex.HexEvalSounds -/** - * A SimpleOperator that always costs the same amount of media. - */ +/** A SimpleOperator that always costs the same amount of media. */ interface ConstMediaAction : Action { - val argc: Int - val mediaCost: Long - get() = 0 - - fun execute(args: List, env: CastingEnvironment): List - - fun executeWithOpCount(args: List, env: CastingEnvironment): CostMediaActionResult { - val stack = this.execute(args, env) - return CostMediaActionResult(stack) - } - - override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { - val stack = image.stack.toMutableList() - - if (this.argc > stack.size) - throw MishapNotEnoughArgs(this.argc, stack.size) - val args = stack.takeLast(this.argc) - repeat(this.argc) { stack.removeLast() } - val result = this.executeWithOpCount(args, env) - stack.addAll(result.resultStack) - - val sideEffects = mutableListOf(OperatorSideEffect.ConsumeMedia(this.mediaCost)) - - val image2 = image.copy(stack = stack, opsConsumed = image.opsConsumed + result.opCount) - return OperationResult(image2, sideEffects, continuation, HexEvalSounds.NORMAL_EXECUTE) - } - - data class CostMediaActionResult(val resultStack: List, val opCount: Long = 1) + val argc: Int + val mediaCost: Long + get() = 0 + + fun execute(args: List, env: CastingEnvironment): List + + fun executeWithOpCount(args: List, env: CastingEnvironment): CostMediaActionResult { + val stack = this.execute(args, env) + return CostMediaActionResult(stack) + } + + override fun operate( + env: CastingEnvironment, + image: CastingImage, + continuation: SpellContinuation + ): OperationResult { + val stack = image.stack.toMutableList() + + if (this.argc > stack.size) throw MishapNotEnoughArgs(this.argc, stack.size) + val args = stack.takeLast(this.argc) + repeat(this.argc) { stack.removeLast() } + val result = this.executeWithOpCount(args, env) + stack.addAll(result.resultStack) + + val sideEffects = + mutableListOf(OperatorSideEffect.ConsumeMedia(this.mediaCost)) + + val image2 = image.copy(stack = stack, opsConsumed = image.opsConsumed + result.opCount) + return OperationResult(image2, sideEffects, continuation, HexEvalSounds.NORMAL_EXECUTE) + } + + data class CostMediaActionResult(val resultStack: List, val opCount: Long = 1) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/OperationAction.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/OperationAction.kt index 1e9a39f80f..b81947d0bf 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/OperationAction.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/OperationAction.kt @@ -10,15 +10,20 @@ import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidOperatorArgs import at.petrak.hexcasting.common.lib.hex.HexArithmetics /** - * Represents an Operator with the give pattern as its identifier, a special type of Action that calls a different function depending on the type of its arguments. - * This exists so that addons can easily define their own overloads to patterns like addition, subtraction, etc. + * Represents an Operator with the give pattern as its identifier, a special type of Action that + * calls a different function depending on the type of its arguments. This exists so that addons can + * easily define their own overloads to patterns like addition, subtraction, etc. */ data class OperationAction(val pattern: HexPattern) : Action { - override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { - return try { - HexArithmetics.getEngine().run(pattern, env, image, continuation) - } catch (e: NoOperatorCandidatesException) { - throw MishapInvalidOperatorArgs(e.args) - } - } -} \ No newline at end of file + override fun operate( + env: CastingEnvironment, + image: CastingImage, + continuation: SpellContinuation + ): OperationResult { + return try { + HexArithmetics.getEngine().run(pattern, env, image, continuation) + } catch (e: NoOperatorCandidatesException) { + throw MishapInvalidOperatorArgs(e.args) + } + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/SpecialHandler.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/SpecialHandler.java index 14a64879e9..b2299439c3 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/SpecialHandler.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/castables/SpecialHandler.java @@ -7,35 +7,33 @@ /** * Special handling of a pattern. Before checking any of the normal angle-signature based patterns, - * a given pattern is run by all of these special handlers patterns. If none of them return non-null, - * then its signature is checked. - *

- * In the base mod, this is used for number patterns and Bookkeeper's Gambit. - *

- * There's a separation between the special handlers and their factories so we never have to use - * {@link Action} instances on the client. We can have SpecialHandlers on the client though because they're just - * wrappers. + * a given pattern is run by all of these special handlers patterns. If none of them return + * non-null, then its signature is checked. + * + *

In the base mod, this is used for number patterns and Bookkeeper's Gambit. + * + *

There's a separation between the special handlers and their factories so we never have to use + * {@link Action} instances on the client. We can have SpecialHandlers on the client though because + * they're just wrappers. */ public interface SpecialHandler { - /** - * Convert this to an action, for modification of the stack and state. - *

- * This is called on the SERVER-SIDE ONLY. - */ - Action act(); + /** + * Convert this to an action, for modification of the stack and state. + * + *

This is called on the SERVER-SIDE ONLY. + */ + Action act(); - /** - * Get the name of this handler. - */ - Component getName(); + /** Get the name of this handler. */ + Component getName(); - /** - * Given a pattern, possibly make up the special handler from it. - *

- * This is what goes in the registry! Think of it like BlockEntityType vs BlockEntity. - */ - @FunctionalInterface - public interface Factory { - @Nullable T tryMatch(HexPattern pattern, CastingEnvironment env); - } + /** + * Given a pattern, possibly make up the special handler from it. + * + *

This is what goes in the registry! Think of it like BlockEntityType vs BlockEntity. + */ + @FunctionalInterface + public interface Factory { + @Nullable T tryMatch(HexPattern pattern, CastingEnvironment env); + } } 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..4d06840310 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 @@ -13,56 +13,66 @@ import at.petrak.hexcasting.common.lib.hex.HexEvalSounds import net.minecraft.nbt.CompoundTag interface SpellAction : Action { - val argc: Int - - fun hasCastingSound(ctx: CastingEnvironment): Boolean = true - - fun awardsCastingStat(ctx: CastingEnvironment): Boolean = true - - fun execute( - args: List, - env: CastingEnvironment - ): Result - - fun executeWithUserdata( - args: List, env: CastingEnvironment, userData: CompoundTag - ): Result { - return this.execute(args, env) - } - - override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult { - val stack = image.stack.toMutableList() - - if (this.argc > stack.size) - throw MishapNotEnoughArgs(this.argc, stack.size) - val args = stack.takeLast(this.argc) - for (_i in 0 until this.argc) stack.removeLast() - - // execute! - val userDataMut = image.userData.copy() - val result = this.executeWithUserdata(args, env, userDataMut) - - val sideEffects = mutableListOf() - - if (result.cost > 0) - sideEffects.add(OperatorSideEffect.ConsumeMedia(result.cost)) - - sideEffects.add( - OperatorSideEffect.AttemptSpell( - result.effect, - this.hasCastingSound(env), - this.awardsCastingStat(env) - ) - ) - - for (spray in result.particles) - sideEffects.add(OperatorSideEffect.Particles(spray)) - - val image2 = image.copy(stack = stack, opsConsumed = image.opsConsumed + result.opCount, userData = userDataMut) - - val sound = if (this.hasCastingSound(env)) HexEvalSounds.SPELL else HexEvalSounds.MUTE - return OperationResult(image2, sideEffects, continuation, sound) - } - - data class Result(val effect: RenderedSpell, val cost: Long, val particles: List, val opCount: Long = 1) + val argc: Int + + fun hasCastingSound(ctx: CastingEnvironment): Boolean = true + + fun awardsCastingStat(ctx: CastingEnvironment): Boolean = true + + fun execute(args: List, env: CastingEnvironment): Result + + fun executeWithUserdata( + args: List, + env: CastingEnvironment, + userData: CompoundTag + ): Result { + return this.execute(args, env) + } + + override fun operate( + env: CastingEnvironment, + image: CastingImage, + continuation: SpellContinuation + ): OperationResult { + val stack = image.stack.toMutableList() + + if (this.argc > stack.size) throw MishapNotEnoughArgs(this.argc, stack.size) + val args = stack.takeLast(this.argc) + for (_i in 0 until this.argc) stack.removeLast() + + // execute! + val userDataMut = image.userData.copy() + val result = this.executeWithUserdata(args, env, userDataMut) + + val sideEffects = mutableListOf() + + if (result.cost > 0) sideEffects.add(OperatorSideEffect.ConsumeMedia(result.cost)) + + sideEffects.add( + OperatorSideEffect.AttemptSpell( + result.effect, + this.hasCastingSound(env), + this.awardsCastingStat(env) + ) + ) + + for (spray in result.particles) sideEffects.add(OperatorSideEffect.Particles(spray)) + + val image2 = + image.copy( + stack = stack, + opsConsumed = image.opsConsumed + result.opCount, + userData = userDataMut + ) + + val sound = if (this.hasCastingSound(env)) HexEvalSounds.SPELL else HexEvalSounds.MUTE + return OperationResult(image2, sideEffects, continuation, sound) + } + + data class Result( + val effect: RenderedSpell, + val cost: Long, + val particles: List, + val opCount: Long = 1 + ) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/BlockEntityAbstractImpetus.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/BlockEntityAbstractImpetus.java index ac8bf92d0d..73c793f41d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/BlockEntityAbstractImpetus.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/BlockEntityAbstractImpetus.java @@ -8,6 +8,8 @@ import at.petrak.hexcasting.common.items.magic.ItemCreativeUnlocker; import at.petrak.hexcasting.common.lib.HexItems; import com.mojang.datafixers.util.Pair; +import java.text.DecimalFormat; +import java.util.List; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -31,428 +33,427 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; -import java.text.DecimalFormat; -import java.util.List; - /** * Default impl for an impetus, not tecnically necessary but I'm exposing it for ease of use - *

- * This does assume a great deal so you might have to re-implement a lot of this yourself if you + * + *

This does assume a great deal so you might have to re-implement a lot of this yourself if you * wanna do something wild and new */ -public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implements WorldlyContainer { - private static final DecimalFormat DUST_AMOUNT = new DecimalFormat("###,###.##"); - private static final long MAX_CAPACITY = 9_000_000_000_000_000_000L; - - public static final String - TAG_EXECUTION_STATE = "executor", - TAG_MEDIA = "media", - TAG_ERROR_MSG = "errorMsg", - TAG_ERROR_DISPLAY = "errorDisplay", - TAG_PIGMENT = "pigment"; - - // We might try to load the executor in loadModData when the level doesn't exist yet, - // so save the tag and load it lazy - @Nullable CompoundTag lazyExecutionState; - @Nullable - protected CircleExecutionState executionState; - - protected long media = 0; - - // these are null together - @Nullable - protected Component displayMsg = null; - @Nullable - protected ItemStack displayItem = null; - @Nullable - protected FrozenPigment pigment = null; - - - public BlockEntityAbstractImpetus(BlockEntityType pType, BlockPos pWorldPosition, BlockState pBlockState) { - super(pType, pWorldPosition, pBlockState); - } - - public Direction getStartDirection() { - return this.getBlockState().getValue(BlockStateProperties.FACING); - } - - @Nullable - public Component getDisplayMsg() { - return displayMsg; - } - - public void clearDisplay() { - this.displayMsg = null; - this.displayItem = null; - this.sync(); - } - - public void postDisplay(Component error, ItemStack display) { - this.displayMsg = error; - this.displayItem = display; - this.sync(); - } - - public void postMishap(Component mishapDisplay) { - this.postDisplay(mishapDisplay, new ItemStack(Items.MUSIC_DISC_11)); - } - - public void postPrint(Component printDisplay) { - this.postDisplay(printDisplay, new ItemStack(Items.BOOK)); - } - - // Pull this out because we may need to call it both on startup and halfway thru - public void postNoExits(BlockPos pos) { - this.postDisplay( - Component.translatable("hexcasting.tooltip.circle.no_exit", - Component.literal(pos.toShortString()).withStyle(ChatFormatting.RED)), - new ItemStack(Items.OAK_SIGN)); - } - - //region execution - - public void tickExecution() { - if (this.level == null) - return; - - this.setChanged(); - - var state = this.getExecutionState(); - if (state == null) { - return; - } - - var shouldContinue = state.tick(this); - - if (!shouldContinue) { - this.endExecution(); - this.executionState = null; - } else - this.level.scheduleTick(this.getBlockPos(), this.getBlockState().getBlock(), state.getTickSpeed()); - } - - public void endExecution() { - if (this.executionState == null) - return; - - this.executionState.endExecution(this); - } - - /** - * ONLY CALL THIS WHEN YOU KNOW THE WORLD EXISTS AND ON THE SERVER, lazy-loads it - */ - public @Nullable CircleExecutionState getExecutionState() { - if (this.level == null) { - throw new IllegalStateException("didn't you read the doc comment, don't call this if the level is null"); - } - - if (this.executionState != null) - return this.executionState; - - if (this.lazyExecutionState != null) - this.executionState = CircleExecutionState.load(this.lazyExecutionState, (ServerLevel) this.level); - - return this.executionState; - } - - public void startExecution(@Nullable ServerPlayer player) { - if (this.level == null) - return; // TODO: error here? - if (this.level.isClientSide) - return; // TODO: error here? - - if (this.executionState != null) { - return; - } - var result = CircleExecutionState.createNew(this, player); - if (result.isErr()) { - var errPos = result.unwrapErr(); - if (errPos == null) { - ICircleComponent.sfx(this.getBlockPos(), this.getBlockState(), this.level, null, false); - this.postNoExits(this.getBlockPos()); - } else { - ICircleComponent.sfx(errPos, this.level.getBlockState(errPos), this.level, null, false); - this.postDisplay(Component.translatable("hexcasting.tooltip.circle.no_closure", - Component.literal(errPos.toShortString()).withStyle(ChatFormatting.RED)), - new ItemStack(Items.LEAD)); - } - - return; - } - this.executionState = result.unwrap(); - - this.clearDisplay(); - var serverLevel = (ServerLevel) this.level; - serverLevel.scheduleTick(this.getBlockPos(), this.getBlockState().getBlock(), - this.executionState.getTickSpeed()); - serverLevel.setBlockAndUpdate(this.getBlockPos(), - this.getBlockState().setValue(BlockCircleComponent.ENERGIZED, true)); - } - - @Contract(pure = true) - protected static AABB getBounds(List poses) { - int minX = Integer.MAX_VALUE; - int minY = Integer.MAX_VALUE; - int minZ = Integer.MAX_VALUE; - int maxX = Integer.MIN_VALUE; - int maxY = Integer.MIN_VALUE; - int maxZ = Integer.MIN_VALUE; - - for (var pos : poses) { - if (pos.getX() < minX) { - minX = pos.getX(); - } - if (pos.getY() < minY) { - minY = pos.getY(); - } - if (pos.getZ() < minZ) { - minZ = pos.getZ(); - } - if (pos.getX() > maxX) { - maxX = pos.getX(); - } - if (pos.getY() > maxY) { - maxY = pos.getY(); - } - if (pos.getZ() > maxZ) { - maxZ = pos.getZ(); - } - } - - return new AABB(minX, minY, minZ, maxX + 1, maxY + 1, maxZ + 1); - } - - //endregion - - //region media handling - - public long getMedia() { - return this.media; - } - - public void setMedia(long media) { - this.media = media; - sync(); - } - - public long extractMediaFromInsertedItem(ItemStack stack, boolean simulate) { - if (this.media < 0) { - return 0; - } - return MediaHelper.extractMedia(stack, remainingMediaCapacity(), true, simulate); - } - - public void insertMedia(ItemStack stack) { - if (getMedia() >= 0 && !stack.isEmpty() && stack.getItem() == HexItems.CREATIVE_UNLOCKER) { - setInfiniteMedia(); - stack.shrink(1); - } else { - var mediamount = extractMediaFromInsertedItem(stack, false); - if (mediamount > 0) { - this.media = Math.min(mediamount + media, MAX_CAPACITY); - this.sync(); - } - } - } - - public void setInfiniteMedia() { - this.media = -1; - this.sync(); - } - - public long remainingMediaCapacity() { - if (this.media < 0) { - return 0; - } - return Math.max(0, MAX_CAPACITY - this.media); - } - - //endregion - - - public FrozenPigment getPigment() { - if (pigment != null) - return pigment; - if (executionState != null && executionState.casterPigment != null) - return executionState.casterPigment; - return FrozenPigment.DEFAULT.get(); - } - - public @Nullable FrozenPigment setPigment(@Nullable FrozenPigment pigment) { - this.pigment = pigment; - return this.pigment; - } - - @Override - protected void saveModData(CompoundTag tag) { - if (this.executionState != null) { - tag.put(TAG_EXECUTION_STATE, this.executionState.save()); - } - - tag.putLong(TAG_MEDIA, this.media); - - if (this.displayMsg != null && this.displayItem != null) { - tag.putString(TAG_ERROR_MSG, Component.Serializer.toJson(this.displayMsg)); - var itemTag = new CompoundTag(); - this.displayItem.save(itemTag); - tag.put(TAG_ERROR_DISPLAY, itemTag); - } - if (this.pigment != null) - tag.put(TAG_PIGMENT, this.pigment.serializeToNBT()); - } - - @Override - protected void loadModData(CompoundTag tag) { - this.executionState = null; - if (tag.contains(TAG_EXECUTION_STATE, Tag.TAG_COMPOUND)) { - this.lazyExecutionState = tag.getCompound(TAG_EXECUTION_STATE); - } else { - this.lazyExecutionState = null; - } - - if (tag.contains(TAG_MEDIA, Tag.TAG_LONG)) { - this.media = tag.getLong(TAG_MEDIA); - } - - if (tag.contains(TAG_ERROR_MSG, Tag.TAG_STRING) && tag.contains(TAG_ERROR_DISPLAY, Tag.TAG_COMPOUND)) { - var msg = Component.Serializer.fromJson(tag.getString(TAG_ERROR_MSG)); - var display = ItemStack.of(tag.getCompound(TAG_ERROR_DISPLAY)); - this.displayMsg = msg; - this.displayItem = display; - } else { - this.displayMsg = null; - this.displayItem = null; - } - if (tag.contains(TAG_PIGMENT, Tag.TAG_COMPOUND)) - this.pigment = FrozenPigment.fromNBT(tag.getCompound(TAG_PIGMENT)); - } - - public void applyScryingLensOverlay(List> lines, - BlockState state, BlockPos pos, Player observer, Level world, Direction hitFace) { - if (world.getBlockEntity(pos) instanceof BlockEntityAbstractImpetus beai) { - if (beai.getMedia() < 0) { - lines.add(new Pair<>(new ItemStack(HexItems.AMETHYST_DUST), ItemCreativeUnlocker.infiniteMedia(world))); - } else { - var dustCount = (float) beai.getMedia() / (float) MediaConstants.DUST_UNIT; - var dustCmp = Component.translatable("hexcasting.tooltip.media", - DUST_AMOUNT.format(dustCount)); - lines.add(new Pair<>(new ItemStack(HexItems.AMETHYST_DUST), dustCmp)); - } - - if (this.displayMsg != null && this.displayItem != null) { - lines.add(new Pair<>(this.displayItem, this.displayMsg)); - } - } - } - - //region music - - protected int semitoneFromScale(int note) { - var blockBelow = this.level.getBlockState(this.getBlockPos().below()); - var scale = MAJOR_SCALE; - if (blockBelow.is(Blocks.CRYING_OBSIDIAN)) { - scale = MINOR_SCALE; - } else if (blockBelow.is(BlockTags.DOORS) || blockBelow.is(BlockTags.TRAPDOORS)) { - scale = DORIAN_SCALE; - } else if (blockBelow.is(Blocks.PISTON) || blockBelow.is(Blocks.STICKY_PISTON)) { - scale = MIXOLYDIAN_SCALE; - } else if (blockBelow.is(Blocks.BLUE_WOOL) - || blockBelow.is(Blocks.BLUE_CONCRETE) || blockBelow.is(Blocks.BLUE_CONCRETE_POWDER) - || blockBelow.is(Blocks.BLUE_TERRACOTTA) || blockBelow.is(Blocks.BLUE_GLAZED_TERRACOTTA) - || blockBelow.is(Blocks.BLUE_STAINED_GLASS) || blockBelow.is(Blocks.BLUE_STAINED_GLASS_PANE)) { - scale = BLUES_SCALE; - } else if (blockBelow.is(Blocks.BONE_BLOCK)) { - scale = BAD_TIME; - } else if (blockBelow.is(Blocks.COMPOSTER)) { - scale = SUSSY_BAKA; - } - - note = Mth.clamp(note, 0, scale.length - 1); - return scale[note]; - } - - // this is a good use of my time - private static final int[] MAJOR_SCALE = {0, 2, 4, 5, 7, 9, 11, 12}; - private static final int[] MINOR_SCALE = {0, 2, 3, 5, 7, 8, 11, 12}; - private static final int[] DORIAN_SCALE = {0, 2, 3, 5, 7, 9, 10, 12}; - private static final int[] MIXOLYDIAN_SCALE = {0, 2, 4, 5, 7, 9, 10, 12}; - private static final int[] BLUES_SCALE = {0, 3, 5, 6, 7, 10, 12}; - private static final int[] BAD_TIME = {0, 0, 12, 7, 6, 5, 3, 0, 3, 5}; - private static final int[] SUSSY_BAKA = {5, 8, 10, 11, 10, 8, 5, 3, 7, 5}; - - //endregion - - //region item handler contract stuff - private static final int[] SLOTS = {0}; - - @Override - public int[] getSlotsForFace(Direction var1) { - return SLOTS; - } - - @Override - public boolean canPlaceItemThroughFace(int index, ItemStack stack, @Nullable Direction dir) { - return this.canPlaceItem(index, stack); - } - - @Override - public boolean canTakeItemThroughFace(int var1, ItemStack var2, Direction var3) { - return false; - } - - @Override - public int getContainerSize() { - return 1; - } - - @Override - public boolean isEmpty() { - return true; - } - - @Override - public ItemStack getItem(int index) { - return ItemStack.EMPTY.copy(); - } - - @Override - public ItemStack removeItem(int index, int count) { - return ItemStack.EMPTY.copy(); - } - - @Override - public ItemStack removeItemNoUpdate(int index) { - return ItemStack.EMPTY.copy(); - } - - @Override - public void setItem(int index, ItemStack stack) { - insertMedia(stack); - } - - @Override - public boolean stillValid(Player player) { - return false; - } - - @Override - public void clearContent() { - // NO-OP - } - - @Override - public boolean canPlaceItem(int index, ItemStack stack) { - if (remainingMediaCapacity() == 0) { - return false; - } - - if (stack.is(HexItems.CREATIVE_UNLOCKER)) { - return true; - } - - var mediamount = extractMediaFromInsertedItem(stack, true); - return mediamount > 0; - } - - //endregion +public abstract class BlockEntityAbstractImpetus extends HexBlockEntity + implements WorldlyContainer { + private static final DecimalFormat DUST_AMOUNT = new DecimalFormat("###,###.##"); + private static final long MAX_CAPACITY = 9_000_000_000_000_000_000L; + + public static final String TAG_EXECUTION_STATE = "executor", + TAG_MEDIA = "media", + TAG_ERROR_MSG = "errorMsg", + TAG_ERROR_DISPLAY = "errorDisplay", + TAG_PIGMENT = "pigment"; + + // We might try to load the executor in loadModData when the level doesn't exist yet, + // so save the tag and load it lazy + @Nullable CompoundTag lazyExecutionState; + @Nullable protected CircleExecutionState executionState; + + protected long media = 0; + + // these are null together + @Nullable protected Component displayMsg = null; + @Nullable protected ItemStack displayItem = null; + @Nullable protected FrozenPigment pigment = null; + + public BlockEntityAbstractImpetus( + BlockEntityType pType, BlockPos pWorldPosition, BlockState pBlockState) { + super(pType, pWorldPosition, pBlockState); + } + + public Direction getStartDirection() { + return this.getBlockState().getValue(BlockStateProperties.FACING); + } + + @Nullable public Component getDisplayMsg() { + return displayMsg; + } + + public void clearDisplay() { + this.displayMsg = null; + this.displayItem = null; + this.sync(); + } + + public void postDisplay(Component error, ItemStack display) { + this.displayMsg = error; + this.displayItem = display; + this.sync(); + } + + public void postMishap(Component mishapDisplay) { + this.postDisplay(mishapDisplay, new ItemStack(Items.MUSIC_DISC_11)); + } + + public void postPrint(Component printDisplay) { + this.postDisplay(printDisplay, new ItemStack(Items.BOOK)); + } + + // Pull this out because we may need to call it both on startup and halfway thru + public void postNoExits(BlockPos pos) { + this.postDisplay( + Component.translatable( + "hexcasting.tooltip.circle.no_exit", + Component.literal(pos.toShortString()).withStyle(ChatFormatting.RED)), + new ItemStack(Items.OAK_SIGN)); + } + + // region execution + + public void tickExecution() { + if (this.level == null) return; + + this.setChanged(); + + var state = this.getExecutionState(); + if (state == null) { + return; + } + + var shouldContinue = state.tick(this); + + if (!shouldContinue) { + this.endExecution(); + this.executionState = null; + } else + this.level.scheduleTick( + this.getBlockPos(), this.getBlockState().getBlock(), state.getTickSpeed()); + } + + public void endExecution() { + if (this.executionState == null) return; + + this.executionState.endExecution(this); + } + + /** ONLY CALL THIS WHEN YOU KNOW THE WORLD EXISTS AND ON THE SERVER, lazy-loads it */ + public @Nullable CircleExecutionState getExecutionState() { + if (this.level == null) { + throw new IllegalStateException( + "didn't you read the doc comment, don't call this if the level is null"); + } + + if (this.executionState != null) return this.executionState; + + if (this.lazyExecutionState != null) + this.executionState = + CircleExecutionState.load(this.lazyExecutionState, (ServerLevel) this.level); + + return this.executionState; + } + + public void startExecution(@Nullable ServerPlayer player) { + if (this.level == null) return; // TODO: error here? + if (this.level.isClientSide) return; // TODO: error here? + + if (this.executionState != null) { + return; + } + var result = CircleExecutionState.createNew(this, player); + if (result.isErr()) { + var errPos = result.unwrapErr(); + if (errPos == null) { + ICircleComponent.sfx(this.getBlockPos(), this.getBlockState(), this.level, null, false); + this.postNoExits(this.getBlockPos()); + } else { + ICircleComponent.sfx(errPos, this.level.getBlockState(errPos), this.level, null, false); + this.postDisplay( + Component.translatable( + "hexcasting.tooltip.circle.no_closure", + Component.literal(errPos.toShortString()).withStyle(ChatFormatting.RED)), + new ItemStack(Items.LEAD)); + } + + return; + } + this.executionState = result.unwrap(); + + this.clearDisplay(); + var serverLevel = (ServerLevel) this.level; + serverLevel.scheduleTick( + this.getBlockPos(), this.getBlockState().getBlock(), this.executionState.getTickSpeed()); + serverLevel.setBlockAndUpdate( + this.getBlockPos(), this.getBlockState().setValue(BlockCircleComponent.ENERGIZED, true)); + } + + @Contract(pure = true) + protected static AABB getBounds(List poses) { + int minX = Integer.MAX_VALUE; + int minY = Integer.MAX_VALUE; + int minZ = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + int maxY = Integer.MIN_VALUE; + int maxZ = Integer.MIN_VALUE; + + for (var pos : poses) { + if (pos.getX() < minX) { + minX = pos.getX(); + } + if (pos.getY() < minY) { + minY = pos.getY(); + } + if (pos.getZ() < minZ) { + minZ = pos.getZ(); + } + if (pos.getX() > maxX) { + maxX = pos.getX(); + } + if (pos.getY() > maxY) { + maxY = pos.getY(); + } + if (pos.getZ() > maxZ) { + maxZ = pos.getZ(); + } + } + + return new AABB(minX, minY, minZ, maxX + 1, maxY + 1, maxZ + 1); + } + + // endregion + + // region media handling + + public long getMedia() { + return this.media; + } + + public void setMedia(long media) { + this.media = media; + sync(); + } + + public long extractMediaFromInsertedItem(ItemStack stack, boolean simulate) { + if (this.media < 0) { + return 0; + } + return MediaHelper.extractMedia(stack, remainingMediaCapacity(), true, simulate); + } + + public void insertMedia(ItemStack stack) { + if (getMedia() >= 0 && !stack.isEmpty() && stack.getItem() == HexItems.CREATIVE_UNLOCKER) { + setInfiniteMedia(); + stack.shrink(1); + } else { + var mediamount = extractMediaFromInsertedItem(stack, false); + if (mediamount > 0) { + this.media = Math.min(mediamount + media, MAX_CAPACITY); + this.sync(); + } + } + } + + public void setInfiniteMedia() { + this.media = -1; + this.sync(); + } + + public long remainingMediaCapacity() { + if (this.media < 0) { + return 0; + } + return Math.max(0, MAX_CAPACITY - this.media); + } + + // endregion + + public FrozenPigment getPigment() { + if (pigment != null) return pigment; + if (executionState != null && executionState.casterPigment != null) + return executionState.casterPigment; + return FrozenPigment.DEFAULT.get(); + } + + public @Nullable FrozenPigment setPigment(@Nullable FrozenPigment pigment) { + this.pigment = pigment; + return this.pigment; + } + + @Override + protected void saveModData(CompoundTag tag) { + if (this.executionState != null) { + tag.put(TAG_EXECUTION_STATE, this.executionState.save()); + } + + tag.putLong(TAG_MEDIA, this.media); + + if (this.displayMsg != null && this.displayItem != null) { + tag.putString(TAG_ERROR_MSG, Component.Serializer.toJson(this.displayMsg)); + var itemTag = new CompoundTag(); + this.displayItem.save(itemTag); + tag.put(TAG_ERROR_DISPLAY, itemTag); + } + if (this.pigment != null) tag.put(TAG_PIGMENT, this.pigment.serializeToNBT()); + } + + @Override + protected void loadModData(CompoundTag tag) { + this.executionState = null; + if (tag.contains(TAG_EXECUTION_STATE, Tag.TAG_COMPOUND)) { + this.lazyExecutionState = tag.getCompound(TAG_EXECUTION_STATE); + } else { + this.lazyExecutionState = null; + } + + if (tag.contains(TAG_MEDIA, Tag.TAG_LONG)) { + this.media = tag.getLong(TAG_MEDIA); + } + + if (tag.contains(TAG_ERROR_MSG, Tag.TAG_STRING) + && tag.contains(TAG_ERROR_DISPLAY, Tag.TAG_COMPOUND)) { + var msg = Component.Serializer.fromJson(tag.getString(TAG_ERROR_MSG)); + var display = ItemStack.of(tag.getCompound(TAG_ERROR_DISPLAY)); + this.displayMsg = msg; + this.displayItem = display; + } else { + this.displayMsg = null; + this.displayItem = null; + } + if (tag.contains(TAG_PIGMENT, Tag.TAG_COMPOUND)) + this.pigment = FrozenPigment.fromNBT(tag.getCompound(TAG_PIGMENT)); + } + + public void applyScryingLensOverlay( + List> lines, + BlockState state, + BlockPos pos, + Player observer, + Level world, + Direction hitFace) { + if (world.getBlockEntity(pos) instanceof BlockEntityAbstractImpetus beai) { + if (beai.getMedia() < 0) { + lines.add( + new Pair<>( + new ItemStack(HexItems.AMETHYST_DUST), ItemCreativeUnlocker.infiniteMedia(world))); + } else { + var dustCount = (float) beai.getMedia() / (float) MediaConstants.DUST_UNIT; + var dustCmp = + Component.translatable("hexcasting.tooltip.media", DUST_AMOUNT.format(dustCount)); + lines.add(new Pair<>(new ItemStack(HexItems.AMETHYST_DUST), dustCmp)); + } + + if (this.displayMsg != null && this.displayItem != null) { + lines.add(new Pair<>(this.displayItem, this.displayMsg)); + } + } + } + + // region music + + protected int semitoneFromScale(int note) { + var blockBelow = this.level.getBlockState(this.getBlockPos().below()); + var scale = MAJOR_SCALE; + if (blockBelow.is(Blocks.CRYING_OBSIDIAN)) { + scale = MINOR_SCALE; + } else if (blockBelow.is(BlockTags.DOORS) || blockBelow.is(BlockTags.TRAPDOORS)) { + scale = DORIAN_SCALE; + } else if (blockBelow.is(Blocks.PISTON) || blockBelow.is(Blocks.STICKY_PISTON)) { + scale = MIXOLYDIAN_SCALE; + } else if (blockBelow.is(Blocks.BLUE_WOOL) + || blockBelow.is(Blocks.BLUE_CONCRETE) + || blockBelow.is(Blocks.BLUE_CONCRETE_POWDER) + || blockBelow.is(Blocks.BLUE_TERRACOTTA) + || blockBelow.is(Blocks.BLUE_GLAZED_TERRACOTTA) + || blockBelow.is(Blocks.BLUE_STAINED_GLASS) + || blockBelow.is(Blocks.BLUE_STAINED_GLASS_PANE)) { + scale = BLUES_SCALE; + } else if (blockBelow.is(Blocks.BONE_BLOCK)) { + scale = BAD_TIME; + } else if (blockBelow.is(Blocks.COMPOSTER)) { + scale = SUSSY_BAKA; + } + + note = Mth.clamp(note, 0, scale.length - 1); + return scale[note]; + } + + // this is a good use of my time + private static final int[] MAJOR_SCALE = {0, 2, 4, 5, 7, 9, 11, 12}; + private static final int[] MINOR_SCALE = {0, 2, 3, 5, 7, 8, 11, 12}; + private static final int[] DORIAN_SCALE = {0, 2, 3, 5, 7, 9, 10, 12}; + private static final int[] MIXOLYDIAN_SCALE = {0, 2, 4, 5, 7, 9, 10, 12}; + private static final int[] BLUES_SCALE = {0, 3, 5, 6, 7, 10, 12}; + private static final int[] BAD_TIME = {0, 0, 12, 7, 6, 5, 3, 0, 3, 5}; + private static final int[] SUSSY_BAKA = {5, 8, 10, 11, 10, 8, 5, 3, 7, 5}; + + // endregion + + // region item handler contract stuff + private static final int[] SLOTS = {0}; + + @Override + public int[] getSlotsForFace(Direction var1) { + return SLOTS; + } + + @Override + public boolean canPlaceItemThroughFace(int index, ItemStack stack, @Nullable Direction dir) { + return this.canPlaceItem(index, stack); + } + + @Override + public boolean canTakeItemThroughFace(int var1, ItemStack var2, Direction var3) { + return false; + } + + @Override + public int getContainerSize() { + return 1; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public ItemStack getItem(int index) { + return ItemStack.EMPTY.copy(); + } + + @Override + public ItemStack removeItem(int index, int count) { + return ItemStack.EMPTY.copy(); + } + + @Override + public ItemStack removeItemNoUpdate(int index) { + return ItemStack.EMPTY.copy(); + } + + @Override + public void setItem(int index, ItemStack stack) { + insertMedia(stack); + } + + @Override + public boolean stillValid(Player player) { + return false; + } + + @Override + public void clearContent() { + // NO-OP + } + + @Override + public boolean canPlaceItem(int index, ItemStack stack) { + if (remainingMediaCapacity() == 0) { + return false; + } + + if (stack.is(HexItems.CREATIVE_UNLOCKER)) { + return true; + } + + var mediamount = extractMediaFromInsertedItem(stack, true); + return mediamount > 0; + } + + // endregion } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/CircleExecutionState.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/CircleExecutionState.java index f95fa84f01..9abaf8e67a 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/CircleExecutionState.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/CircleExecutionState.java @@ -7,6 +7,7 @@ import at.petrak.hexcasting.api.pigment.FrozenPigment; import at.petrak.hexcasting.api.utils.HexUtils; import com.mojang.datafixers.util.Pair; +import java.util.*; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -22,287 +23,323 @@ import net.minecraft.world.phys.AABB; import org.jetbrains.annotations.Nullable; -import java.util.*; - -/** - * See {@link BlockEntityAbstractImpetus}, this is what's stored in it - */ +/** See {@link BlockEntityAbstractImpetus}, this is what's stored in it */ public class CircleExecutionState { - public static final String - TAG_IMPETUS_POS = "impetus_pos", - TAG_IMPETUS_DIR = "impetus_dir", - TAG_KNOWN_POSITIONS = "known_positions", - TAG_REACHED_POSITIONS = "reached_positions", - TAG_CURRENT_POS = "current_pos", - TAG_ENTERED_FROM = "entered_from", - TAG_IMAGE = "image", - TAG_CASTER = "caster", - TAG_PIGMENT = "pigment"; - - public final BlockPos impetusPos; - public final Direction impetusDir; - // Does contain the starting impetus - public final Set knownPositions; - public final List reachedPositions; - public BlockPos currentPos; - public Direction enteredFrom; - public CastingImage currentImage; - public @Nullable UUID caster; - public @Nullable FrozenPigment casterPigment; - - public final AABB bounds; - - - protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, Set knownPositions, - List reachedPositions, BlockPos currentPos, Direction enteredFrom, - CastingImage currentImage, @Nullable UUID caster, @Nullable FrozenPigment casterPigment) { - this.impetusPos = impetusPos; - this.impetusDir = impetusDir; - this.knownPositions = knownPositions; - this.reachedPositions = reachedPositions; - this.currentPos = currentPos; - this.enteredFrom = enteredFrom; - this.currentImage = currentImage; - this.caster = caster; - this.casterPigment = casterPigment; - - this.bounds = BlockEntityAbstractImpetus.getBounds(new ArrayList<>(this.knownPositions)); - } - - public @Nullable ServerPlayer getCaster(ServerLevel world) { - if (this.caster == null) { - return null; - } - var entity = world.getEntity(this.caster); - if (entity instanceof ServerPlayer serverPlayer) { - return serverPlayer; - } - // there's a problem if this branch is reached - return null; - } - - // Return OK if it succeeded; returns Err if it didn't close and the location - public static Result createNew(BlockEntityAbstractImpetus impetus, - @Nullable ServerPlayer caster) { - var level = (ServerLevel) impetus.getLevel(); - - if (level == null) - return new Result.Err<>(null); - - // Flood fill! Just like VCC all over again. - // this contains tentative positions and directions entered from - var todo = new Stack>(); - todo.add(Pair.of(impetus.getStartDirection(), impetus.getBlockPos().relative(impetus.getStartDirection()))); - var seenGoodPosSet = new HashSet(); - var seenGoodPositions = new ArrayList(); - - while (!todo.isEmpty()) { - var pair = todo.pop(); - var enterDir = pair.getFirst(); - var herePos = pair.getSecond(); - - var hereBs = level.getBlockState(herePos); - if (!(hereBs.getBlock() instanceof ICircleComponent cmp)) { - continue; - } - if (!cmp.canEnterFromDirection(enterDir, herePos, hereBs, level)) { - continue; - } - - if (seenGoodPosSet.add(herePos)) { - // it's new - seenGoodPositions.add(herePos); - var outs = cmp.possibleExitDirections(herePos, hereBs, level); - for (var out : outs) { - todo.add(Pair.of(out, herePos.relative(out))); - } - } - } - - if (seenGoodPositions.isEmpty()) { - return new Result.Err<>(null); - } else if (!seenGoodPosSet.contains(impetus.getBlockPos())) { - // we can't enter from the side the directrix exits from, so this means we couldn't loop back. - // the last item we tried to examine will always be a terminal slate (b/c if it wasn't, - // then the *next* slate would be last qed) - return new Result.Err<>(seenGoodPositions.get(seenGoodPositions.size() - 1)); - } - - var knownPositions = new HashSet<>(seenGoodPositions); - var reachedPositions = new ArrayList(); - reachedPositions.add(impetus.getBlockPos()); - var start = seenGoodPositions.get(0); - - FrozenPigment colorizer = null; - UUID casterUUID; - if (caster == null) { - casterUUID = null; - } else { - colorizer = HexAPI.instance().getColorizer(caster); - casterUUID = caster.getUUID(); - } - return new Result.Ok<>( - new CircleExecutionState(impetus.getBlockPos(), impetus.getStartDirection(), knownPositions, - reachedPositions, start, impetus.getStartDirection(), new CastingImage(), casterUUID, colorizer)); - } - - public CompoundTag save() { - var out = new CompoundTag(); - - out.put(TAG_IMPETUS_POS, NbtUtils.writeBlockPos(this.impetusPos)); - out.putByte(TAG_IMPETUS_DIR, (byte) this.impetusDir.ordinal()); - - var knownTag = new ListTag(); - for (var bp : this.knownPositions) { - knownTag.add(NbtUtils.writeBlockPos(bp)); - } - out.put(TAG_KNOWN_POSITIONS, knownTag); - - var reachedTag = new ListTag(); - for (var bp : this.reachedPositions) { - reachedTag.add(NbtUtils.writeBlockPos(bp)); - } - out.put(TAG_REACHED_POSITIONS, reachedTag); - - out.put(TAG_CURRENT_POS, NbtUtils.writeBlockPos(this.currentPos)); - out.putByte(TAG_ENTERED_FROM, (byte) this.enteredFrom.ordinal()); - out.put(TAG_IMAGE, this.currentImage.serializeToNbt()); - - if (this.caster != null) - out.putUUID(TAG_CASTER, this.caster); - - if (this.casterPigment != null) - out.put(TAG_PIGMENT, this.casterPigment.serializeToNBT()); - - return out; - } - - public static CircleExecutionState load(CompoundTag nbt, ServerLevel world) { - var startPos = NbtUtils.readBlockPos(nbt.getCompound(TAG_IMPETUS_POS)); - var startDir = Direction.values()[nbt.getByte(TAG_IMPETUS_DIR)]; - - var knownPositions = new HashSet(); - var knownTag = nbt.getList(TAG_KNOWN_POSITIONS, Tag.TAG_COMPOUND); - for (var tag : knownTag) { - knownPositions.add(NbtUtils.readBlockPos(HexUtils.downcast(tag, CompoundTag.TYPE))); - } - var reachedPositions = new ArrayList(); - var reachedTag = nbt.getList(TAG_REACHED_POSITIONS, Tag.TAG_COMPOUND); - for (var tag : reachedTag) { - reachedPositions.add(NbtUtils.readBlockPos(HexUtils.downcast(tag, CompoundTag.TYPE))); - } - - var currentPos = NbtUtils.readBlockPos(nbt.getCompound(TAG_CURRENT_POS)); - var enteredFrom = Direction.values()[nbt.getByte(TAG_ENTERED_FROM)]; - var image = CastingImage.loadFromNbt(nbt.getCompound(TAG_IMAGE), world); - - UUID caster = null; - if (nbt.hasUUID(TAG_CASTER)) - caster = nbt.getUUID(TAG_CASTER); - - FrozenPigment pigment = null; - if (nbt.contains(TAG_PIGMENT, Tag.TAG_COMPOUND)) - pigment = FrozenPigment.fromNBT(nbt.getCompound(TAG_PIGMENT)); - - return new CircleExecutionState(startPos, startDir, knownPositions, reachedPositions, currentPos, - enteredFrom, image, caster, pigment); - } - - /** - * Update this, also mutates the impetus. - *

- * Returns whether to continue. - */ - public boolean tick(BlockEntityAbstractImpetus impetus) { - var world = (ServerLevel) impetus.getLevel(); - - if (world == null) - return true; // if the world is null, try again next tick. - - var env = new CircleCastEnv(world, this); - - var executorBlockState = world.getBlockState(this.currentPos); - if (!(executorBlockState.getBlock() instanceof ICircleComponent executor)) { - // TODO: notification of the error? - ICircleComponent.sfx(this.currentPos, executorBlockState, world, - Objects.requireNonNull(env.getImpetus()), false); - return false; - } - - executorBlockState = executor.startEnergized(this.currentPos, executorBlockState, world); - this.reachedPositions.add(this.currentPos); - - // Do the execution! - boolean halt = false; - var ctrl = executor.acceptControlFlow(this.currentImage, env, this.enteredFrom, this.currentPos, - executorBlockState, world); - - if (env.getImpetus() == null) - return false; //the impetus got removed during the cast and no longer exists in the world. stop casting - - if (ctrl instanceof ICircleComponent.ControlFlow.Stop) { - // acceptControlFlow should have already posted the error - halt = true; - } else if (ctrl instanceof ICircleComponent.ControlFlow.Continue cont) { - Pair found = null; - - for (var exit : cont.exits) { - var there = world.getBlockState(exit.getFirst()); - if (there.getBlock() instanceof ICircleComponent cc - && cc.canEnterFromDirection(exit.getSecond(), exit.getFirst(), there, world)) { - if (found != null) { - // oh no! - impetus.postDisplay( - Component.translatable("hexcasting.tooltip.circle.many_exits", - Component.literal(this.currentPos.toShortString()).withStyle(ChatFormatting.RED)), - new ItemStack(Items.COMPASS)); - ICircleComponent.sfx(this.currentPos, executorBlockState, world, - Objects.requireNonNull(env.getImpetus()), false); - halt = true; - break; - } else { - found = exit; - } - } - } - - if (found == null) { - // will never enter here if there were too many because found will have been set - ICircleComponent.sfx(this.currentPos, executorBlockState, world, - Objects.requireNonNull(env.getImpetus()), false); - impetus.postNoExits(this.currentPos); - halt = true; - } else { - // A single valid exit position has been found. - ICircleComponent.sfx(this.currentPos, executorBlockState, world, - Objects.requireNonNull(env.getImpetus()), true); - currentPos = found.getFirst(); - enteredFrom = found.getSecond(); - currentImage = cont.update.withOverriddenUsedOps(0); // reset ops used after each slate finishes executing - } - } - - return !halt; - } - - /** - * How many ticks should pass between activations, given the number of blocks encountered so far. - */ - protected int getTickSpeed() { - return Math.max(2, 10 - (this.reachedPositions.size() - 1) / 3); - } - - public void endExecution(BlockEntityAbstractImpetus impetus) { - var world = (ServerLevel) impetus.getLevel(); - - if (world == null) - return; // TODO: error here? - - for (var pos : this.reachedPositions) { - var there = world.getBlockState(pos); - if (there.getBlock() instanceof ICircleComponent cc) { - cc.endEnergized(pos, there, world); - } - } - } + public static final String TAG_IMPETUS_POS = "impetus_pos", + TAG_IMPETUS_DIR = "impetus_dir", + TAG_KNOWN_POSITIONS = "known_positions", + TAG_REACHED_POSITIONS = "reached_positions", + TAG_CURRENT_POS = "current_pos", + TAG_ENTERED_FROM = "entered_from", + TAG_IMAGE = "image", + TAG_CASTER = "caster", + TAG_PIGMENT = "pigment"; + + public final BlockPos impetusPos; + public final Direction impetusDir; + // Does contain the starting impetus + public final Set knownPositions; + public final List reachedPositions; + public BlockPos currentPos; + public Direction enteredFrom; + public CastingImage currentImage; + public @Nullable UUID caster; + public @Nullable FrozenPigment casterPigment; + + public final AABB bounds; + + protected CircleExecutionState( + BlockPos impetusPos, + Direction impetusDir, + Set knownPositions, + List reachedPositions, + BlockPos currentPos, + Direction enteredFrom, + CastingImage currentImage, + @Nullable UUID caster, + @Nullable FrozenPigment casterPigment) { + this.impetusPos = impetusPos; + this.impetusDir = impetusDir; + this.knownPositions = knownPositions; + this.reachedPositions = reachedPositions; + this.currentPos = currentPos; + this.enteredFrom = enteredFrom; + this.currentImage = currentImage; + this.caster = caster; + this.casterPigment = casterPigment; + + this.bounds = BlockEntityAbstractImpetus.getBounds(new ArrayList<>(this.knownPositions)); + } + + public @Nullable ServerPlayer getCaster(ServerLevel world) { + if (this.caster == null) { + return null; + } + var entity = world.getEntity(this.caster); + if (entity instanceof ServerPlayer serverPlayer) { + return serverPlayer; + } + // there's a problem if this branch is reached + return null; + } + + // Return OK if it succeeded; returns Err if it didn't close and the location + public static Result createNew( + BlockEntityAbstractImpetus impetus, @Nullable ServerPlayer caster) { + var level = (ServerLevel) impetus.getLevel(); + + if (level == null) return new Result.Err<>(null); + + // Flood fill! Just like VCC all over again. + // this contains tentative positions and directions entered from + var todo = new Stack>(); + todo.add( + Pair.of( + impetus.getStartDirection(), + impetus.getBlockPos().relative(impetus.getStartDirection()))); + var seenGoodPosSet = new HashSet(); + var seenGoodPositions = new ArrayList(); + + while (!todo.isEmpty()) { + var pair = todo.pop(); + var enterDir = pair.getFirst(); + var herePos = pair.getSecond(); + + var hereBs = level.getBlockState(herePos); + if (!(hereBs.getBlock() instanceof ICircleComponent cmp)) { + continue; + } + if (!cmp.canEnterFromDirection(enterDir, herePos, hereBs, level)) { + continue; + } + + if (seenGoodPosSet.add(herePos)) { + // it's new + seenGoodPositions.add(herePos); + var outs = cmp.possibleExitDirections(herePos, hereBs, level); + for (var out : outs) { + todo.add(Pair.of(out, herePos.relative(out))); + } + } + } + + if (seenGoodPositions.isEmpty()) { + return new Result.Err<>(null); + } else if (!seenGoodPosSet.contains(impetus.getBlockPos())) { + // we can't enter from the side the directrix exits from, so this means we couldn't loop back. + // the last item we tried to examine will always be a terminal slate (b/c if it wasn't, + // then the *next* slate would be last qed) + return new Result.Err<>(seenGoodPositions.get(seenGoodPositions.size() - 1)); + } + + var knownPositions = new HashSet<>(seenGoodPositions); + var reachedPositions = new ArrayList(); + reachedPositions.add(impetus.getBlockPos()); + var start = seenGoodPositions.get(0); + + FrozenPigment colorizer = null; + UUID casterUUID; + if (caster == null) { + casterUUID = null; + } else { + colorizer = HexAPI.instance().getColorizer(caster); + casterUUID = caster.getUUID(); + } + return new Result.Ok<>( + new CircleExecutionState( + impetus.getBlockPos(), + impetus.getStartDirection(), + knownPositions, + reachedPositions, + start, + impetus.getStartDirection(), + new CastingImage(), + casterUUID, + colorizer)); + } + + public CompoundTag save() { + var out = new CompoundTag(); + + out.put(TAG_IMPETUS_POS, NbtUtils.writeBlockPos(this.impetusPos)); + out.putByte(TAG_IMPETUS_DIR, (byte) this.impetusDir.ordinal()); + + var knownTag = new ListTag(); + for (var bp : this.knownPositions) { + knownTag.add(NbtUtils.writeBlockPos(bp)); + } + out.put(TAG_KNOWN_POSITIONS, knownTag); + + var reachedTag = new ListTag(); + for (var bp : this.reachedPositions) { + reachedTag.add(NbtUtils.writeBlockPos(bp)); + } + out.put(TAG_REACHED_POSITIONS, reachedTag); + + out.put(TAG_CURRENT_POS, NbtUtils.writeBlockPos(this.currentPos)); + out.putByte(TAG_ENTERED_FROM, (byte) this.enteredFrom.ordinal()); + out.put(TAG_IMAGE, this.currentImage.serializeToNbt()); + + if (this.caster != null) out.putUUID(TAG_CASTER, this.caster); + + if (this.casterPigment != null) out.put(TAG_PIGMENT, this.casterPigment.serializeToNBT()); + + return out; + } + + public static CircleExecutionState load(CompoundTag nbt, ServerLevel world) { + var startPos = NbtUtils.readBlockPos(nbt.getCompound(TAG_IMPETUS_POS)); + var startDir = Direction.values()[nbt.getByte(TAG_IMPETUS_DIR)]; + + var knownPositions = new HashSet(); + var knownTag = nbt.getList(TAG_KNOWN_POSITIONS, Tag.TAG_COMPOUND); + for (var tag : knownTag) { + knownPositions.add(NbtUtils.readBlockPos(HexUtils.downcast(tag, CompoundTag.TYPE))); + } + var reachedPositions = new ArrayList(); + var reachedTag = nbt.getList(TAG_REACHED_POSITIONS, Tag.TAG_COMPOUND); + for (var tag : reachedTag) { + reachedPositions.add(NbtUtils.readBlockPos(HexUtils.downcast(tag, CompoundTag.TYPE))); + } + + var currentPos = NbtUtils.readBlockPos(nbt.getCompound(TAG_CURRENT_POS)); + var enteredFrom = Direction.values()[nbt.getByte(TAG_ENTERED_FROM)]; + var image = CastingImage.loadFromNbt(nbt.getCompound(TAG_IMAGE), world); + + UUID caster = null; + if (nbt.hasUUID(TAG_CASTER)) caster = nbt.getUUID(TAG_CASTER); + + FrozenPigment pigment = null; + if (nbt.contains(TAG_PIGMENT, Tag.TAG_COMPOUND)) + pigment = FrozenPigment.fromNBT(nbt.getCompound(TAG_PIGMENT)); + + return new CircleExecutionState( + startPos, + startDir, + knownPositions, + reachedPositions, + currentPos, + enteredFrom, + image, + caster, + pigment); + } + + /** + * Update this, also mutates the impetus. + * + *

Returns whether to continue. + */ + public boolean tick(BlockEntityAbstractImpetus impetus) { + var world = (ServerLevel) impetus.getLevel(); + + if (world == null) return true; // if the world is null, try again next tick. + + var env = new CircleCastEnv(world, this); + + var executorBlockState = world.getBlockState(this.currentPos); + if (!(executorBlockState.getBlock() instanceof ICircleComponent executor)) { + // TODO: notification of the error? + ICircleComponent.sfx( + this.currentPos, + executorBlockState, + world, + Objects.requireNonNull(env.getImpetus()), + false); + return false; + } + + executorBlockState = executor.startEnergized(this.currentPos, executorBlockState, world); + this.reachedPositions.add(this.currentPos); + + // Do the execution! + boolean halt = false; + var ctrl = + executor.acceptControlFlow( + this.currentImage, env, this.enteredFrom, this.currentPos, executorBlockState, world); + + if (env.getImpetus() == null) + return false; // the impetus got removed during the cast and no longer exists in the world. + // stop casting + + if (ctrl instanceof ICircleComponent.ControlFlow.Stop) { + // acceptControlFlow should have already posted the error + halt = true; + } else if (ctrl instanceof ICircleComponent.ControlFlow.Continue cont) { + Pair found = null; + + for (var exit : cont.exits) { + var there = world.getBlockState(exit.getFirst()); + if (there.getBlock() instanceof ICircleComponent cc + && cc.canEnterFromDirection(exit.getSecond(), exit.getFirst(), there, world)) { + if (found != null) { + // oh no! + impetus.postDisplay( + Component.translatable( + "hexcasting.tooltip.circle.many_exits", + Component.literal(this.currentPos.toShortString()) + .withStyle(ChatFormatting.RED)), + new ItemStack(Items.COMPASS)); + ICircleComponent.sfx( + this.currentPos, + executorBlockState, + world, + Objects.requireNonNull(env.getImpetus()), + false); + halt = true; + break; + } else { + found = exit; + } + } + } + + if (found == null) { + // will never enter here if there were too many because found will have been set + ICircleComponent.sfx( + this.currentPos, + executorBlockState, + world, + Objects.requireNonNull(env.getImpetus()), + false); + impetus.postNoExits(this.currentPos); + halt = true; + } else { + // A single valid exit position has been found. + ICircleComponent.sfx( + this.currentPos, + executorBlockState, + world, + Objects.requireNonNull(env.getImpetus()), + true); + currentPos = found.getFirst(); + enteredFrom = found.getSecond(); + currentImage = + cont.update.withOverriddenUsedOps( + 0); // reset ops used after each slate finishes executing + } + } + + return !halt; + } + + /** + * How many ticks should pass between activations, given the number of blocks encountered so far. + */ + protected int getTickSpeed() { + return Math.max(2, 10 - (this.reachedPositions.size() - 1) / 3); + } + + public void endExecution(BlockEntityAbstractImpetus impetus) { + var world = (ServerLevel) impetus.getLevel(); + + if (world == null) return; // TODO: error here? + + for (var pos : this.reachedPositions) { + var there = world.getBlockState(pos); + if (there.getBlock() instanceof ICircleComponent cc) { + cc.endEnergized(pos, there, world); + } + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/ICircleComponent.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/ICircleComponent.java index 66e06b4d0d..f3deda4ded 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/ICircleComponent.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/ICircleComponent.java @@ -11,6 +11,9 @@ import at.petrak.hexcasting.common.lib.HexItems; import at.petrak.hexcasting.common.lib.HexSounds; import com.mojang.datafixers.util.Pair; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -25,146 +28,165 @@ import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Contract; -import java.util.EnumSet; -import java.util.List; -import java.util.UUID; - /** * Implement this on a block to make circles interact with it. - *

- * This is its own interface so you can have your blocks subclass something else, and to avoid enormous - * files. The mod doesn't check for the interface on anything but blocks. + * + *

This is its own interface so you can have your blocks subclass something else, and to avoid + * enormous files. The mod doesn't check for the interface on anything but blocks. */ public interface ICircleComponent { - /** - * The heart of the interface! Functionally modify the casting environment. - *

- * With the new update you can have the side effects happen inline. In fact, you have to have the side effects - * happen inline. - *

- * Also, return a list of directions that the control flow can exit this block in. - * The circle environment will mishap if not exactly 1 of the returned directions can be accepted from. - */ - ControlFlow acceptControlFlow(CastingImage imageIn, CircleCastEnv env, Direction enterDir, BlockPos pos, - BlockState bs, ServerLevel world); - - /** - * Can this component get transferred to from a block coming in from that direction, with the given normal? - */ - @Contract(pure = true) - boolean canEnterFromDirection(Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world); - - /** - * This determines the directions the control flow can exit from. It's called at the beginning of execution - * to see if the circle actually forms a loop. - *

- * For most blocks, this should be the same as returned from {@link ICircleComponent#acceptControlFlow} - * Things like directrices might return otherwise. Whatever is returned when controlling flow must be a subset of - * this set. - */ - @Contract(pure = true) - EnumSet possibleExitDirections(BlockPos pos, BlockState bs, Level world); - - /** - * Given the current position and a direction, return a pair of the new position after a step - * in that direction, along with the direction (this is a helper function for creating - * {@link ICircleComponent.ControlFlow}s. - */ - @Contract(pure = true) - default Pair exitPositionFromDirection(BlockPos pos, Direction dir) { - return Pair.of(pos.offset(dir.getStepX(), dir.getStepY(), dir.getStepZ()), dir); - } - - /** - * Start the {@link ICircleComponent} at the given position glowing. Returns the new state - * of the given block. - * // TODO: determine if this should just be in - * {@link ICircleComponent#acceptControlFlow(CastingImage, CircleCastEnv, Direction, BlockPos, BlockState, ServerLevel)}. - */ - BlockState startEnergized(BlockPos pos, BlockState bs, Level world); - - /** - * Returns whether the {@link ICircleComponent} at the given position is energized. - */ - boolean isEnergized(BlockPos pos, BlockState bs, Level world); - - /** - * End the {@link ICircleComponent} at the given position glowing. Returns the new state of - * the given block. - */ - BlockState endEnergized(BlockPos pos, BlockState bs, Level world); - - static void sfx(BlockPos pos, BlockState bs, Level world, BlockEntityAbstractImpetus impetus, boolean success) { - Vec3 vpos; - Vec3 vecOutDir; - FrozenPigment colorizer; - - UUID activator = Util.NIL_UUID; - if (impetus != null && impetus.getExecutionState() != null && impetus.getExecutionState().caster != null) - activator = impetus.getExecutionState().caster; - - if (impetus == null || impetus.getExecutionState() == null) - colorizer = new FrozenPigment(new ItemStack(HexItems.DYE_PIGMENTS.get(DyeColor.RED)), activator); - else - colorizer = impetus.getPigment(); - - if (bs.getBlock() instanceof BlockCircleComponent bcc) { - var outDir = bcc.normalDir(pos, bs, world); - var height = bcc.particleHeight(pos, bs, world); - vecOutDir = new Vec3(outDir.step()); - vpos = Vec3.atCenterOf(pos).add(vecOutDir.scale(height)); - } else { - // we probably are doing this because it's an error and we removed a block - vpos = Vec3.atCenterOf(pos); - vecOutDir = new Vec3(0, 0, 0); - } - - if (world instanceof ServerLevel serverLevel) { - var spray = new ParticleSpray(vpos, vecOutDir.scale(success ? 1.0 : 1.5), success ? 0.1 : 0.5, - Mth.PI / (success ? 4 : 2), success ? 30 : 100); - spray.sprayParticles(serverLevel, - success ? colorizer : new FrozenPigment(new ItemStack(HexItems.DYE_PIGMENTS.get(DyeColor.RED)), - activator)); - } - - var pitch = 1f; - var sound = HexSounds.SPELL_CIRCLE_FAIL; - if (success && impetus != null) { - sound = HexSounds.SPELL_CIRCLE_FIND_BLOCK; - - var state = impetus.getExecutionState(); - - // This is a good use of my time - var note = state.reachedPositions.size() - 1; - var semitone = impetus.semitoneFromScale(note); - pitch = (float) Math.pow(2.0, (semitone - 8) / 12d); - } - world.playSound(null, vpos.x, vpos.y, vpos.z, sound, SoundSource.BLOCKS, 1f, pitch); - } - - /** - * Helper function to "throw a mishap" - */ - default void fakeThrowMishap(BlockPos pos, BlockState bs, CastingImage image, CircleCastEnv env, Mishap mishap) { - Mishap.Context errorCtx = new Mishap.Context(null, - bs.getBlock().getName().append(" (").append(Component.literal(pos.toShortString())).append(")")); - var sideEffect = new OperatorSideEffect.DoMishap(mishap, errorCtx); - var vm = new CastingVM(image, env); - sideEffect.performEffect(vm); - } - - abstract sealed class ControlFlow { - public static final class Continue extends ControlFlow { - public final CastingImage update; - public final List> exits; - - public Continue(CastingImage update, List> exits) { - this.update = update; - this.exits = exits; - } - } - - public static final class Stop extends ControlFlow { - } - } + /** + * The heart of the interface! Functionally modify the casting environment. + * + *

With the new update you can have the side effects happen inline. In fact, you have to have + * the side effects happen inline. + * + *

Also, return a list of directions that the control flow can exit this block in. The circle + * environment will mishap if not exactly 1 of the returned directions can be accepted from. + */ + ControlFlow acceptControlFlow( + CastingImage imageIn, + CircleCastEnv env, + Direction enterDir, + BlockPos pos, + BlockState bs, + ServerLevel world); + + /** + * Can this component get transferred to from a block coming in from that direction, with the + * given normal? + */ + @Contract(pure = true) + boolean canEnterFromDirection(Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world); + + /** + * This determines the directions the control flow can exit from. It's called at the + * beginning of execution to see if the circle actually forms a loop. + * + *

For most blocks, this should be the same as returned from {@link + * ICircleComponent#acceptControlFlow} Things like directrices might return otherwise. Whatever is + * returned when controlling flow must be a subset of this set. + */ + @Contract(pure = true) + EnumSet possibleExitDirections(BlockPos pos, BlockState bs, Level world); + + /** + * Given the current position and a direction, return a pair of the new position after a step in + * that direction, along with the direction (this is a helper function for creating {@link + * ICircleComponent.ControlFlow}s. + */ + @Contract(pure = true) + default Pair exitPositionFromDirection(BlockPos pos, Direction dir) { + return Pair.of(pos.offset(dir.getStepX(), dir.getStepY(), dir.getStepZ()), dir); + } + + /** + * Start the {@link ICircleComponent} at the given position glowing. Returns the new state of the + * given block. // TODO: determine if this should just be in {@link + * ICircleComponent#acceptControlFlow(CastingImage, CircleCastEnv, Direction, BlockPos, + * BlockState, ServerLevel)}. + */ + BlockState startEnergized(BlockPos pos, BlockState bs, Level world); + + /** Returns whether the {@link ICircleComponent} at the given position is energized. */ + boolean isEnergized(BlockPos pos, BlockState bs, Level world); + + /** + * End the {@link ICircleComponent} at the given position glowing. Returns the new state of the + * given block. + */ + BlockState endEnergized(BlockPos pos, BlockState bs, Level world); + + static void sfx( + BlockPos pos, + BlockState bs, + Level world, + BlockEntityAbstractImpetus impetus, + boolean success) { + Vec3 vpos; + Vec3 vecOutDir; + FrozenPigment colorizer; + + UUID activator = Util.NIL_UUID; + if (impetus != null + && impetus.getExecutionState() != null + && impetus.getExecutionState().caster != null) + activator = impetus.getExecutionState().caster; + + if (impetus == null || impetus.getExecutionState() == null) + colorizer = + new FrozenPigment(new ItemStack(HexItems.DYE_PIGMENTS.get(DyeColor.RED)), activator); + else colorizer = impetus.getPigment(); + + if (bs.getBlock() instanceof BlockCircleComponent bcc) { + var outDir = bcc.normalDir(pos, bs, world); + var height = bcc.particleHeight(pos, bs, world); + vecOutDir = new Vec3(outDir.step()); + vpos = Vec3.atCenterOf(pos).add(vecOutDir.scale(height)); + } else { + // we probably are doing this because it's an error and we removed a block + vpos = Vec3.atCenterOf(pos); + vecOutDir = new Vec3(0, 0, 0); + } + + if (world instanceof ServerLevel serverLevel) { + var spray = + new ParticleSpray( + vpos, + vecOutDir.scale(success ? 1.0 : 1.5), + success ? 0.1 : 0.5, + Mth.PI / (success ? 4 : 2), + success ? 30 : 100); + spray.sprayParticles( + serverLevel, + success + ? colorizer + : new FrozenPigment( + new ItemStack(HexItems.DYE_PIGMENTS.get(DyeColor.RED)), activator)); + } + + var pitch = 1f; + var sound = HexSounds.SPELL_CIRCLE_FAIL; + if (success && impetus != null) { + sound = HexSounds.SPELL_CIRCLE_FIND_BLOCK; + + var state = impetus.getExecutionState(); + + // This is a good use of my time + var note = state.reachedPositions.size() - 1; + var semitone = impetus.semitoneFromScale(note); + pitch = (float) Math.pow(2.0, (semitone - 8) / 12d); + } + world.playSound(null, vpos.x, vpos.y, vpos.z, sound, SoundSource.BLOCKS, 1f, pitch); + } + + /** Helper function to "throw a mishap" */ + default void fakeThrowMishap( + BlockPos pos, BlockState bs, CastingImage image, CircleCastEnv env, Mishap mishap) { + Mishap.Context errorCtx = + new Mishap.Context( + null, + bs.getBlock() + .getName() + .append(" (") + .append(Component.literal(pos.toShortString())) + .append(")")); + var sideEffect = new OperatorSideEffect.DoMishap(mishap, errorCtx); + var vm = new CastingVM(image, env); + sideEffect.performEffect(vm); + } + + abstract sealed class ControlFlow { + public static final class Continue extends ControlFlow { + public final CastingImage update; + public final List> exits; + + public Continue(CastingImage update, List> exits) { + this.update = update; + this.exits = exits; + } + } + + public static final class Stop extends ControlFlow {} + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastResult.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastResult.kt index c0b4d4442d..8b516c85a7 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastResult.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/CastResult.kt @@ -9,15 +9,15 @@ import at.petrak.hexcasting.api.casting.iota.Iota /** * The result of doing something to a cast harness. * - * Contains the iota that was executed to produce this CastResult, - * the next thing to execute after this is finished, the modified state of the stack, - * and side effects, as well as display information for the client. + * Contains the iota that was executed to produce this CastResult, the next thing to execute after + * this is finished, the modified state of the stack, and side effects, as well as display + * information for the client. */ data class CastResult( - val cast: Iota, - val continuation: SpellContinuation, - val newData: CastingImage?, - val sideEffects: List, - val resolutionType: ResolvedPatternType, - val sound: EvalSound, + val cast: Iota, + val continuation: SpellContinuation, + val newData: CastingImage?, + val sideEffects: List, + val resolutionType: ResolvedPatternType, + val sound: EvalSound, ) 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..ae347569f3 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 @@ -1,5 +1,8 @@ package at.petrak.hexcasting.api.casting.eval; +import static at.petrak.hexcasting.api.HexAPI.modLoc; +import static at.petrak.hexcasting.api.casting.eval.CastingEnvironmentComponent.*; + import at.petrak.hexcasting.api.casting.ParticleSpray; import at.petrak.hexcasting.api.casting.PatternShapeMatch; import at.petrak.hexcasting.api.casting.eval.vm.CastingImage; @@ -10,6 +13,13 @@ import at.petrak.hexcasting.api.mod.HexConfig; import at.petrak.hexcasting.api.pigment.FrozenPigment; import at.petrak.hexcasting.api.utils.HexUtils; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Predicate; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; @@ -25,463 +35,423 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import static at.petrak.hexcasting.api.HexAPI.modLoc; -import static at.petrak.hexcasting.api.casting.eval.CastingEnvironmentComponent.*; - /** * Environment within which hexes are cast. - *

- * Stuff like "the player with a staff," "the player with a trinket," "spell circles," + * + *

Stuff like "the player with a staff," "the player with a trinket," "spell circles," */ public abstract class CastingEnvironment { - /** - * Stores all listeners that should be notified whenever a CastingEnvironment is initialised. - */ - private static final List> createEventListeners = new ArrayList<>(); - - /** - * Add a listener that will be called whenever a new CastingEnvironment is created. - */ - public static void addCreateEventListener(BiConsumer listener) { - createEventListeners.add(listener); - } - - /** - * Add a listener that will be called whenever a new CastingEnvironment is created (legacy). - * @deprecated replaced by {@link #addCreateEventListener(BiConsumer)} - */ - @Deprecated(since = "0.11.0-pre-660") - public static void addCreateEventListener(Consumer listener) { - createEventListeners.add((env, data) -> {listener.accept(env);}); - } - - private boolean createEventTriggered = false; - - public final void triggerCreateEvent(CompoundTag userData) { - if (!createEventTriggered) { - for (var listener : createEventListeners) - listener.accept(this, userData); - createEventTriggered = true; - } - } - - - protected final ServerLevel world; - - protected Map, @NotNull CastingEnvironmentComponent> componentMap = new HashMap<>(); - private final List postExecutions = new ArrayList<>(); - - private final List postCasts = new ArrayList<>(); - private final List preMediaExtract = new ArrayList<>(); - private final List postMediaExtract = new ArrayList<>(); - - private final List isVecInRanges = new ArrayList<>(); - private final List hasEditPermissionsAts = new ArrayList<>(); - - protected CastingEnvironment(ServerLevel world) { - this.world = world; - } - - public final ServerLevel getWorld() { - return this.world; - } - - public int maxOpCount() { - return HexConfig.server().maxOpCount(); - } - - /** - * Get the caster. Might be null! - *

- * 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") - @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 - public abstract LivingEntity getCastingEntity(); - - /** - * Get an interface used to do mishaps - */ - public abstract MishapEnvironment getMishapEnvironment(); - - public void addExtension(@NotNull T extension) { - componentMap.put(extension.getKey(), extension); - if (extension instanceof PostExecution postExecution) - postExecutions.add(postExecution); - if (extension instanceof PostCast postCast) - postCasts.add(postCast); - if (extension instanceof ExtractMedia extractMedia) - if (extension instanceof ExtractMedia.Pre pre) { - preMediaExtract.add(pre); - } else if (extension instanceof ExtractMedia.Post post) { - postMediaExtract.add(post); - } - if (extension instanceof IsVecInRange isVecInRange) - isVecInRanges.add(isVecInRange); - if (extension instanceof HasEditPermissionsAt hasEditPermissionsAt) - hasEditPermissionsAts.add(hasEditPermissionsAt); - } - - public void removeExtension(@NotNull CastingEnvironmentComponent.Key key) { - var extension = componentMap.remove(key); - if (extension == null) - return; - - if (extension instanceof PostExecution postExecution) - postExecutions.remove(postExecution); - if (extension instanceof PostCast postCast) - postCasts.remove(postCast); - if (extension instanceof ExtractMedia extractMedia) - if (extension instanceof ExtractMedia.Pre pre) { - preMediaExtract.remove(pre); - } else if (extension instanceof ExtractMedia.Post post) { - postMediaExtract.remove(post); - } - if (extension instanceof IsVecInRange isVecInRange) - isVecInRanges.remove(isVecInRange); - if (extension instanceof HasEditPermissionsAt hasEditPermissionsAt) - hasEditPermissionsAts.remove(hasEditPermissionsAt); - } - - @Nullable - @SuppressWarnings("unchecked") - public T getExtension(@NotNull CastingEnvironmentComponent.Key key) { - return (T) componentMap.get(key); - } - - /** - * If something about this ARE itself is invalid, mishap. - *

- * This is used for stuff like requiring enlightenment and pattern denylists - */ - public void precheckAction(PatternShapeMatch match) throws Mishap { - // TODO: this doesn't let you select special handlers. - // Might be worth making a "no casting" tag on each thing - ResourceLocation key = actionKey(match); - - if (!HexConfig.server().isActionAllowed(key)) { - throw new MishapDisallowedSpell(); - } - } - - @Nullable - protected ResourceLocation actionKey(PatternShapeMatch match) { - ResourceLocation key; - if (match instanceof PatternShapeMatch.Normal normal) { - key = normal.key.location(); - } else if (match instanceof PatternShapeMatch.PerWorld perWorld) { - key = perWorld.key.location(); - } else if (match instanceof PatternShapeMatch.Special special) { - key = special.key.location(); - } else { - key = null; - } - return key; - } - - /** - * Do whatever you like after a pattern is executed. - */ - public void postExecution(CastResult result) { - for (var postExecutionComponent : postExecutions) - postExecutionComponent.onPostExecution(result); - } - - /** - * Do things after the whole cast is finished (i.e. every pattern to be executed has been executed). - */ - public void postCast(CastingImage image) { - for (var postCastComponent : postCasts) - postCastComponent.onPostCast(image); - } - - public abstract Vec3 mishapSprayPos(); - - /** - * Return whether this env can cast great spells. - */ - public boolean isEnlightened() { - var adv = this.world.getServer().getAdvancements().getAdvancement(modLoc("enlightenment")); - if (adv == null) - return false; - - var caster = this.getCastingEntity(); - if (caster instanceof ServerPlayer player) - return player.getAdvancements().getOrStartProgress(adv).isDone(); - - return false; - } - - /** - * Attempt to extract the given amount of media. Returns the amount of media left in the cost. - *

- * 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) { - for (var extractMediaComponent : preMediaExtract) - cost = extractMediaComponent.onExtractMedia(cost); - cost = extractMediaEnvironment(cost); - for (var extractMediaComponent : postMediaExtract) - cost = extractMediaComponent.onExtractMedia(cost); - return cost; - } - - /** - * Attempt to extract the given amount of media. Returns the amount of media left in the 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); - - /** - * Get if the vec is close enough, to the player or sentinel ... - *

- * Doesn't take into account being out of the world. - */ - public boolean isVecInRange(Vec3 vec) { - boolean isInRange = isVecInRangeEnvironment(vec); - for (var isVecInRangeComponent : isVecInRanges) - isInRange = isVecInRangeComponent.onIsVecInRange(vec, isInRange); - return isInRange; - } - - /** - * Get if the vec is close enough, to the player or sentinel ... - *

- * Doesn't take into account being out of the world. - */ - protected abstract boolean isVecInRangeEnvironment(Vec3 vec); - - /** - * Return whether the caster can edit blocks at the given permission (i.e. not adventure mode, etc.) - */ - public boolean hasEditPermissionsAt(BlockPos pos) { - boolean hasEditPermissionsAt = hasEditPermissionsAtEnvironment(pos); - for (var hasEditPermissionsAtComponent : hasEditPermissionsAts) - hasEditPermissionsAt = hasEditPermissionsAtComponent.onHasEditPermissionsAt(pos, hasEditPermissionsAt); - return hasEditPermissionsAt; - } - - /** - * Return whether the caster can edit blocks at the given permission (i.e. not adventure mode, etc.) - */ - protected abstract boolean hasEditPermissionsAtEnvironment(BlockPos pos); - - public final boolean isVecInWorld(Vec3 vec) { - return this.world.isInWorldBounds(BlockPos.containing(vec)) - && this.world.getWorldBorder().isWithinBounds(vec.x, vec.z, 0.5); - } - - public final boolean isVecInAmbit(Vec3 vec) { - return this.isVecInRange(vec) && this.isVecInWorld(vec); - } - - public final boolean isEntityInRange(Entity e) { - return (e instanceof Player && HexConfig.server().trueNameHasAmbit()) || (this.isVecInWorld(e.position()) && this.isVecInRange(e.position())); - } - - /** - * Convenience function to throw if the vec is out of the caster's range or the world - */ - public final void assertVecInRange(Vec3 vec) throws MishapBadLocation { - this.assertVecInWorld(vec); - if (!this.isVecInRange(vec)) { - throw new MishapBadLocation(vec, "too_far"); - } - } - - public final void assertPosInRange(BlockPos vec) throws MishapBadLocation { - this.assertVecInRange(new Vec3(vec.getX(), vec.getY(), vec.getZ())); - } - - public final void assertPosInRangeForEditing(BlockPos vec) throws MishapBadLocation { - this.assertVecInRange(new Vec3(vec.getX(), vec.getY(), vec.getZ())); - if (!this.canEditBlockAt(vec)) - throw new MishapBadLocation(Vec3.atCenterOf(vec), "forbidden"); - } - - public final boolean canEditBlockAt(BlockPos vec) { - return this.isVecInRange(Vec3.atCenterOf(vec)) && this.hasEditPermissionsAt(vec); - } - - /** - * Convenience function to throw if the entity is out of the caster's range or the world - */ - public final void assertEntityInRange(Entity e) throws MishapEntityTooFarAway { - if (e instanceof ServerPlayer && HexConfig.server().trueNameHasAmbit()) { - return; - } - if (!this.isVecInWorld(e.position())) { - throw new MishapEntityTooFarAway(e); - } - if (!this.isVecInRange(e.position())) { - throw new MishapEntityTooFarAway(e); - } - } - - /** - * Convenience function to throw if the vec is out of the world (for GTP) - */ - public final void assertVecInWorld(Vec3 vec) throws MishapBadLocation { - if (!this.isVecInWorld(vec)) { - throw new MishapBadLocation(vec, "out_of_world"); - } - } - - public abstract InteractionHand getCastingHand(); - - public InteractionHand getOtherHand() { - return HexUtils.otherHand(this.getCastingHand()); - } - - /** - * Get all the item stacks this env can use. - */ - protected abstract List getUsableStacks(StackDiscoveryMode mode); - - /** - * Get the primary/secondary item stacks this env can use (i.e. main hand and offhand for the player). - */ - protected abstract List getPrimaryStacks(); - - /** - * Return the slot from which to take blocks and items. - */ - @Nullable - public ItemStack queryForMatchingStack(Predicate stackOk) { - var stacks = this.getUsableStacks(StackDiscoveryMode.QUERY); - for (ItemStack stack : stacks) { - if (stackOk.test(stack)) { - return stack; - } - } - - return null; - } - - public record HeldItemInfo(ItemStack stack, @Nullable InteractionHand hand) { - public ItemStack component1() { - return stack; - } - - public @Nullable InteractionHand component2() { - return hand; - } - } - - /** - * Return the slot from which to take blocks and items. - */ - // TODO winfy: resolve the null here - public @Nullable HeldItemInfo getHeldItemToOperateOn(Predicate stackOk) { - var stacks = this.getPrimaryStacks(); - for (HeldItemInfo stack : stacks) { - if (stackOk.test(stack.stack)) { - return stack; - } - } - - return null; - } - - /** - * Whether to provide infinite items. - */ - protected boolean isCreativeMode() { - return false; - } - - /** - * Attempt to withdraw some number of items from stacks available. - *

- * Return whether it was successful. - */ - public boolean withdrawItem(Predicate stackOk, int count, boolean actuallyRemove) { - if (this.isCreativeMode()) { - return true; - } - - var stacks = this.getUsableStacks(StackDiscoveryMode.EXTRACTION); - - var presentCount = 0; - var matches = new ArrayList(); - for (ItemStack stack : stacks) { - if (stackOk.test(stack)) { - presentCount += stack.getCount(); - matches.add(stack); - - if (presentCount >= count) - break; - } - } - if (presentCount < count) { - return false; - } - - if (!actuallyRemove) { - return true; - } // Otherwise do the removal - - var remaining = count; - for (ItemStack match : matches) { - var toWithdraw = Math.min(match.getCount(), remaining); - match.shrink(toWithdraw); - - remaining -= toWithdraw; - if (remaining <= 0) { - return true; - } - } - - throw new IllegalStateException("unreachable"); - } - - /** - * 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); - - /** - * The order/mode stacks should be discovered in - */ - protected enum StackDiscoveryMode { - /** - * When finding items to pick (hotbar) - */ - QUERY, - /** - * When extracting things - */ - EXTRACTION, - } - - public abstract FrozenPigment getPigment(); - - public abstract @Nullable FrozenPigment setPigment(@Nullable FrozenPigment pigment); - - public abstract void produceParticles(ParticleSpray particles, FrozenPigment colorizer); - - public abstract void printMessage(Component message); + /** Stores all listeners that should be notified whenever a CastingEnvironment is initialised. */ + private static final List> createEventListeners = + new ArrayList<>(); + + /** Add a listener that will be called whenever a new CastingEnvironment is created. */ + public static void addCreateEventListener(BiConsumer listener) { + createEventListeners.add(listener); + } + + /** + * Add a listener that will be called whenever a new CastingEnvironment is created (legacy). + * + * @deprecated replaced by {@link #addCreateEventListener(BiConsumer)} + */ + @Deprecated(since = "0.11.0-pre-660") + public static void addCreateEventListener(Consumer listener) { + createEventListeners.add( + (env, data) -> { + listener.accept(env); + }); + } + + private boolean createEventTriggered = false; + + public final void triggerCreateEvent(CompoundTag userData) { + if (!createEventTriggered) { + for (var listener : createEventListeners) listener.accept(this, userData); + createEventTriggered = true; + } + } + + protected final ServerLevel world; + + protected Map, @NotNull CastingEnvironmentComponent> + componentMap = new HashMap<>(); + private final List postExecutions = new ArrayList<>(); + + private final List postCasts = new ArrayList<>(); + private final List preMediaExtract = new ArrayList<>(); + private final List postMediaExtract = new ArrayList<>(); + + private final List isVecInRanges = new ArrayList<>(); + private final List hasEditPermissionsAts = new ArrayList<>(); + + protected CastingEnvironment(ServerLevel world) { + this.world = world; + } + + public final ServerLevel getWorld() { + return this.world; + } + + public int maxOpCount() { + return HexConfig.server().maxOpCount(); + } + + /** + * Get the caster. Might be null! + * + *

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") + @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 public abstract LivingEntity getCastingEntity(); + + /** Get an interface used to do mishaps */ + public abstract MishapEnvironment getMishapEnvironment(); + + public void addExtension(@NotNull T extension) { + componentMap.put(extension.getKey(), extension); + if (extension instanceof PostExecution postExecution) postExecutions.add(postExecution); + if (extension instanceof PostCast postCast) postCasts.add(postCast); + if (extension instanceof ExtractMedia extractMedia) + if (extension instanceof ExtractMedia.Pre pre) { + preMediaExtract.add(pre); + } else if (extension instanceof ExtractMedia.Post post) { + postMediaExtract.add(post); + } + if (extension instanceof IsVecInRange isVecInRange) isVecInRanges.add(isVecInRange); + if (extension instanceof HasEditPermissionsAt hasEditPermissionsAt) + hasEditPermissionsAts.add(hasEditPermissionsAt); + } + + public void removeExtension(@NotNull CastingEnvironmentComponent.Key key) { + var extension = componentMap.remove(key); + if (extension == null) return; + + if (extension instanceof PostExecution postExecution) postExecutions.remove(postExecution); + if (extension instanceof PostCast postCast) postCasts.remove(postCast); + if (extension instanceof ExtractMedia extractMedia) + if (extension instanceof ExtractMedia.Pre pre) { + preMediaExtract.remove(pre); + } else if (extension instanceof ExtractMedia.Post post) { + postMediaExtract.remove(post); + } + if (extension instanceof IsVecInRange isVecInRange) isVecInRanges.remove(isVecInRange); + if (extension instanceof HasEditPermissionsAt hasEditPermissionsAt) + hasEditPermissionsAts.remove(hasEditPermissionsAt); + } + + @Nullable @SuppressWarnings("unchecked") + public T getExtension( + @NotNull CastingEnvironmentComponent.Key key) { + return (T) componentMap.get(key); + } + + /** + * If something about this ARE itself is invalid, mishap. + * + *

This is used for stuff like requiring enlightenment and pattern denylists + */ + public void precheckAction(PatternShapeMatch match) throws Mishap { + // TODO: this doesn't let you select special handlers. + // Might be worth making a "no casting" tag on each thing + ResourceLocation key = actionKey(match); + + if (!HexConfig.server().isActionAllowed(key)) { + throw new MishapDisallowedSpell(); + } + } + + @Nullable protected ResourceLocation actionKey(PatternShapeMatch match) { + ResourceLocation key; + if (match instanceof PatternShapeMatch.Normal normal) { + key = normal.key.location(); + } else if (match instanceof PatternShapeMatch.PerWorld perWorld) { + key = perWorld.key.location(); + } else if (match instanceof PatternShapeMatch.Special special) { + key = special.key.location(); + } else { + key = null; + } + return key; + } + + /** Do whatever you like after a pattern is executed. */ + public void postExecution(CastResult result) { + for (var postExecutionComponent : postExecutions) + postExecutionComponent.onPostExecution(result); + } + + /** + * Do things after the whole cast is finished (i.e. every pattern to be executed has been + * executed). + */ + public void postCast(CastingImage image) { + for (var postCastComponent : postCasts) postCastComponent.onPostCast(image); + } + + public abstract Vec3 mishapSprayPos(); + + /** Return whether this env can cast great spells. */ + public boolean isEnlightened() { + var adv = this.world.getServer().getAdvancements().getAdvancement(modLoc("enlightenment")); + if (adv == null) return false; + + var caster = this.getCastingEntity(); + if (caster instanceof ServerPlayer player) + return player.getAdvancements().getOrStartProgress(adv).isDone(); + + return false; + } + + /** + * Attempt to extract the given amount of media. Returns the amount of media left in the cost. + * + *

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) { + for (var extractMediaComponent : preMediaExtract) + cost = extractMediaComponent.onExtractMedia(cost); + cost = extractMediaEnvironment(cost); + for (var extractMediaComponent : postMediaExtract) + cost = extractMediaComponent.onExtractMedia(cost); + return cost; + } + + /** + * Attempt to extract the given amount of media. Returns the amount of media left in the 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); + + /** + * Get if the vec is close enough, to the player or sentinel ... + * + *

Doesn't take into account being out of the world. + */ + public boolean isVecInRange(Vec3 vec) { + boolean isInRange = isVecInRangeEnvironment(vec); + for (var isVecInRangeComponent : isVecInRanges) + isInRange = isVecInRangeComponent.onIsVecInRange(vec, isInRange); + return isInRange; + } + + /** + * Get if the vec is close enough, to the player or sentinel ... + * + *

Doesn't take into account being out of the world. + */ + protected abstract boolean isVecInRangeEnvironment(Vec3 vec); + + /** + * Return whether the caster can edit blocks at the given permission (i.e. not adventure mode, + * etc.) + */ + public boolean hasEditPermissionsAt(BlockPos pos) { + boolean hasEditPermissionsAt = hasEditPermissionsAtEnvironment(pos); + for (var hasEditPermissionsAtComponent : hasEditPermissionsAts) + hasEditPermissionsAt = + hasEditPermissionsAtComponent.onHasEditPermissionsAt(pos, hasEditPermissionsAt); + return hasEditPermissionsAt; + } + + /** + * Return whether the caster can edit blocks at the given permission (i.e. not adventure mode, + * etc.) + */ + protected abstract boolean hasEditPermissionsAtEnvironment(BlockPos pos); + + public final boolean isVecInWorld(Vec3 vec) { + return this.world.isInWorldBounds(BlockPos.containing(vec)) + && this.world.getWorldBorder().isWithinBounds(vec.x, vec.z, 0.5); + } + + public final boolean isVecInAmbit(Vec3 vec) { + return this.isVecInRange(vec) && this.isVecInWorld(vec); + } + + public final boolean isEntityInRange(Entity e) { + return (e instanceof Player && HexConfig.server().trueNameHasAmbit()) + || (this.isVecInWorld(e.position()) && this.isVecInRange(e.position())); + } + + /** Convenience function to throw if the vec is out of the caster's range or the world */ + public final void assertVecInRange(Vec3 vec) throws MishapBadLocation { + this.assertVecInWorld(vec); + if (!this.isVecInRange(vec)) { + throw new MishapBadLocation(vec, "too_far"); + } + } + + public final void assertPosInRange(BlockPos vec) throws MishapBadLocation { + this.assertVecInRange(new Vec3(vec.getX(), vec.getY(), vec.getZ())); + } + + public final void assertPosInRangeForEditing(BlockPos vec) throws MishapBadLocation { + this.assertVecInRange(new Vec3(vec.getX(), vec.getY(), vec.getZ())); + if (!this.canEditBlockAt(vec)) throw new MishapBadLocation(Vec3.atCenterOf(vec), "forbidden"); + } + + public final boolean canEditBlockAt(BlockPos vec) { + return this.isVecInRange(Vec3.atCenterOf(vec)) && this.hasEditPermissionsAt(vec); + } + + /** Convenience function to throw if the entity is out of the caster's range or the world */ + public final void assertEntityInRange(Entity e) throws MishapEntityTooFarAway { + if (e instanceof ServerPlayer && HexConfig.server().trueNameHasAmbit()) { + return; + } + if (!this.isVecInWorld(e.position())) { + throw new MishapEntityTooFarAway(e); + } + if (!this.isVecInRange(e.position())) { + throw new MishapEntityTooFarAway(e); + } + } + + /** Convenience function to throw if the vec is out of the world (for GTP) */ + public final void assertVecInWorld(Vec3 vec) throws MishapBadLocation { + if (!this.isVecInWorld(vec)) { + throw new MishapBadLocation(vec, "out_of_world"); + } + } + + public abstract InteractionHand getCastingHand(); + + public InteractionHand getOtherHand() { + return HexUtils.otherHand(this.getCastingHand()); + } + + /** Get all the item stacks this env can use. */ + protected abstract List getUsableStacks(StackDiscoveryMode mode); + + /** + * Get the primary/secondary item stacks this env can use (i.e. main hand and offhand for the + * player). + */ + protected abstract List getPrimaryStacks(); + + /** Return the slot from which to take blocks and items. */ + @Nullable public ItemStack queryForMatchingStack(Predicate stackOk) { + var stacks = this.getUsableStacks(StackDiscoveryMode.QUERY); + for (ItemStack stack : stacks) { + if (stackOk.test(stack)) { + return stack; + } + } + + return null; + } + + public record HeldItemInfo(ItemStack stack, @Nullable InteractionHand hand) { + public ItemStack component1() { + return stack; + } + + public @Nullable InteractionHand component2() { + return hand; + } + } + + /** Return the slot from which to take blocks and items. */ + // TODO winfy: resolve the null here + public @Nullable HeldItemInfo getHeldItemToOperateOn(Predicate stackOk) { + var stacks = this.getPrimaryStacks(); + for (HeldItemInfo stack : stacks) { + if (stackOk.test(stack.stack)) { + return stack; + } + } + + return null; + } + + /** Whether to provide infinite items. */ + protected boolean isCreativeMode() { + return false; + } + + /** + * Attempt to withdraw some number of items from stacks available. + * + *

Return whether it was successful. + */ + public boolean withdrawItem(Predicate stackOk, int count, boolean actuallyRemove) { + if (this.isCreativeMode()) { + return true; + } + + var stacks = this.getUsableStacks(StackDiscoveryMode.EXTRACTION); + + var presentCount = 0; + var matches = new ArrayList(); + for (ItemStack stack : stacks) { + if (stackOk.test(stack)) { + presentCount += stack.getCount(); + matches.add(stack); + + if (presentCount >= count) break; + } + } + if (presentCount < count) { + return false; + } + + if (!actuallyRemove) { + return true; + } // Otherwise do the removal + + var remaining = count; + for (ItemStack match : matches) { + var toWithdraw = Math.min(match.getCount(), remaining); + match.shrink(toWithdraw); + + remaining -= toWithdraw; + if (remaining <= 0) { + return true; + } + } + + throw new IllegalStateException("unreachable"); + } + + /** + * 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); + + /** The order/mode stacks should be discovered in */ + protected enum StackDiscoveryMode { + /** When finding items to pick (hotbar) */ + QUERY, + /** When extracting things */ + EXTRACTION, + } + + public abstract FrozenPigment getPigment(); + + public abstract @Nullable FrozenPigment setPigment(@Nullable FrozenPigment pigment); + + public abstract void produceParticles(ParticleSpray particles, FrozenPigment colorizer); + + public abstract void printMessage(Component message); } 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..f39b7374a0 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 @@ -5,55 +5,51 @@ import net.minecraft.world.phys.Vec3; public interface CastingEnvironmentComponent { - Key getKey(); - - interface Key {} - - interface PostExecution extends CastingEnvironmentComponent { - /** - * Do whatever you like after a pattern is executed. - */ - void onPostExecution(CastResult result); - } - - interface PostCast extends CastingEnvironmentComponent { - /** - * Do things after the whole cast is finished (i.e. every pattern to be executed has been executed). - */ - void onPostCast(CastingImage image); - } - - interface ExtractMedia extends CastingEnvironmentComponent { - /** - * Receives the cost that is being extracted, should return the - * remaining cost after deducting whatever cost source this component - * is responsible for (should be >= 0) - */ - long onExtractMedia(long cost); - - /** - * ExtractMedia component that extracts media BEFORE the call to {@link CastingEnvironment#extractMediaEnvironment(long)} - */ - interface Pre extends ExtractMedia {} - - /** - * ExtractMedia component that extracts media AFTER the call to {@link CastingEnvironment#extractMediaEnvironment(long)} - * if the input is <= 0 you should also probably return 0 (since media cost was already paid off) - */ - interface Post extends ExtractMedia {} - } - - interface IsVecInRange extends CastingEnvironmentComponent { - /** - * Receives the vec, and the current return value, and returns the new return value. - */ - boolean onIsVecInRange(Vec3 vec, boolean current); - } - - interface HasEditPermissionsAt extends CastingEnvironmentComponent { - /** - * Receives the vec, and the current return value, and returns the new return value. - */ - boolean onHasEditPermissionsAt(BlockPos pos, boolean current); - } + Key getKey(); + + interface Key {} + + interface PostExecution extends CastingEnvironmentComponent { + /** Do whatever you like after a pattern is executed. */ + void onPostExecution(CastResult result); + } + + interface PostCast extends CastingEnvironmentComponent { + /** + * Do things after the whole cast is finished (i.e. every pattern to be executed has been + * executed). + */ + void onPostCast(CastingImage image); + } + + interface ExtractMedia extends CastingEnvironmentComponent { + /** + * Receives the cost that is being extracted, should return the remaining cost after deducting + * whatever cost source this component is responsible for (should be >= 0) + */ + long onExtractMedia(long cost); + + /** + * ExtractMedia component that extracts media BEFORE the call to {@link + * CastingEnvironment#extractMediaEnvironment(long)} + */ + interface Pre extends ExtractMedia {} + + /** + * ExtractMedia component that extracts media AFTER the call to {@link + * CastingEnvironment#extractMediaEnvironment(long)} if the input is <= 0 you should also + * probably return 0 (since media cost was already paid off) + */ + interface Post extends ExtractMedia {} + } + + interface IsVecInRange extends CastingEnvironmentComponent { + /** Receives the vec, and the current return value, and returns the new return value. */ + boolean onIsVecInRange(Vec3 vec, boolean current); + } + + interface HasEditPermissionsAt extends CastingEnvironmentComponent { + /** Receives the vec, and the current return value, and returns the new return value. */ + boolean onHasEditPermissionsAt(BlockPos pos, boolean current); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ExecutionClientView.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ExecutionClientView.kt index 3fcf1bf766..46e773a46f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ExecutionClientView.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ExecutionClientView.kt @@ -2,16 +2,13 @@ package at.petrak.hexcasting.api.casting.eval import net.minecraft.nbt.CompoundTag -/** - * Information sent back to the client - */ +/** Information sent back to the client */ data class ExecutionClientView( - val isStackClear: Boolean, - val resolutionType: ResolvedPatternType, + val isStackClear: Boolean, + val resolutionType: ResolvedPatternType, - // These must be tags so the wrapping of the text can happen on the client - // otherwise we don't know when to stop rendering - val stackDescs: List, - val ravenmind: CompoundTag?, + // These must be tags so the wrapping of the text can happen on the client + // otherwise we don't know when to stop rendering + val stackDescs: List, + val ravenmind: CompoundTag?, ) - diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/MishapEnvironment.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/MishapEnvironment.java index 5469914454..4b1125b709 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/MishapEnvironment.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/MishapEnvironment.java @@ -9,42 +9,43 @@ /** * Kinda like {@link CastingEnvironment} but for executing mishaps. - *

- * To avoid horrible O(mn) scope problems we offer a set of stock bad effects. - * The player is exposed nullably if you like though. + * + *

To avoid horrible O(mn) scope problems we offer a set of stock bad effects. The player is + * exposed nullably if you like though. */ public abstract class MishapEnvironment { - @Nullable - protected final ServerPlayer caster; - protected final ServerLevel world; + @Nullable protected final ServerPlayer caster; + protected final ServerLevel world; - protected MishapEnvironment(ServerLevel world, @Nullable ServerPlayer caster) { - this.caster = caster; - this.world = world; - } + protected MishapEnvironment(ServerLevel world, @Nullable ServerPlayer caster) { + this.caster = caster; + this.world = world; + } - public abstract void yeetHeldItemsTowards(Vec3 targetPos); + public abstract void yeetHeldItemsTowards(Vec3 targetPos); - public abstract void dropHeldItems(); + public abstract void dropHeldItems(); - public abstract void drown(); + public abstract void drown(); - public abstract void damage(float healthProportion); + public abstract void damage(float healthProportion); - public abstract void removeXp(int amount); + public abstract void removeXp(int amount); - public abstract void blind(int ticks); + public abstract void blind(int ticks); - protected void yeetItem(ItemStack stack, Vec3 srcPos, Vec3 delta) { - var entity = new ItemEntity( - this.world, - srcPos.x, srcPos.y, srcPos.z, - stack, - delta.x + (Math.random() - 0.5) * 0.1, - delta.y + (Math.random() - 0.5) * 0.1, - delta.z + (Math.random() - 0.5) * 0.1 - ); - entity.setPickUpDelay(40); - this.world.addWithUUID(entity); - } + protected void yeetItem(ItemStack stack, Vec3 srcPos, Vec3 delta) { + var entity = + new ItemEntity( + this.world, + srcPos.x, + srcPos.y, + srcPos.z, + stack, + delta.x + (Math.random() - 0.5) * 0.1, + delta.y + (Math.random() - 0.5) * 0.1, + delta.z + (Math.random() - 0.5) * 0.1); + entity.setPickUpDelay(40); + this.world.addWithUUID(entity); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/OperationResult.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/OperationResult.kt index 3a0f066fbc..2a5673c0a1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/OperationResult.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/OperationResult.kt @@ -5,12 +5,10 @@ import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect import at.petrak.hexcasting.api.casting.eval.vm.CastingImage import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation -/** - * What happens when an operator is through? - */ +/** What happens when an operator is through? */ data class OperationResult( - val newImage: CastingImage, - val sideEffects: List, - val newContinuation: SpellContinuation, - val sound: EvalSound, + val newImage: CastingImage, + val sideEffects: List, + val newContinuation: SpellContinuation, + val sound: EvalSound, ) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ResolvedPattern.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ResolvedPattern.kt index 6108850727..035977e253 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ResolvedPattern.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ResolvedPattern.kt @@ -3,25 +3,28 @@ package at.petrak.hexcasting.api.casting.eval import at.petrak.hexcasting.api.casting.math.HexCoord import at.petrak.hexcasting.api.casting.math.HexPattern import at.petrak.hexcasting.api.utils.NBTBuilder -import net.minecraft.nbt.CompoundTag import java.util.* +import net.minecraft.nbt.CompoundTag +data class ResolvedPattern( + val pattern: HexPattern, + val origin: HexCoord, + var type: ResolvedPatternType +) { + fun serializeToNBT() = NBTBuilder { + "Pattern" %= pattern.serializeToNBT() + "OriginQ" %= origin.q + "OriginR" %= origin.r + "Valid" %= type.name.lowercase(Locale.ROOT) + } -data class ResolvedPattern(val pattern: HexPattern, val origin: HexCoord, var type: ResolvedPatternType) { - fun serializeToNBT() = NBTBuilder { - "Pattern" %= pattern.serializeToNBT() - "OriginQ" %= origin.q - "OriginR" %= origin.r - "Valid" %= type.name.lowercase(Locale.ROOT) - } - - companion object { - @JvmStatic - fun fromNBT(tag: CompoundTag): ResolvedPattern { - val pattern = HexPattern.fromNBT(tag.getCompound("Pattern")) - val origin = HexCoord(tag.getInt("OriginQ"), tag.getInt("OriginR")) - val valid = ResolvedPatternType.fromString(tag.getString("Valid")) - return ResolvedPattern(pattern, origin, valid) - } - } + companion object { + @JvmStatic + fun fromNBT(tag: CompoundTag): ResolvedPattern { + val pattern = HexPattern.fromNBT(tag.getCompound("Pattern")) + val origin = HexCoord(tag.getInt("OriginQ"), tag.getInt("OriginR")) + val valid = ResolvedPatternType.fromString(tag.getString("Valid")) + return ResolvedPattern(pattern, origin, valid) + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ResolvedPatternType.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ResolvedPatternType.kt index 8e332bb802..433332f7b0 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ResolvedPatternType.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/ResolvedPatternType.kt @@ -3,17 +3,17 @@ package at.petrak.hexcasting.api.casting.eval import at.petrak.hexcasting.api.utils.getSafe enum class ResolvedPatternType(val color: Int, val fadeColor: Int, val success: Boolean) { - UNRESOLVED(0x7f7f7f, 0xcccccc, false), - EVALUATED(0x7385de, 0xfecbe6, true), - ESCAPED(0xddcc73, 0xfffae5, true), - UNDONE(0xb26b6b, 0xcca88e, true), // TODO: Pick better colours - ERRORED(0xde6262, 0xffc7a0, false), - INVALID(0xb26b6b, 0xcca88e, false); + UNRESOLVED(0x7f7f7f, 0xcccccc, false), + EVALUATED(0x7385de, 0xfecbe6, true), + ESCAPED(0xddcc73, 0xfffae5, true), + UNDONE(0xb26b6b, 0xcca88e, true), // TODO: Pick better colours + ERRORED(0xde6262, 0xffc7a0, false), + INVALID(0xb26b6b, 0xcca88e, false); - companion object { - @JvmStatic - fun fromString(key: String): ResolvedPatternType { - return values().getSafe(key) - } - } + companion object { + @JvmStatic + fun fromString(key: String): ResolvedPatternType { + return values().getSafe(key) + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java index f87fcfdfae..75d6a8bd10 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpecialPatterns.java @@ -4,9 +4,9 @@ import at.petrak.hexcasting.api.casting.math.HexPattern; public final class SpecialPatterns { - public static final HexPattern INTROSPECTION = HexPattern.fromAngles("qqq", HexDir.WEST); - public static final HexPattern RETROSPECTION = HexPattern.fromAngles("eee", HexDir.EAST); - public static final HexPattern CONSIDERATION = HexPattern.fromAngles("qqqaw", HexDir.WEST); + public static final HexPattern INTROSPECTION = HexPattern.fromAngles("qqq", HexDir.WEST); + public static final HexPattern RETROSPECTION = HexPattern.fromAngles("eee", HexDir.EAST); + public static final HexPattern CONSIDERATION = HexPattern.fromAngles("qqqaw", HexDir.WEST); - public static final HexPattern EVANITION = HexPattern.fromAngles("eeedw", HexDir.EAST); + public static final HexPattern EVANITION = HexPattern.fromAngles("eeedw", HexDir.EAST); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpellCircleContext.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpellCircleContext.kt index 269d6465c4..eaf7c9771b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpellCircleContext.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/SpellCircleContext.kt @@ -5,52 +5,58 @@ import net.minecraft.core.BlockPos import net.minecraft.nbt.CompoundTag import net.minecraft.world.phys.AABB -/** - * Optional field on a [CastingEnvironment] for the spell circle - */ -data class SpellCircleContext(val impetusPos: BlockPos, val aabb: AABB, val activatorAlwaysInRange: Boolean) { - fun serializeToNBT() = NBTBuilder { - TAG_IMPETUS_X %= impetusPos.x - TAG_IMPETUS_Y %= impetusPos.y - TAG_IMPETUS_Z %= impetusPos.z - - TAG_MIN_X %= aabb.minX - TAG_MIN_Y %= aabb.minY - TAG_MIN_Z %= aabb.minZ - TAG_MAX_X %= aabb.maxX - TAG_MAX_Y %= aabb.maxY - TAG_MAX_Z %= aabb.maxZ - - TAG_PLAYER_ALWAYS_IN_RANGE %= activatorAlwaysInRange - } - - companion object { - const val TAG_IMPETUS_X = "impetus_x" - const val TAG_IMPETUS_Y = "impetus_y" - const val TAG_IMPETUS_Z = "impetus_z" - const val TAG_MIN_X = "min_x" - const val TAG_MIN_Y = "min_y" - const val TAG_MIN_Z = "min_z" - const val TAG_MAX_X = "max_x" - const val TAG_MAX_Y = "max_y" - const val TAG_MAX_Z = "max_z" - const val TAG_PLAYER_ALWAYS_IN_RANGE = "player_always_in_range" - - fun fromNBT(tag: CompoundTag): SpellCircleContext { - val impX = tag.getInt(TAG_IMPETUS_X) - val impY = tag.getInt(TAG_IMPETUS_Y) - val impZ = tag.getInt(TAG_IMPETUS_Z) - - val minX = tag.getDouble(TAG_MIN_X) - val minY = tag.getDouble(TAG_MIN_Y) - val minZ = tag.getDouble(TAG_MIN_Z) - val maxX = tag.getDouble(TAG_MAX_X) - val maxY = tag.getDouble(TAG_MAX_Y) - val maxZ = tag.getDouble(TAG_MAX_Z) - - val playerAIR = tag.getBoolean(TAG_PLAYER_ALWAYS_IN_RANGE) - - return SpellCircleContext(BlockPos(impX, impY, impZ), AABB(minX, minY, minZ, maxX, maxY, maxZ), playerAIR) - } - } +/** Optional field on a [CastingEnvironment] for the spell circle */ +data class SpellCircleContext( + val impetusPos: BlockPos, + val aabb: AABB, + val activatorAlwaysInRange: Boolean +) { + fun serializeToNBT() = NBTBuilder { + TAG_IMPETUS_X %= impetusPos.x + TAG_IMPETUS_Y %= impetusPos.y + TAG_IMPETUS_Z %= impetusPos.z + + TAG_MIN_X %= aabb.minX + TAG_MIN_Y %= aabb.minY + TAG_MIN_Z %= aabb.minZ + TAG_MAX_X %= aabb.maxX + TAG_MAX_Y %= aabb.maxY + TAG_MAX_Z %= aabb.maxZ + + TAG_PLAYER_ALWAYS_IN_RANGE %= activatorAlwaysInRange + } + + companion object { + const val TAG_IMPETUS_X = "impetus_x" + const val TAG_IMPETUS_Y = "impetus_y" + const val TAG_IMPETUS_Z = "impetus_z" + const val TAG_MIN_X = "min_x" + const val TAG_MIN_Y = "min_y" + const val TAG_MIN_Z = "min_z" + const val TAG_MAX_X = "max_x" + const val TAG_MAX_Y = "max_y" + const val TAG_MAX_Z = "max_z" + const val TAG_PLAYER_ALWAYS_IN_RANGE = "player_always_in_range" + + fun fromNBT(tag: CompoundTag): SpellCircleContext { + val impX = tag.getInt(TAG_IMPETUS_X) + val impY = tag.getInt(TAG_IMPETUS_Y) + val impZ = tag.getInt(TAG_IMPETUS_Z) + + val minX = tag.getDouble(TAG_MIN_X) + val minY = tag.getDouble(TAG_MIN_Y) + val minZ = tag.getDouble(TAG_MIN_Z) + val maxX = tag.getDouble(TAG_MAX_X) + val maxY = tag.getDouble(TAG_MAX_Y) + val maxZ = tag.getDouble(TAG_MAX_Z) + + val playerAIR = tag.getBoolean(TAG_PLAYER_ALWAYS_IN_RANGE) + + return SpellCircleContext( + BlockPos(impX, impY, impZ), + AABB(minX, minY, minZ, maxX, maxY, maxZ), + playerAIR + ) + } + } } 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 7a9bac13cc..aeab007b44 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 @@ -1,5 +1,7 @@ package at.petrak.hexcasting.api.casting.eval.env; +import static at.petrak.hexcasting.api.casting.eval.env.PlayerBasedCastEnv.SENTINEL_RADIUS; + import at.petrak.hexcasting.api.HexAPI; import at.petrak.hexcasting.api.casting.ParticleSpray; import at.petrak.hexcasting.api.casting.PatternShapeMatch; @@ -13,6 +15,9 @@ import at.petrak.hexcasting.api.casting.mishaps.MishapDisallowedSpell; import at.petrak.hexcasting.api.mod.HexConfig; import at.petrak.hexcasting.api.pigment.FrozenPigment; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; @@ -25,187 +30,176 @@ import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; - -import static at.petrak.hexcasting.api.casting.eval.env.PlayerBasedCastEnv.SENTINEL_RADIUS; - public class CircleCastEnv extends CastingEnvironment { - protected final CircleExecutionState execState; - - public CircleCastEnv(ServerLevel world, CircleExecutionState execState) { - super(world); - this.execState = execState; - } - - @Override - public @Nullable LivingEntity getCastingEntity() { - return this.execState.getCaster(this.world); - } - - @Override - public @Nullable ServerPlayer getCaster() { - return this.execState.getCaster(this.world); - } - - public @Nullable BlockEntityAbstractImpetus getImpetus() { - var entity = this.world.getBlockEntity(execState.impetusPos); - - if (entity instanceof BlockEntityAbstractImpetus) - return (BlockEntityAbstractImpetus) entity; - return null; - } - - public CircleExecutionState circleState() { - return execState; - } - - @Override - public MishapEnvironment getMishapEnvironment() { - return new CircleMishapEnv(this.world, this.execState); - } - - @Override - public void precheckAction(PatternShapeMatch match) throws Mishap { - super.precheckAction(match); - - ResourceLocation key = actionKey(match); - - if (!HexConfig.server().isActionAllowedInCircles(key)) { - throw new MishapDisallowedSpell("disallowed_circle"); - } - } - - @Override - public void postExecution(CastResult result) { - super.postExecution(result); - - // we always want to play this sound one at a time - var sound = result.getSound().sound(); - if (sound != null) { - var soundPos = this.execState.currentPos; - this.world.playSound(null, soundPos, sound, SoundSource.PLAYERS, 1f, 1f); - } - - // TODO: this is gonna bite us in the bum someday - // we check whether we should cut the execution in BlockSlate, but post the mishap here; - // although everything should be pretty immutable here it's something to keep in mind - // classic time-of-check/time-of-use - var imp = this.getImpetus(); - if (imp != null) { - for (var sideEffect : result.getSideEffects()) { - if (sideEffect instanceof OperatorSideEffect.DoMishap doMishap) { - var msg = doMishap.getMishap().errorMessageWithName(this, doMishap.getErrorCtx()); - if (msg != null) { - imp.postMishap(msg); - } - } - } - } - } - - @Override - public Vec3 mishapSprayPos() { - return Vec3.atCenterOf(this.execState.currentPos); - } - - @Override - public long extractMediaEnvironment(long cost) { - var entity = this.getImpetus(); - if (entity == null) - return cost; - - var mediaAvailable = entity.getMedia(); - if (mediaAvailable < 0) - return 0; - - long mediaToTake = Math.min(cost, mediaAvailable); - cost -= mediaToTake; - entity.setMedia(mediaAvailable - mediaToTake); - - return cost; - } - - @Override - public boolean isVecInRangeEnvironment(Vec3 vec) { - var caster = this.execState.getCaster(this.world); - if (caster != null) { - if (vec.distanceToSqr(caster.position()) <= caster.getBbHeight() * caster.getBbHeight()) { - return true; - } - - var sentinel = HexAPI.instance().getSentinel(caster); - if (sentinel != null - && sentinel.extendsRange() - && caster.level().dimension() == sentinel.dimension() - && vec.distanceToSqr(sentinel.position()) <= SENTINEL_RADIUS * SENTINEL_RADIUS - ) { - return true; - } - } - - 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; - } - - @Override - public InteractionHand getCastingHand() { - return InteractionHand.MAIN_HAND; - } - - @Override - protected List getUsableStacks(StackDiscoveryMode mode) { - return new ArrayList<>(); // TODO: Could do something like get items in inventories adjacent to the circle? - } - - @Override - protected List getPrimaryStacks() { - return List.of(); // TODO: Adjacent inv! - } - - @Override - public boolean replaceItem(Predicate stackOk, ItemStack replaceWith, @Nullable InteractionHand hand) { - return false; // TODO: Adjacent inv! - } - - @Override - public FrozenPigment getPigment() { - var impetus = this.getImpetus(); - if (impetus == null) - return FrozenPigment.DEFAULT.get(); - return impetus.getPigment(); - } - - @Override - public @Nullable FrozenPigment setPigment(@Nullable FrozenPigment pigment) { - var impetus = this.getImpetus(); - if (impetus == null) - return null; - return impetus.setPigment(pigment); - } - - @Override - public void produceParticles(ParticleSpray particles, FrozenPigment pigment) { - particles.sprayParticles(this.world, pigment); - } - - @Override - public void printMessage(Component message) { - var impetus = getImpetus(); - if (impetus == null) - return; - impetus.postPrint(message); - } + protected final CircleExecutionState execState; + + public CircleCastEnv(ServerLevel world, CircleExecutionState execState) { + super(world); + this.execState = execState; + } + + @Override + public @Nullable LivingEntity getCastingEntity() { + return this.execState.getCaster(this.world); + } + + @Override + public @Nullable ServerPlayer getCaster() { + return this.execState.getCaster(this.world); + } + + public @Nullable BlockEntityAbstractImpetus getImpetus() { + var entity = this.world.getBlockEntity(execState.impetusPos); + + if (entity instanceof BlockEntityAbstractImpetus) return (BlockEntityAbstractImpetus) entity; + return null; + } + + public CircleExecutionState circleState() { + return execState; + } + + @Override + public MishapEnvironment getMishapEnvironment() { + return new CircleMishapEnv(this.world, this.execState); + } + + @Override + public void precheckAction(PatternShapeMatch match) throws Mishap { + super.precheckAction(match); + + ResourceLocation key = actionKey(match); + + if (!HexConfig.server().isActionAllowedInCircles(key)) { + throw new MishapDisallowedSpell("disallowed_circle"); + } + } + + @Override + public void postExecution(CastResult result) { + super.postExecution(result); + + // we always want to play this sound one at a time + var sound = result.getSound().sound(); + if (sound != null) { + var soundPos = this.execState.currentPos; + this.world.playSound(null, soundPos, sound, SoundSource.PLAYERS, 1f, 1f); + } + + // TODO: this is gonna bite us in the bum someday + // we check whether we should cut the execution in BlockSlate, but post the mishap here; + // although everything should be pretty immutable here it's something to keep in mind + // classic time-of-check/time-of-use + var imp = this.getImpetus(); + if (imp != null) { + for (var sideEffect : result.getSideEffects()) { + if (sideEffect instanceof OperatorSideEffect.DoMishap doMishap) { + var msg = doMishap.getMishap().errorMessageWithName(this, doMishap.getErrorCtx()); + if (msg != null) { + imp.postMishap(msg); + } + } + } + } + } + + @Override + public Vec3 mishapSprayPos() { + return Vec3.atCenterOf(this.execState.currentPos); + } + + @Override + public long extractMediaEnvironment(long cost) { + var entity = this.getImpetus(); + if (entity == null) return cost; + + var mediaAvailable = entity.getMedia(); + if (mediaAvailable < 0) return 0; + + long mediaToTake = Math.min(cost, mediaAvailable); + cost -= mediaToTake; + entity.setMedia(mediaAvailable - mediaToTake); + + return cost; + } + + @Override + public boolean isVecInRangeEnvironment(Vec3 vec) { + var caster = this.execState.getCaster(this.world); + if (caster != null) { + if (vec.distanceToSqr(caster.position()) <= caster.getBbHeight() * caster.getBbHeight()) { + return true; + } + + var sentinel = HexAPI.instance().getSentinel(caster); + if (sentinel != null + && sentinel.extendsRange() + && caster.level().dimension() == sentinel.dimension() + && vec.distanceToSqr(sentinel.position()) <= SENTINEL_RADIUS * SENTINEL_RADIUS) { + return true; + } + } + + 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; + } + + @Override + public InteractionHand getCastingHand() { + return InteractionHand.MAIN_HAND; + } + + @Override + protected List getUsableStacks(StackDiscoveryMode mode) { + return new ArrayList<>(); // TODO: Could do something like get items in inventories adjacent to + // the circle? + } + + @Override + protected List getPrimaryStacks() { + return List.of(); // TODO: Adjacent inv! + } + + @Override + public boolean replaceItem( + Predicate stackOk, ItemStack replaceWith, @Nullable InteractionHand hand) { + return false; // TODO: Adjacent inv! + } + + @Override + public FrozenPigment getPigment() { + var impetus = this.getImpetus(); + if (impetus == null) return FrozenPigment.DEFAULT.get(); + return impetus.getPigment(); + } + + @Override + public @Nullable FrozenPigment setPigment(@Nullable FrozenPigment pigment) { + var impetus = this.getImpetus(); + if (impetus == null) return null; + return impetus.setPigment(pigment); + } + + @Override + public void produceParticles(ParticleSpray particles, FrozenPigment pigment) { + particles.sprayParticles(this.world, pigment); + } + + @Override + public void printMessage(Component message) { + var impetus = getImpetus(); + if (impetus == null) return; + impetus.postPrint(message); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/CircleMishapEnv.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/CircleMishapEnv.java index 407f0eb307..17a17066d0 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/CircleMishapEnv.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/CircleMishapEnv.java @@ -6,40 +6,28 @@ import net.minecraft.world.phys.Vec3; public class CircleMishapEnv extends MishapEnvironment { - protected final CircleExecutionState execState; + protected final CircleExecutionState execState; - protected CircleMishapEnv(ServerLevel world, CircleExecutionState execState) { - super(world, null); - this.execState = execState; - } + protected CircleMishapEnv(ServerLevel world, CircleExecutionState execState) { + super(world, null); + this.execState = execState; + } - @Override - public void yeetHeldItemsTowards(Vec3 targetPos) { + @Override + public void yeetHeldItemsTowards(Vec3 targetPos) {} - } + @Override + public void dropHeldItems() {} - @Override - public void dropHeldItems() { + @Override + public void drown() {} - } + @Override + public void damage(float healthProportion) {} - @Override - public void drown() { + @Override + public void removeXp(int amount) {} - } - - @Override - public void damage(float healthProportion) { - - } - - @Override - public void removeXp(int amount) { - - } - - @Override - public void blind(int ticks) { - - } + @Override + public void blind(int ticks) {} } 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..139ca51127 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 @@ -7,79 +7,74 @@ import at.petrak.hexcasting.common.lib.hex.HexEvalSounds; import at.petrak.hexcasting.common.msgs.MsgNewSpiralPatternsS2C; import at.petrak.hexcasting.xplat.IXplatAbstractions; +import java.util.List; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; -import java.util.List; - public class PackagedItemCastEnv extends PlayerBasedCastEnv { - protected EvalSound sound = HexEvalSounds.NOTHING; - - public PackagedItemCastEnv(ServerPlayer caster, InteractionHand castingHand) { - super(caster, castingHand); - } - - @Override - public void postExecution(CastResult result) { - super.postExecution(result); - - if (result.component1() instanceof PatternIota patternIota) { - var packet = new MsgNewSpiralPatternsS2C( - this.caster.getUUID(), List.of(patternIota.getPattern()), 140 - ); - IXplatAbstractions.INSTANCE.sendPacketToPlayer(this.caster, packet); - IXplatAbstractions.INSTANCE.sendPacketTracking(this.caster, packet); - } - - // TODO: how do we know when to actually play this sound? - this.sound = this.sound.greaterOf(result.getSound()); - } - - @Override - public long extractMediaEnvironment(long costLeft) { - if (this.caster.isCreative()) - return 0; - - var casterStack = this.caster.getItemInHand(this.castingHand); - var casterHexHolder = IXplatAbstractions.INSTANCE.findHexHolder(casterStack); - if (casterHexHolder == null) - return costLeft; - var canCastFromInv = casterHexHolder.canDrawMediaFromInventory(); - - var casterMediaHolder = IXplatAbstractions.INSTANCE.findMediaHolder(casterStack); - - // The contracts on the AD and on this function are different. - // ADs return the amount extracted, this wants the amount left - if (casterMediaHolder != null) { - long extracted = casterMediaHolder.withdrawMedia((int) costLeft, false); - costLeft -= extracted; - } - if (canCastFromInv && costLeft > 0) { - costLeft = this.extractMediaFromInventory(costLeft, this.canOvercast()); - } - - return costLeft; - } - - @Override - public InteractionHand getCastingHand() { - return this.castingHand; - } - - @Override - public FrozenPigment getPigment() { - var casterStack = this.caster.getItemInHand(this.castingHand); - var casterHexHolder = IXplatAbstractions.INSTANCE.findHexHolder(casterStack); - if (casterHexHolder == null) - return IXplatAbstractions.INSTANCE.getPigment(this.caster); - var hexHolderPigment = casterHexHolder.getPigment(); - if (hexHolderPigment != null) - return hexHolderPigment; - return IXplatAbstractions.INSTANCE.getPigment(this.caster); - } - - public EvalSound getSound() { - return sound; - } + protected EvalSound sound = HexEvalSounds.NOTHING; + + public PackagedItemCastEnv(ServerPlayer caster, InteractionHand castingHand) { + super(caster, castingHand); + } + + @Override + public void postExecution(CastResult result) { + super.postExecution(result); + + if (result.component1() instanceof PatternIota patternIota) { + var packet = + new MsgNewSpiralPatternsS2C( + this.caster.getUUID(), List.of(patternIota.getPattern()), 140); + IXplatAbstractions.INSTANCE.sendPacketToPlayer(this.caster, packet); + IXplatAbstractions.INSTANCE.sendPacketTracking(this.caster, packet); + } + + // TODO: how do we know when to actually play this sound? + this.sound = this.sound.greaterOf(result.getSound()); + } + + @Override + public long extractMediaEnvironment(long costLeft) { + if (this.caster.isCreative()) return 0; + + var casterStack = this.caster.getItemInHand(this.castingHand); + var casterHexHolder = IXplatAbstractions.INSTANCE.findHexHolder(casterStack); + if (casterHexHolder == null) return costLeft; + var canCastFromInv = casterHexHolder.canDrawMediaFromInventory(); + + var casterMediaHolder = IXplatAbstractions.INSTANCE.findMediaHolder(casterStack); + + // The contracts on the AD and on this function are different. + // ADs return the amount extracted, this wants the amount left + if (casterMediaHolder != null) { + long extracted = casterMediaHolder.withdrawMedia((int) costLeft, false); + costLeft -= extracted; + } + if (canCastFromInv && costLeft > 0) { + costLeft = this.extractMediaFromInventory(costLeft, this.canOvercast()); + } + + return costLeft; + } + + @Override + public InteractionHand getCastingHand() { + return this.castingHand; + } + + @Override + public FrozenPigment getPigment() { + var casterStack = this.caster.getItemInHand(this.castingHand); + var casterHexHolder = IXplatAbstractions.INSTANCE.findHexHolder(casterStack); + if (casterHexHolder == null) return IXplatAbstractions.INSTANCE.getPigment(this.caster); + var hexHolderPigment = casterHexHolder.getPigment(); + if (hexHolderPigment != null) return hexHolderPigment; + return IXplatAbstractions.INSTANCE.getPigment(this.caster); + } + + public EvalSound getSound() { + return sound; + } } 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..c2fb2a4501 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 @@ -1,5 +1,7 @@ package at.petrak.hexcasting.api.casting.eval.env; +import static at.petrak.hexcasting.api.HexAPI.modLoc; + import at.petrak.hexcasting.api.HexAPI; import at.petrak.hexcasting.api.addldata.ADMediaHolder; import at.petrak.hexcasting.api.advancements.HexAdvancementTriggers; @@ -16,6 +18,9 @@ import at.petrak.hexcasting.api.utils.MediaHelper; import at.petrak.hexcasting.common.lib.HexDamageTypes; import at.petrak.hexcasting.xplat.IXplatAbstractions; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; @@ -28,251 +33,245 @@ import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; - -import static at.petrak.hexcasting.api.HexAPI.modLoc; - public abstract class PlayerBasedCastEnv extends CastingEnvironment { - public static final double AMBIT_RADIUS = 32.0; - public static final double SENTINEL_RADIUS = 16.0; - - protected final ServerPlayer caster; - protected final InteractionHand castingHand; - - protected PlayerBasedCastEnv(ServerPlayer caster, InteractionHand castingHand) { - super(caster.serverLevel()); - this.caster = caster; - this.castingHand = castingHand; - } - - @Override - public LivingEntity getCastingEntity() { - return this.caster; - } - - @Override - public ServerPlayer getCaster() { - return this.caster; - } - - @Override - public void postExecution(CastResult result) { - super.postExecution(result); - - for (var sideEffect : result.getSideEffects()) { - if (sideEffect instanceof OperatorSideEffect.DoMishap doMishap) { - this.sendMishapMsgToPlayer(doMishap); - } - } - } - - @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; - } - }; - } - - @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; - } - } - - @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; - } - - @Override - public boolean isVecInRangeEnvironment(Vec3 vec) { - var sentinel = HexAPI.instance().getSentinel(this.caster); - if (sentinel != null - && sentinel.extendsRange() - && this.caster.level().dimension() == sentinel.dimension() - && vec.distanceToSqr(sentinel.position()) <= SENTINEL_RADIUS * SENTINEL_RADIUS - ) { - return true; - } - - return vec.distanceToSqr(this.caster.position()) <= AMBIT_RADIUS * AMBIT_RADIUS; - } - - @Override - public boolean hasEditPermissionsAtEnvironment(BlockPos pos) { - return this.caster.gameMode.getGameModeForPlayer() != GameType.ADVENTURE && this.world.mayInteract(this.caster, pos); - } - - /** - * Search the player's inventory for media ADs and use them. - */ - protected long extractMediaFromInventory(long costLeft, boolean allowOvercast) { - List sources = MediaHelper.scanPlayerForMediaStuff(this.caster); - - var startCost = costLeft; - - for (var source : sources) { - var found = MediaHelper.extractMedia(source, costLeft, false, false); - costLeft -= found; - if (costLeft <= 0) { - break; - } - } - - if (costLeft > 0 && allowOvercast) { - double mediaToHealth = HexConfig.common().mediaToHealthRate(); - double healthToRemove = Math.max(costLeft / mediaToHealth, 0.5); - var mediaAbleToCastFromHP = this.caster.getHealth() * mediaToHealth; - - Mishap.trulyHurt(this.caster, this.caster.damageSources().source(HexDamageTypes.OVERCAST), (float) healthToRemove); - - var actuallyTaken = Mth.ceil(mediaAbleToCastFromHP - (this.caster.getHealth() * mediaToHealth)); - - HexAdvancementTriggers.OVERCAST_TRIGGER.trigger(this.caster, actuallyTaken); - this.caster.awardStat(HexStatistics.MEDIA_OVERCAST, actuallyTaken); - - costLeft -= actuallyTaken; - } - - this.caster.awardStat(HexStatistics.MEDIA_USED, (int) (startCost - costLeft)); - HexAdvancementTriggers.SPEND_MEDIA_TRIGGER.trigger( - this.caster, - startCost - costLeft, - costLeft < 0 ? -costLeft : 0 - ); - - return costLeft; - } - - protected boolean canOvercast() { - var adv = this.world.getServer().getAdvancements().getAdvancement(modLoc("y_u_no_cast_angy")); - var advs = this.caster.getAdvancements(); - return advs.getOrStartProgress(adv).isDone(); - } - - @Override - public @Nullable FrozenPigment setPigment(@Nullable FrozenPigment pigment) { - return IXplatAbstractions.INSTANCE.setPigment(caster, pigment); - } - - @Override - public void produceParticles(ParticleSpray particles, FrozenPigment pigment) { - particles.sprayParticles(this.world, pigment); - } - - @Override - public Vec3 mishapSprayPos() { - return this.caster.position(); - } - - @Override - public MishapEnvironment getMishapEnvironment() { - return new PlayerBasedMishapEnv(this.caster); - } - - protected void sendMishapMsgToPlayer(OperatorSideEffect.DoMishap mishap) { - var msg = mishap.getMishap().errorMessageWithName(this, mishap.getErrorCtx()); - if (msg != null) { - this.caster.sendSystemMessage(msg); - } - } - - @Override - protected boolean isCreativeMode() { - // not sure what the diff between this and isCreative() is - return this.caster.getAbilities().instabuild; - } - - @Override - public void printMessage(Component message) { - caster.sendSystemMessage(message); - } + public static final double AMBIT_RADIUS = 32.0; + public static final double SENTINEL_RADIUS = 16.0; + + protected final ServerPlayer caster; + protected final InteractionHand castingHand; + + protected PlayerBasedCastEnv(ServerPlayer caster, InteractionHand castingHand) { + super(caster.serverLevel()); + this.caster = caster; + this.castingHand = castingHand; + } + + @Override + public LivingEntity getCastingEntity() { + return this.caster; + } + + @Override + public ServerPlayer getCaster() { + return this.caster; + } + + @Override + public void postExecution(CastResult result) { + super.postExecution(result); + + for (var sideEffect : result.getSideEffects()) { + if (sideEffect instanceof OperatorSideEffect.DoMishap doMishap) { + this.sendMishapMsgToPlayer(doMishap); + } + } + } + + @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; + } + }; + } + + @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; + } + } + + @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; + } + + @Override + public boolean isVecInRangeEnvironment(Vec3 vec) { + var sentinel = HexAPI.instance().getSentinel(this.caster); + if (sentinel != null + && sentinel.extendsRange() + && this.caster.level().dimension() == sentinel.dimension() + && vec.distanceToSqr(sentinel.position()) <= SENTINEL_RADIUS * SENTINEL_RADIUS) { + return true; + } + + return vec.distanceToSqr(this.caster.position()) <= AMBIT_RADIUS * AMBIT_RADIUS; + } + + @Override + public boolean hasEditPermissionsAtEnvironment(BlockPos pos) { + return this.caster.gameMode.getGameModeForPlayer() != GameType.ADVENTURE + && this.world.mayInteract(this.caster, pos); + } + + /** Search the player's inventory for media ADs and use them. */ + protected long extractMediaFromInventory(long costLeft, boolean allowOvercast) { + List sources = MediaHelper.scanPlayerForMediaStuff(this.caster); + + var startCost = costLeft; + + for (var source : sources) { + var found = MediaHelper.extractMedia(source, costLeft, false, false); + costLeft -= found; + if (costLeft <= 0) { + break; + } + } + + if (costLeft > 0 && allowOvercast) { + double mediaToHealth = HexConfig.common().mediaToHealthRate(); + double healthToRemove = Math.max(costLeft / mediaToHealth, 0.5); + var mediaAbleToCastFromHP = this.caster.getHealth() * mediaToHealth; + + Mishap.trulyHurt( + this.caster, + this.caster.damageSources().source(HexDamageTypes.OVERCAST), + (float) healthToRemove); + + var actuallyTaken = + Mth.ceil(mediaAbleToCastFromHP - (this.caster.getHealth() * mediaToHealth)); + + HexAdvancementTriggers.OVERCAST_TRIGGER.trigger(this.caster, actuallyTaken); + this.caster.awardStat(HexStatistics.MEDIA_OVERCAST, actuallyTaken); + + costLeft -= actuallyTaken; + } + + this.caster.awardStat(HexStatistics.MEDIA_USED, (int) (startCost - costLeft)); + HexAdvancementTriggers.SPEND_MEDIA_TRIGGER.trigger( + this.caster, startCost - costLeft, costLeft < 0 ? -costLeft : 0); + + return costLeft; + } + + protected boolean canOvercast() { + var adv = this.world.getServer().getAdvancements().getAdvancement(modLoc("y_u_no_cast_angy")); + var advs = this.caster.getAdvancements(); + return advs.getOrStartProgress(adv).isDone(); + } + + @Override + public @Nullable FrozenPigment setPigment(@Nullable FrozenPigment pigment) { + return IXplatAbstractions.INSTANCE.setPigment(caster, pigment); + } + + @Override + public void produceParticles(ParticleSpray particles, FrozenPigment pigment) { + particles.sprayParticles(this.world, pigment); + } + + @Override + public Vec3 mishapSprayPos() { + return this.caster.position(); + } + + @Override + public MishapEnvironment getMishapEnvironment() { + return new PlayerBasedMishapEnv(this.caster); + } + + protected void sendMishapMsgToPlayer(OperatorSideEffect.DoMishap mishap) { + var msg = mishap.getMishap().errorMessageWithName(this, mishap.getErrorCtx()); + if (msg != null) { + this.caster.sendSystemMessage(msg); + } + } + + @Override + protected boolean isCreativeMode() { + // not sure what the diff between this and isCreative() is + return this.caster.getAbilities().instabuild; + } + + @Override + public void printMessage(Component message) { + caster.sendSystemMessage(message); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedMishapEnv.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedMishapEnv.java index 370ba5002a..d21c81b3c9 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedMishapEnv.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedMishapEnv.java @@ -11,48 +11,51 @@ import net.minecraft.world.phys.Vec3; public class PlayerBasedMishapEnv extends MishapEnvironment { - public PlayerBasedMishapEnv(ServerPlayer player) { - super(player.serverLevel(), player); - } - - @Override - public void yeetHeldItemsTowards(Vec3 targetPos) { - var pos = this.caster.position(); - var delta = targetPos.subtract(pos).normalize().scale(0.5); - - for (var hand : InteractionHand.values()) { - var stack = this.caster.getItemInHand(hand); - this.caster.setItemInHand(hand, ItemStack.EMPTY); - this.yeetItem(stack, pos, delta); - } - } - - @Override - public void dropHeldItems() { - var delta = this.caster.getLookAngle(); - this.yeetHeldItemsTowards(this.caster.position().add(delta)); - } - - @Override - public void damage(float healthProportion) { - Mishap.trulyHurt(this.caster, this.caster.damageSources().source(HexDamageTypes.OVERCAST), this.caster.getHealth() * healthProportion); - } - - @Override - public void drown() { - if (this.caster.getAirSupply() < 200) { - this.caster.hurt(this.caster.damageSources().drown(), 2f); - } - this.caster.setAirSupply(0); - } - - @Override - public void removeXp(int amount) { - this.caster.giveExperiencePoints(-amount); - } - - @Override - public void blind(int ticks) { - this.caster.addEffect(new MobEffectInstance(MobEffects.BLINDNESS, ticks)); - } + public PlayerBasedMishapEnv(ServerPlayer player) { + super(player.serverLevel(), player); + } + + @Override + public void yeetHeldItemsTowards(Vec3 targetPos) { + var pos = this.caster.position(); + var delta = targetPos.subtract(pos).normalize().scale(0.5); + + for (var hand : InteractionHand.values()) { + var stack = this.caster.getItemInHand(hand); + this.caster.setItemInHand(hand, ItemStack.EMPTY); + this.yeetItem(stack, pos, delta); + } + } + + @Override + public void dropHeldItems() { + var delta = this.caster.getLookAngle(); + this.yeetHeldItemsTowards(this.caster.position().add(delta)); + } + + @Override + public void damage(float healthProportion) { + Mishap.trulyHurt( + this.caster, + this.caster.damageSources().source(HexDamageTypes.OVERCAST), + this.caster.getHealth() * healthProportion); + } + + @Override + public void drown() { + if (this.caster.getAirSupply() < 200) { + this.caster.hurt(this.caster.damageSources().drown(), 2f); + } + this.caster.setAirSupply(0); + } + + @Override + public void removeXp(int amount) { + this.caster.giveExperiencePoints(-amount); + } + + @Override + public void blind(int ticks) { + this.caster.addEffect(new MobEffectInstance(MobEffects.BLINDNESS, ticks)); + } } 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..7fb367afb5 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 @@ -13,142 +13,141 @@ import at.petrak.hexcasting.api.pigment.FrozenPigment; import at.petrak.hexcasting.common.msgs.*; import at.petrak.hexcasting.xplat.IXplatAbstractions; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundSource; import net.minecraft.world.InteractionHand; import net.minecraft.world.phys.Vec3; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - public class StaffCastEnv extends PlayerBasedCastEnv { - private final InteractionHand castingHand; - - private final Set castPatterns = new HashSet<>(); - private int soundsPlayed = 0; - - - public StaffCastEnv(ServerPlayer caster, InteractionHand castingHand) { - super(caster, castingHand); - - this.castingHand = castingHand; - } - - @Override - public void postExecution(CastResult result) { - super.postExecution(result); - - if (result.component1() instanceof PatternIota patternIota) { - castPatterns.add(patternIota.getPattern()); - } - - // we always want to play this sound one at a time - var sound = result.getSound().sound(); - if (soundsPlayed < 100 && sound != null) { // TODO: Make configurable - var soundPos = this.caster.position(); - this.world.playSound(null, soundPos.x, soundPos.y, soundPos.z, - sound, SoundSource.PLAYERS, 1f, 1f); - soundsPlayed++; - } - } - - @Override - public void postCast(CastingImage image) { - super.postCast(image); - - var packet = new MsgNewSpiralPatternsS2C( - this.caster.getUUID(), castPatterns.stream().toList(), Integer.MAX_VALUE - ); - IXplatAbstractions.INSTANCE.sendPacketToPlayer(this.caster, packet); - IXplatAbstractions.INSTANCE.sendPacketTracking(this.caster, packet); - - castPatterns.clear(); - soundsPlayed = 0; - } - - @Override - public long extractMediaEnvironment(long cost) { - 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; - } - - @Override - public InteractionHand getCastingHand() { - return castingHand; - } - - @Override - public FrozenPigment getPigment() { - return HexAPI.instance().getColorizer(this.caster); - } - - public static void handleNewPatternOnServer(ServerPlayer sender, MsgNewSpellPatternC2S msg) { - boolean cheatedPatternOverlap = false; - - List resolvedPatterns = msg.resolvedPatterns(); - if (!resolvedPatterns.isEmpty()) { - var allPoints = new HashSet(); - for (int i = 0; i < resolvedPatterns.size() - 1; i++) { - ResolvedPattern pat = resolvedPatterns.get(i); - allPoints.addAll(pat.getPattern().positions(pat.getOrigin())); - } - var currentResolvedPattern = resolvedPatterns.get(resolvedPatterns.size() - 1); - var currentSpellPoints = currentResolvedPattern.getPattern() - .positions(currentResolvedPattern.getOrigin()); - if (currentSpellPoints.stream().anyMatch(allPoints::contains)) { - cheatedPatternOverlap = true; - } - } - - if (cheatedPatternOverlap) { - return; - } - - sender.awardStat(HexStatistics.PATTERNS_DRAWN); - - var vm = IXplatAbstractions.INSTANCE.getStaffcastVM(sender, msg.handUsed()); - - // TODO: do we reset the number of evals run via the staff? because each new pat is a new tick. - - ExecutionClientView clientInfo = vm.queueExecuteAndWrapIota(new PatternIota(msg.pattern()), sender.serverLevel()); - - if (clientInfo.isStackClear()) { - IXplatAbstractions.INSTANCE.setStaffcastImage(sender, null); - IXplatAbstractions.INSTANCE.setPatterns(sender, List.of()); - } else { - IXplatAbstractions.INSTANCE.setStaffcastImage(sender, vm.getImage().withOverriddenUsedOps(0)); - if (!resolvedPatterns.isEmpty()) { - resolvedPatterns.get(resolvedPatterns.size() - 1).setType(clientInfo.getResolutionType()); - } - IXplatAbstractions.INSTANCE.setPatterns(sender, resolvedPatterns); - } - - IXplatAbstractions.INSTANCE.sendPacketToPlayer(sender, - new MsgNewSpellPatternS2C(clientInfo, resolvedPatterns.size() - 1)); - - IMessage packet; - if (clientInfo.isStackClear()) { - packet = new MsgClearSpiralPatternsS2C(sender.getUUID()); - } else { - packet = new MsgNewSpiralPatternsS2C(sender.getUUID(), List.of(msg.pattern()), Integer.MAX_VALUE); - } - IXplatAbstractions.INSTANCE.sendPacketToPlayer(sender, packet); - IXplatAbstractions.INSTANCE.sendPacketTracking(sender, packet); - - if (clientInfo.getResolutionType().getSuccess()) { - // Somehow we lost spraying particles on each new pattern, so do it here - // this also nicely prevents particle spam on trinkets - new ParticleSpray(sender.position(), new Vec3(0.0, 1.5, 0.0), 0.4, Math.PI / 3, 30) - .sprayParticles(sender.serverLevel(), IXplatAbstractions.INSTANCE.getPigment(sender)); - } - } + private final InteractionHand castingHand; + + private final Set castPatterns = new HashSet<>(); + private int soundsPlayed = 0; + + public StaffCastEnv(ServerPlayer caster, InteractionHand castingHand) { + super(caster, castingHand); + + this.castingHand = castingHand; + } + + @Override + public void postExecution(CastResult result) { + super.postExecution(result); + + if (result.component1() instanceof PatternIota patternIota) { + castPatterns.add(patternIota.getPattern()); + } + + // we always want to play this sound one at a time + var sound = result.getSound().sound(); + if (soundsPlayed < 100 && sound != null) { // TODO: Make configurable + var soundPos = this.caster.position(); + this.world.playSound( + null, soundPos.x, soundPos.y, soundPos.z, sound, SoundSource.PLAYERS, 1f, 1f); + soundsPlayed++; + } + } + + @Override + public void postCast(CastingImage image) { + super.postCast(image); + + var packet = + new MsgNewSpiralPatternsS2C( + this.caster.getUUID(), castPatterns.stream().toList(), Integer.MAX_VALUE); + IXplatAbstractions.INSTANCE.sendPacketToPlayer(this.caster, packet); + IXplatAbstractions.INSTANCE.sendPacketTracking(this.caster, packet); + + castPatterns.clear(); + soundsPlayed = 0; + } + + @Override + public long extractMediaEnvironment(long cost) { + 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; + } + + @Override + public InteractionHand getCastingHand() { + return castingHand; + } + + @Override + public FrozenPigment getPigment() { + return HexAPI.instance().getColorizer(this.caster); + } + + public static void handleNewPatternOnServer(ServerPlayer sender, MsgNewSpellPatternC2S msg) { + boolean cheatedPatternOverlap = false; + + List resolvedPatterns = msg.resolvedPatterns(); + if (!resolvedPatterns.isEmpty()) { + var allPoints = new HashSet(); + for (int i = 0; i < resolvedPatterns.size() - 1; i++) { + ResolvedPattern pat = resolvedPatterns.get(i); + allPoints.addAll(pat.getPattern().positions(pat.getOrigin())); + } + var currentResolvedPattern = resolvedPatterns.get(resolvedPatterns.size() - 1); + var currentSpellPoints = + currentResolvedPattern.getPattern().positions(currentResolvedPattern.getOrigin()); + if (currentSpellPoints.stream().anyMatch(allPoints::contains)) { + cheatedPatternOverlap = true; + } + } + + if (cheatedPatternOverlap) { + return; + } + + sender.awardStat(HexStatistics.PATTERNS_DRAWN); + + var vm = IXplatAbstractions.INSTANCE.getStaffcastVM(sender, msg.handUsed()); + + // TODO: do we reset the number of evals run via the staff? because each new pat is a new tick. + + ExecutionClientView clientInfo = + vm.queueExecuteAndWrapIota(new PatternIota(msg.pattern()), sender.serverLevel()); + + if (clientInfo.isStackClear()) { + IXplatAbstractions.INSTANCE.setStaffcastImage(sender, null); + IXplatAbstractions.INSTANCE.setPatterns(sender, List.of()); + } else { + IXplatAbstractions.INSTANCE.setStaffcastImage(sender, vm.getImage().withOverriddenUsedOps(0)); + if (!resolvedPatterns.isEmpty()) { + resolvedPatterns.get(resolvedPatterns.size() - 1).setType(clientInfo.getResolutionType()); + } + IXplatAbstractions.INSTANCE.setPatterns(sender, resolvedPatterns); + } + + IXplatAbstractions.INSTANCE.sendPacketToPlayer( + sender, new MsgNewSpellPatternS2C(clientInfo, resolvedPatterns.size() - 1)); + + IMessage packet; + if (clientInfo.isStackClear()) { + packet = new MsgClearSpiralPatternsS2C(sender.getUUID()); + } else { + packet = + new MsgNewSpiralPatternsS2C(sender.getUUID(), List.of(msg.pattern()), Integer.MAX_VALUE); + } + IXplatAbstractions.INSTANCE.sendPacketToPlayer(sender, packet); + IXplatAbstractions.INSTANCE.sendPacketTracking(sender, packet); + + if (clientInfo.getResolutionType().getSuccess()) { + // Somehow we lost spraying particles on each new pattern, so do it here + // this also nicely prevents particle spam on trinkets + new ParticleSpray(sender.position(), new Vec3(0.0, 1.5, 0.0), 0.4, Math.PI / 3, 30) + .sprayParticles(sender.serverLevel(), IXplatAbstractions.INSTANCE.getPigment(sender)); + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/package-info.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/package-info.java index 036bb48b23..7818f29677 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/package-info.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/package-info.java @@ -2,4 +2,4 @@ * Default impls for some casting and mishap envs for your convenience and also so i can impl * BlockEntityAbstractImpetus in api guilt-free */ -package at.petrak.hexcasting.api.casting.eval.env; \ No newline at end of file +package at.petrak.hexcasting.api.casting.eval.env; diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/sideeffects/EvalSound.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/sideeffects/EvalSound.java index f81a34eb3f..36c3d85200 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/sideeffects/EvalSound.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/sideeffects/EvalSound.java @@ -6,13 +6,12 @@ /** * The kind of sound that plays after a cast. * - * @param sound the actual sound file - * @param priority the priority of this sound. the sound with the highest priority in a given cast will be - * played. - * shortcutMetacasting takes precedence over this. + * @param sound the actual sound file + * @param priority the priority of this sound. the sound with the highest priority in a given cast + * will be played. shortcutMetacasting takes precedence over this. */ public record EvalSound(@Nullable SoundEvent sound, int priority) { - public EvalSound greaterOf(EvalSound that) { - return (this.priority > that.priority) ? this : that; - } + public EvalSound greaterOf(EvalSound that) { + return (this.priority > that.priority) ? this : that; + } } 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..cd1b0065b6 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 @@ -13,70 +13,69 @@ import net.minecraft.server.level.ServerPlayer import net.minecraft.world.item.DyeColor import net.minecraft.world.item.ItemStack -/** - * Things that happen after a spell is cast. - */ +/** Things that happen after a spell is cast. */ sealed class OperatorSideEffect { - /** Return whether to cancel all further [OperatorSideEffect] */ - abstract fun performEffect(harness: CastingVM): Boolean + /** Return whether to cancel all further [OperatorSideEffect] */ + abstract fun performEffect(harness: CastingVM): Boolean - data class RequiredEnlightenment(val awardStat: Boolean) : OperatorSideEffect() { - override fun performEffect(harness: CastingVM): Boolean { - harness.env.castingEntity?.sendSystemMessage("hexcasting.message.cant_great_spell".asTranslatedComponent) + data class RequiredEnlightenment(val awardStat: Boolean) : OperatorSideEffect() { + override fun performEffect(harness: CastingVM): Boolean { + harness.env.castingEntity?.sendSystemMessage( + "hexcasting.message.cant_great_spell".asTranslatedComponent + ) + return true + } + } - return true - } - } + /** Try to cast a spell */ + data class AttemptSpell( + val spell: RenderedSpell, + val hasCastingSound: Boolean = true, + val awardStat: Boolean = true + ) : OperatorSideEffect() { + override fun performEffect(harness: CastingVM): Boolean { + this.spell.cast(harness.env, harness.image)?.let { harness.image = it } + if (awardStat) + (harness.env.castingEntity as? ServerPlayer)?.awardStat(HexStatistics.SPELLS_CAST) - /** Try to cast a spell */ - data class AttemptSpell( - val spell: RenderedSpell, - val hasCastingSound: Boolean = true, - val awardStat: Boolean = true - ) : - OperatorSideEffect() { - override fun performEffect(harness: CastingVM): Boolean { - this.spell.cast(harness.env, harness.image)?.let { harness.image = it } - if (awardStat) - (harness.env.castingEntity as? ServerPlayer)?.awardStat(HexStatistics.SPELLS_CAST) + return false + } + } - 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 + } + } - data class ConsumeMedia(val amount: Long) : OperatorSideEffect() { - override fun performEffect(harness: CastingVM): Boolean { - val leftoverMedia = harness.env.extractMedia(this.amount) - return leftoverMedia > 0 - } - } + data class Particles(val spray: ParticleSpray) : OperatorSideEffect() { + override fun performEffect(harness: CastingVM): Boolean { + harness.env.produceParticles(this.spray, harness.env.pigment) + // this.spray.sprayParticles(harness.env.world, harness.env.colorizer) - data class Particles(val spray: ParticleSpray) : OperatorSideEffect() { - override fun performEffect(harness: CastingVM): Boolean { - harness.env.produceParticles(this.spray, harness.env.pigment) -// this.spray.sprayParticles(harness.env.world, harness.env.colorizer) + return false + } + } - return false - } - } + data class DoMishap(val mishap: Mishap, val errorCtx: Mishap.Context) : OperatorSideEffect() { + override fun performEffect(harness: CastingVM): Boolean { + val spray = mishap.particleSpray(harness.env) + val color = mishap.accentColor(harness.env, errorCtx) + spray.sprayParticles(harness.env.world, color) + spray.sprayParticles( + harness.env.world, + FrozenPigment(ItemStack(HexItems.DYE_PIGMENTS[DyeColor.RED]!!), Util.NIL_UUID) + ) - data class DoMishap(val mishap: Mishap, val errorCtx: Mishap.Context) : OperatorSideEffect() { - override fun performEffect(harness: CastingVM): Boolean { - val spray = mishap.particleSpray(harness.env) - val color = mishap.accentColor(harness.env, errorCtx) - spray.sprayParticles(harness.env.world, color) - spray.sprayParticles( - harness.env.world, - FrozenPigment( - ItemStack(HexItems.DYE_PIGMENTS[DyeColor.RED]!!), - Util.NIL_UUID - ) - ) + harness.image = + harness.image.copy( + stack = + mishap.executeReturnStack(harness.env, errorCtx, harness.image.stack.toMutableList()) + ) - harness.image = harness.image.copy(stack = mishap.executeReturnStack(harness.env, errorCtx, harness.image.stack.toMutableList())) - - return true - } - } + return true + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt index 3ffd771bcd..b73f10e09f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingImage.kt @@ -13,129 +13,122 @@ import net.minecraft.nbt.Tag import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.Entity -/** - * The state of a casting VM, containing the stack and all - */ -data class CastingImage private constructor( - val stack: List, - - val parenCount: Int, - val parenthesized: List, - val escapeNext: Boolean, - val opsConsumed: Long, - - val userData: CompoundTag +/** The state of a casting VM, containing the stack and all */ +data class CastingImage +private constructor( + val stack: List, + val parenCount: Int, + val parenthesized: List, + val escapeNext: Boolean, + val opsConsumed: Long, + val userData: CompoundTag ) { - constructor() : this(listOf(), 0, listOf(), false, 0, CompoundTag()) - - data class ParenthesizedIota(val iota: Iota, val escaped: Boolean) { - companion object { - const val TAG_IOTAS = "iotas" - const val TAG_ESCAPED = "escaped" - } - } - - /** - * Returns an empty list if it's too complicated. - */ - private fun Iterable.serializeToNBT(): CompoundTag { - val tag = CompoundTag() - - if (IotaType.isTooLargeToSerialize(this.map { it.iota })) { - tag.put(TAG_IOTAS, ListTag()) - tag.put(TAG_ESCAPED, ListTag()) - } else { - tag.put(TAG_IOTAS, ListIota(this.map { it.iota }).serialize()) - tag.put(TAG_ESCAPED, this.map { it.escaped }.serializeToNBT()) - } - - return tag - } - - /** - * Return a copy of this with the given number of ops additionally exhausted - */ - fun withUsedOps(count: Long) = this.copy(opsConsumed = this.opsConsumed + count) - - /** - * Return a copy of this with 1 op used - */ - fun withUsedOp() = this.withUsedOps(1) - - /** - * Returns a copy of this with the [opsConsumed] replaced with [count]. - */ - fun withOverriddenUsedOps(count: Long) = this.copy(opsConsumed = count) - - /** - * Returns a copy of this with escape/paren-related fields cleared. - */ - fun withResetEscape() = this.copy(parenCount = 0, parenthesized = listOf(), escapeNext = false) - - fun serializeToNbt() = NBTBuilder { - TAG_STACK %= stack.serializeToNBT() - - TAG_PAREN_COUNT %= parenCount - TAG_ESCAPE_NEXT %= escapeNext - TAG_PARENTHESIZED %= parenthesized.serializeToNBT() - TAG_OPS_CONSUMED %= opsConsumed - - TAG_USERDATA %= userData - } - - companion object { - const val TAG_STACK = "stack" - const val TAG_PAREN_COUNT = "open_parens" - const val TAG_PARENTHESIZED = "parenthesized" - const val TAG_ESCAPE_NEXT = "escape_next" - const val TAG_OPS_CONSUMED = "ops_consumed" - const val TAG_USERDATA = "userdata" - - @JvmStatic - fun loadFromNbt(tag: CompoundTag, world: ServerLevel): CastingImage { - return try { - val stack = mutableListOf() - val stackTag = tag.getList(TAG_STACK, Tag.TAG_COMPOUND) - for (subtag in stackTag) { - val datum = IotaType.deserialize(subtag.asCompound, world) - stack.add(datum) - } - - val userData = if (tag.contains(TAG_USERDATA)) { - tag.getCompound(TAG_USERDATA) - } else { - CompoundTag() - } - - val parenthesized = mutableListOf() - val parenTag = tag.getCompound(TAG_PARENTHESIZED) - val parenIotasTag = parenTag.getList(TAG_IOTAS, Tag.TAG_COMPOUND) - val parenEscapedTag = parenTag.getByteArray(TAG_ESCAPED) - - for ((subtag, isEscapedByte) in parenIotasTag.zipWithDefault(parenEscapedTag) { _ -> 0 }) { - parenthesized.add(ParenthesizedIota(IotaType.deserialize(subtag.downcast(CompoundTag.TYPE), world), isEscapedByte != 0.toByte())) - } - - val parenCount = tag.getInt(TAG_PAREN_COUNT) - val escapeNext = tag.getBoolean(TAG_ESCAPE_NEXT) - val opsUsed = tag.getLong(TAG_OPS_CONSUMED) - - CastingImage(stack, parenCount, parenthesized, escapeNext, opsUsed, userData) - } catch (exn: Exception) { - HexAPI.LOGGER.warn("error while loading a CastingImage", exn) - CastingImage() - } - } - - @JvmStatic - fun checkAndMarkGivenMotion(userData: CompoundTag, entity: Entity): Boolean { - val marked = userData.getOrCreateCompound(HexAPI.MARKED_MOVED_USERDATA) - return if (marked.contains(entity.stringUUID)) { - true - } else { - marked.putBoolean(entity.stringUUID, true) - false - } - } - } + constructor() : this(listOf(), 0, listOf(), false, 0, CompoundTag()) + + data class ParenthesizedIota(val iota: Iota, val escaped: Boolean) { + companion object { + const val TAG_IOTAS = "iotas" + const val TAG_ESCAPED = "escaped" + } + } + + /** Returns an empty list if it's too complicated. */ + private fun Iterable.serializeToNBT(): CompoundTag { + val tag = CompoundTag() + + if (IotaType.isTooLargeToSerialize(this.map { it.iota })) { + tag.put(TAG_IOTAS, ListTag()) + tag.put(TAG_ESCAPED, ListTag()) + } else { + tag.put(TAG_IOTAS, ListIota(this.map { it.iota }).serialize()) + tag.put(TAG_ESCAPED, this.map { it.escaped }.serializeToNBT()) + } + + return tag + } + + /** Return a copy of this with the given number of ops additionally exhausted */ + fun withUsedOps(count: Long) = this.copy(opsConsumed = this.opsConsumed + count) + + /** Return a copy of this with 1 op used */ + fun withUsedOp() = this.withUsedOps(1) + + /** Returns a copy of this with the [opsConsumed] replaced with [count]. */ + fun withOverriddenUsedOps(count: Long) = this.copy(opsConsumed = count) + + /** Returns a copy of this with escape/paren-related fields cleared. */ + fun withResetEscape() = this.copy(parenCount = 0, parenthesized = listOf(), escapeNext = false) + + fun serializeToNbt() = NBTBuilder { + TAG_STACK %= stack.serializeToNBT() + + TAG_PAREN_COUNT %= parenCount + TAG_ESCAPE_NEXT %= escapeNext + TAG_PARENTHESIZED %= parenthesized.serializeToNBT() + TAG_OPS_CONSUMED %= opsConsumed + + TAG_USERDATA %= userData + } + + companion object { + const val TAG_STACK = "stack" + const val TAG_PAREN_COUNT = "open_parens" + const val TAG_PARENTHESIZED = "parenthesized" + const val TAG_ESCAPE_NEXT = "escape_next" + const val TAG_OPS_CONSUMED = "ops_consumed" + const val TAG_USERDATA = "userdata" + + @JvmStatic + fun loadFromNbt(tag: CompoundTag, world: ServerLevel): CastingImage { + return try { + val stack = mutableListOf() + val stackTag = tag.getList(TAG_STACK, Tag.TAG_COMPOUND) + for (subtag in stackTag) { + val datum = IotaType.deserialize(subtag.asCompound, world) + stack.add(datum) + } + + val userData = + if (tag.contains(TAG_USERDATA)) { + tag.getCompound(TAG_USERDATA) + } else { + CompoundTag() + } + + val parenthesized = mutableListOf() + val parenTag = tag.getCompound(TAG_PARENTHESIZED) + val parenIotasTag = parenTag.getList(TAG_IOTAS, Tag.TAG_COMPOUND) + val parenEscapedTag = parenTag.getByteArray(TAG_ESCAPED) + + for ((subtag, isEscapedByte) in parenIotasTag.zipWithDefault(parenEscapedTag) { _ -> 0 }) { + parenthesized.add( + ParenthesizedIota( + IotaType.deserialize(subtag.downcast(CompoundTag.TYPE), world), + isEscapedByte != 0.toByte() + ) + ) + } + + val parenCount = tag.getInt(TAG_PAREN_COUNT) + val escapeNext = tag.getBoolean(TAG_ESCAPE_NEXT) + val opsUsed = tag.getLong(TAG_OPS_CONSUMED) + + CastingImage(stack, parenCount, parenthesized, escapeNext, opsUsed, userData) + } catch (exn: Exception) { + HexAPI.LOGGER.warn("error while loading a CastingImage", exn) + CastingImage() + } + } + + @JvmStatic + fun checkAndMarkGivenMotion(userData: CompoundTag, entity: Entity): Boolean { + val marked = userData.getOrCreateCompound(HexAPI.MARKED_MOVED_USERDATA) + return if (marked.contains(entity.stringUUID)) { + true + } else { + marked.putBoolean(entity.stringUUID, true) + false + } + } + } } 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..c0c7d09684 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 @@ -19,292 +19,291 @@ import net.minecraft.nbt.CompoundTag import net.minecraft.server.level.ServerLevel /** - * The virtual machine! This is the glue that determines the next iteration of a [CastingImage], using a - * [CastingEnvironment] to affect the world. + * The virtual machine! This is the glue that determines the next iteration of a [CastingImage], + * using a [CastingEnvironment] to affect the world. */ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { - init { - env.triggerCreateEvent(image.userData) - } + init { + env.triggerCreateEvent(image.userData) + } - /** - * Execute a single iota. - */ - fun queueExecuteAndWrapIota(iota: Iota, world: ServerLevel): ExecutionClientView = queueExecuteAndWrapIotas(listOf(iota), world) + /** Execute a single iota. */ + fun queueExecuteAndWrapIota(iota: Iota, world: ServerLevel): ExecutionClientView = + queueExecuteAndWrapIotas(listOf(iota), world) - /** - * The main entrypoint to the VM. Given a list of iotas, execute them in sequence, and return whatever the client - * needs to see. - * - * Mutates this - */ - fun queueExecuteAndWrapIotas(iotas: List, world: ServerLevel): ExecutionClientView { - // Initialize the continuation stack to a single top-level eval for all iotas. - var continuation = SpellContinuation.Done.pushFrame(FrameEvaluate(SpellList.LList(0, iotas), false)) - // Begin aggregating info - val info = TempControllerInfo(earlyExit = false) - var lastResolutionType = ResolvedPatternType.UNRESOLVED - while (continuation is SpellContinuation.NotDone && !info.earlyExit) { - // Take the top of the continuation stack... - val next = continuation.frame - // ...and execute it. - // TODO there used to be error checking code here; I'm pretty sure any and all mishaps should already - // get caught and folded into CastResult by evaluate. - val image2 = next.evaluate(continuation.next, world, this).let { result -> - // if stack is unable to be serialized, have the result be an error - if (result.newData != null && IotaType.isTooLargeToSerialize(result.newData.stack)) { - result.copy( - newData = null, - sideEffects = listOf(OperatorSideEffect.DoMishap(MishapStackSize(), Mishap.Context(null, null))), - resolutionType = ResolvedPatternType.ERRORED, - sound = HexEvalSounds.MISHAP, - ) - } else { - result - } - } + /** + * The main entrypoint to the VM. Given a list of iotas, execute them in sequence, and return + * whatever the client needs to see. + * + * Mutates this + */ + fun queueExecuteAndWrapIotas(iotas: List, world: ServerLevel): ExecutionClientView { + // Initialize the continuation stack to a single top-level eval for all iotas. + var continuation = + SpellContinuation.Done.pushFrame(FrameEvaluate(SpellList.LList(0, iotas), false)) + // Begin aggregating info + val info = TempControllerInfo(earlyExit = false) + var lastResolutionType = ResolvedPatternType.UNRESOLVED + while (continuation is SpellContinuation.NotDone && !info.earlyExit) { + // Take the top of the continuation stack... + val next = continuation.frame + // ...and execute it. + // TODO there used to be error checking code here; I'm pretty sure any and all mishaps should + // already + // get caught and folded into CastResult by evaluate. + val image2 = + next.evaluate(continuation.next, world, this).let { result -> + // if stack is unable to be serialized, have the result be an error + if (result.newData != null && IotaType.isTooLargeToSerialize(result.newData.stack)) { + result.copy( + newData = null, + sideEffects = + listOf(OperatorSideEffect.DoMishap(MishapStackSize(), Mishap.Context(null, null))), + resolutionType = ResolvedPatternType.ERRORED, + sound = HexEvalSounds.MISHAP, + ) + } else { + result + } + } - // Then write all pertinent data back to the harness for the next iteration. - if (image2.newData != null) { - this.image = image2.newData - } - this.env.postExecution(image2) + // Then write all pertinent data back to the harness for the next iteration. + if (image2.newData != null) { + this.image = image2.newData + } + this.env.postExecution(image2) - continuation = image2.continuation - lastResolutionType = image2.resolutionType - try { - performSideEffects(info, image2.sideEffects) - } catch (e: Exception) { - e.printStackTrace() - performSideEffects(info, listOf(OperatorSideEffect.DoMishap(MishapInternalException(e), Mishap.Context(null, null)))) - } - info.earlyExit = info.earlyExit || !lastResolutionType.success - } + continuation = image2.continuation + lastResolutionType = image2.resolutionType + try { + performSideEffects(info, image2.sideEffects) + } catch (e: Exception) { + e.printStackTrace() + performSideEffects( + info, + listOf( + OperatorSideEffect.DoMishap(MishapInternalException(e), Mishap.Context(null, null)) + ) + ) + } + info.earlyExit = info.earlyExit || !lastResolutionType.success + } - if (continuation is SpellContinuation.NotDone) { - lastResolutionType = - if (lastResolutionType.success) ResolvedPatternType.EVALUATED else ResolvedPatternType.ERRORED - } + if (continuation is SpellContinuation.NotDone) { + lastResolutionType = + if (lastResolutionType.success) ResolvedPatternType.EVALUATED + else ResolvedPatternType.ERRORED + } - val (stackDescs, ravenmind) = generateDescs() + val (stackDescs, ravenmind) = generateDescs() - val isStackClear = image.stack.isEmpty() && image.parenCount == 0 && !image.escapeNext && ravenmind == null + val isStackClear = + image.stack.isEmpty() && image.parenCount == 0 && !image.escapeNext && ravenmind == null - this.env.postCast(image) - return ExecutionClientView(isStackClear, lastResolutionType, stackDescs, ravenmind) - } + this.env.postCast(image) + return ExecutionClientView(isStackClear, lastResolutionType, stackDescs, ravenmind) + } - /** - * this DOES NOT THROW THINGS - */ - @Throws() - fun executeInner(iota: Iota, world: ServerLevel, continuation: SpellContinuation): CastResult { - try { - // TODO we can have a special intro/retro sound - // ALSO TODO need to add reader macro-style things - try { - this.handleParentheses(iota)?.let { (data, resolutionType) -> - return@executeInner CastResult(iota, continuation, data, listOf(), resolutionType, HexEvalSounds.NORMAL_EXECUTE) - } - } catch (e: MishapTooManyCloseParens) { - // This is ridiculous and needs to be fixed - return CastResult( - iota, - continuation, - null, - listOf( - OperatorSideEffect.DoMishap( - e, - Mishap.Context( - (iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST), - HexAPI.instance().getRawHookI18n(HexAPI.modLoc("close_paren")) - ) - ) - ), - ResolvedPatternType.ERRORED, - HexEvalSounds.MISHAP - ) - } + /** this DOES NOT THROW THINGS */ + @Throws() + fun executeInner(iota: Iota, world: ServerLevel, continuation: SpellContinuation): CastResult { + try { + // TODO we can have a special intro/retro sound + // ALSO TODO need to add reader macro-style things + try { + this.handleParentheses(iota)?.let { (data, resolutionType) -> + return@executeInner CastResult( + iota, + continuation, + data, + listOf(), + resolutionType, + HexEvalSounds.NORMAL_EXECUTE + ) + } + } catch (e: MishapTooManyCloseParens) { + // This is ridiculous and needs to be fixed + return CastResult( + iota, + continuation, + null, + listOf( + OperatorSideEffect.DoMishap( + e, + Mishap.Context( + (iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST), + HexAPI.instance().getRawHookI18n(HexAPI.modLoc("close_paren")) + ) + ) + ), + ResolvedPatternType.ERRORED, + HexEvalSounds.MISHAP + ) + } - return iota.execute(this, world, continuation) - } catch (exception: Exception) { - // This means something very bad has happened - exception.printStackTrace() - return CastResult( - iota, - continuation, - null, - listOf( - OperatorSideEffect.DoMishap( - MishapInternalException(exception), - Mishap.Context( - (iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST), - null - ) - ) - ), - ResolvedPatternType.ERRORED, - HexEvalSounds.MISHAP - ) - } - } + return iota.execute(this, world, continuation) + } catch (exception: Exception) { + // This means something very bad has happened + exception.printStackTrace() + return CastResult( + iota, + continuation, + null, + listOf( + OperatorSideEffect.DoMishap( + MishapInternalException(exception), + Mishap.Context((iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST), null) + ) + ), + ResolvedPatternType.ERRORED, + HexEvalSounds.MISHAP + ) + } + } - /** - * Execute the side effects of a pattern, updating our aggregated info. - */ - fun performSideEffects(info: TempControllerInfo, sideEffects: List) { - for (haskellProgrammersShakingandCryingRN in sideEffects) { - val mustStop = haskellProgrammersShakingandCryingRN.performEffect(this) - if (mustStop) { - info.earlyExit = true - break - } - } - } + /** Execute the side effects of a pattern, updating our aggregated info. */ + fun performSideEffects(info: TempControllerInfo, sideEffects: List) { + for (haskellProgrammersShakingandCryingRN in sideEffects) { + val mustStop = haskellProgrammersShakingandCryingRN.performEffect(this) + if (mustStop) { + info.earlyExit = true + break + } + } + } - fun generateDescs(): Pair, CompoundTag?> { - val stackDescs = this.image.stack.map { IotaType.serialize(it) } - val ravenmind = if (this.image.userData.contains(HexAPI.RAVENMIND_USERDATA)) { - this.image.userData.getCompound(HexAPI.RAVENMIND_USERDATA) - } else null - return Pair(stackDescs, ravenmind) - } + fun generateDescs(): Pair, CompoundTag?> { + val stackDescs = this.image.stack.map { IotaType.serialize(it) } + val ravenmind = + if (this.image.userData.contains(HexAPI.RAVENMIND_USERDATA)) { + this.image.userData.getCompound(HexAPI.RAVENMIND_USERDATA) + } else null + return Pair(stackDescs, ravenmind) + } - /** - * Return a non-null value if we handled this in some sort of parenthesey way, - * either escaping it onto the stack or changing the parenthese-handling state. - */ - @Throws(MishapTooManyCloseParens::class) - private fun handleParentheses(iota: Iota): Pair? { - val sig = (iota as? PatternIota)?.pattern?.angles + /** + * Return a non-null value if we handled this in some sort of parenthesey way, either escaping it + * onto the stack or changing the parenthese-handling state. + */ + @Throws(MishapTooManyCloseParens::class) + private fun handleParentheses(iota: Iota): Pair? { + val sig = (iota as? PatternIota)?.pattern?.angles - var displayDepth = this.image.parenCount + var displayDepth = this.image.parenCount - val out = if (displayDepth > 0) { - if (this.image.escapeNext) { - val newParens = this.image.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, true)) - this.image.copy( - escapeNext = false, - parenthesized = newParens - ) to ResolvedPatternType.ESCAPED - } else { + val out = + if (displayDepth > 0) { + if (this.image.escapeNext) { + val newParens = this.image.parenthesized.toMutableList() + newParens.add(ParenthesizedIota(iota, true)) + this.image.copy(escapeNext = false, parenthesized = newParens) to + ResolvedPatternType.ESCAPED + } else { - when (sig) { - SpecialPatterns.CONSIDERATION.angles -> { - this.image.copy( - escapeNext = true, - ) to ResolvedPatternType.EVALUATED - } + when (sig) { + SpecialPatterns.CONSIDERATION.angles -> { + this.image.copy( + escapeNext = true, + ) to ResolvedPatternType.EVALUATED + } + SpecialPatterns.EVANITION.angles -> { + val newParens = this.image.parenthesized.toMutableList() + val last = newParens.removeLastOrNull() + val newParenCount = + this.image.parenCount + + if (last == null || last.escaped || last.iota !is PatternIota) 0 + else + when (last.iota.pattern) { + SpecialPatterns.INTROSPECTION -> -1 + SpecialPatterns.RETROSPECTION -> 1 + else -> 0 + } + this.image.copy(parenthesized = newParens, parenCount = newParenCount) to + if (last == null) ResolvedPatternType.ERRORED else ResolvedPatternType.UNDONE + } + SpecialPatterns.INTROSPECTION.angles -> { + // we have escaped the parens onto the stack; we just also record our count. + val newParens = this.image.parenthesized.toMutableList() + newParens.add(ParenthesizedIota(iota, false)) + this.image.copy(parenthesized = newParens, parenCount = this.image.parenCount + 1) to + if (this.image.parenCount == 0) ResolvedPatternType.EVALUATED + else ResolvedPatternType.ESCAPED + } + SpecialPatterns.RETROSPECTION.angles -> { + val newParenCount = this.image.parenCount - 1 + displayDepth-- + if (newParenCount == 0) { + val newStack = this.image.stack.toMutableList() + newStack.add(ListIota(this.image.parenthesized.toList().map { it.iota })) + this.image.copy( + stack = newStack, + parenCount = newParenCount, + parenthesized = listOf() + ) to ResolvedPatternType.EVALUATED + } else if (newParenCount < 0) { + throw MishapTooManyCloseParens() + } else { + // we have this situation: "(()" + // we need to add the close paren + val newParens = this.image.parenthesized.toMutableList() + newParens.add(ParenthesizedIota(iota, false)) + this.image.copy(parenCount = newParenCount, parenthesized = newParens) to + ResolvedPatternType.ESCAPED + } + } + else -> { + val newParens = this.image.parenthesized.toMutableList() + newParens.add(ParenthesizedIota(iota, false)) + this.image.copy(parenthesized = newParens) to ResolvedPatternType.ESCAPED + } + } + } + } else if (this.image.escapeNext) { + val newStack = this.image.stack.toMutableList() + newStack.add(iota) + this.image.copy( + stack = newStack, + escapeNext = false, + ) to ResolvedPatternType.ESCAPED + } else { + when (sig) { + SpecialPatterns.CONSIDERATION.angles -> { + this.image.copy(escapeNext = true) to ResolvedPatternType.EVALUATED + } + SpecialPatterns.INTROSPECTION.angles -> { + this.image.copy(parenCount = this.image.parenCount + 1) to ResolvedPatternType.EVALUATED + } + SpecialPatterns.RETROSPECTION.angles -> { + throw MishapTooManyCloseParens() + } + else -> { + null + } + } + } - SpecialPatterns.EVANITION.angles -> { - val newParens = this.image.parenthesized.toMutableList() - val last = newParens.removeLastOrNull() - val newParenCount = this.image.parenCount + if (last == null || last.escaped || last.iota !is PatternIota) 0 else when (last.iota.pattern) { - SpecialPatterns.INTROSPECTION -> -1 - SpecialPatterns.RETROSPECTION -> 1 - else -> 0 - } - this.image.copy(parenthesized = newParens, parenCount = newParenCount) to if (last == null) ResolvedPatternType.ERRORED else ResolvedPatternType.UNDONE - } + // TODO: replace this once we can read things from the client + /* + if (out != null) { + val display = if (iota is PatternIota) { + PatternNameHelper.representationForPattern(iota.pattern) + .copy() + .withStyle(if (out.second == ResolvedPatternType.ESCAPED) ChatFormatting.YELLOW else ChatFormatting.AQUA) + } else iota.display() + displayPatternDebug(this.escapeNext, displayDepth, display) + } + */ + return out + } - SpecialPatterns.INTROSPECTION.angles -> { - // we have escaped the parens onto the stack; we just also record our count. - val newParens = this.image.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, false)) - this.image.copy( - parenthesized = newParens, - parenCount = this.image.parenCount + 1 - ) to if (this.image.parenCount == 0) ResolvedPatternType.EVALUATED else ResolvedPatternType.ESCAPED - } + data class TempControllerInfo( + var earlyExit: Boolean, + ) - SpecialPatterns.RETROSPECTION.angles -> { - val newParenCount = this.image.parenCount - 1 - displayDepth-- - if (newParenCount == 0) { - val newStack = this.image.stack.toMutableList() - newStack.add(ListIota(this.image.parenthesized.toList().map { it.iota })) - this.image.copy( - stack = newStack, - parenCount = newParenCount, - parenthesized = listOf() - ) to ResolvedPatternType.EVALUATED - } else if (newParenCount < 0) { - throw MishapTooManyCloseParens() - } else { - // we have this situation: "(()" - // we need to add the close paren - val newParens = this.image.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, false)) - this.image.copy( - parenCount = newParenCount, - parenthesized = newParens - ) to ResolvedPatternType.ESCAPED - } - } - - else -> { - val newParens = this.image.parenthesized.toMutableList() - newParens.add(ParenthesizedIota(iota, false)) - this.image.copy( - parenthesized = newParens - ) to ResolvedPatternType.ESCAPED - } - } - } - } else if (this.image.escapeNext) { - val newStack = this.image.stack.toMutableList() - newStack.add(iota) - this.image.copy( - stack = newStack, - escapeNext = false, - ) to ResolvedPatternType.ESCAPED - } else { - when (sig) { - SpecialPatterns.CONSIDERATION.angles -> { - this.image.copy( - escapeNext = true - ) to ResolvedPatternType.EVALUATED - } - - SpecialPatterns.INTROSPECTION.angles -> { - this.image.copy( - parenCount = this.image.parenCount + 1 - ) to ResolvedPatternType.EVALUATED - } - - SpecialPatterns.RETROSPECTION.angles -> { - throw MishapTooManyCloseParens() - } - - else -> { - null - } - } - } - - // TODO: replace this once we can read things from the client - /* - if (out != null) { - val display = if (iota is PatternIota) { - PatternNameHelper.representationForPattern(iota.pattern) - .copy() - .withStyle(if (out.second == ResolvedPatternType.ESCAPED) ChatFormatting.YELLOW else ChatFormatting.AQUA) - } else iota.display() - displayPatternDebug(this.escapeNext, displayDepth, display) - } - */ - return out - } - - data class TempControllerInfo( - var earlyExit: Boolean, - ) - - companion object { - @JvmStatic - fun empty(env: CastingEnvironment): CastingVM { - return CastingVM(CastingImage(), env) - } - } + companion object { + @JvmStatic + fun empty(env: CastingEnvironment): CastingVM { + return CastingVM(CastingImage(), env) + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/ContinuationFrame.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/ContinuationFrame.kt index 8000ec09aa..a3b4cebed9 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/ContinuationFrame.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/ContinuationFrame.kt @@ -12,97 +12,102 @@ import net.minecraft.server.level.ServerLevel /** * A single frame of evaluation during the execution of a spell. * - * Specifically, an evaluation will keep a stack of these frames. - * An evaluation with no meta-eval will consist of a single [Evaluate(rest of the pats)] at all times. - * When an Eval is invoked, we push Evaluate(pats) to the top of the stack. + * Specifically, an evaluation will keep a stack of these frames. An evaluation with no meta-eval + * will consist of a single [Evaluate(rest of the pats)] at all times. When an Eval is invoked, we + * push Evaluate(pats) to the top of the stack. * * Evaluation is performed by repeatedly popping the top-most (i.e. innermost) frame from the stack, - * then evaluating that frame (and possibly allowing it to push other frames (e.g. if it's a Hermes)). + * then evaluating that frame (and possibly allowing it to push other frames (e.g. if it's a + * Hermes)). * * Once the stack of frames is empty, there are no more computations to run, so we're done. - * */ interface ContinuationFrame { - /** - * Step the evaluation forward once. - * For Evaluate, this consumes one pattern; for ForEach this queues the next iteration of the outer loop. - * @return the result of this pattern step - */ - fun evaluate(continuation: SpellContinuation, level: ServerLevel, harness: CastingVM): CastResult + /** + * Step the evaluation forward once. For Evaluate, this consumes one pattern; for ForEach this + * queues the next iteration of the outer loop. + * + * @return the result of this pattern step + */ + fun evaluate(continuation: SpellContinuation, level: ServerLevel, harness: CastingVM): CastResult - /** - * The OpHalt instruction wants us to "jump to" the END of the nearest meta-eval. - * In other words, we should consume Evaluate frames until we hit a FinishEval or Thoth frame. - * @return whether the break should stop here, alongside the new stack state (e.g. for finalizing a Thoth) - */ - fun breakDownwards(stack: List): Pair> + /** + * The OpHalt instruction wants us to "jump to" the END of the nearest meta-eval. In other words, + * we should consume Evaluate frames until we hit a FinishEval or Thoth frame. + * + * @return whether the break should stop here, alongside the new stack state (e.g. for finalizing + * a Thoth) + */ + fun breakDownwards(stack: List): Pair> - /** - * Serializes this frame. Used for things like delays, where we pause execution. - */ - fun serializeToNBT(): CompoundTag + /** Serializes this frame. Used for things like delays, where we pause execution. */ + fun serializeToNBT(): CompoundTag - /** - * Return the number of iotas contained inside this frame, used for determining whether it is valid to serialise. - */ - fun size(): Int + /** + * Return the number of iotas contained inside this frame, used for determining whether it is + * valid to serialise. + */ + fun size(): Int - val type: Type<*> + val type: Type<*> - interface Type { - fun deserializeFromNBT(tag: CompoundTag, world: ServerLevel): U? - } + interface Type { + fun deserializeFromNBT(tag: CompoundTag, world: ServerLevel): U? + } - companion object { - /** - * Takes a tag containing the ContinuationFrame.Type resourcelocation and the serialized continuation frame, and returns - * the deserialized continuation frame. - */ - @JvmStatic - fun fromNBT(tag: CompoundTag, world: ServerLevel): ContinuationFrame { - val type = getTypeFromTag(tag) ?: return FrameEvaluate(SpellList.LList(0, listOf()), false) + companion object { + /** + * Takes a tag containing the ContinuationFrame.Type resourcelocation and the serialized + * continuation frame, and returns the deserialized continuation frame. + */ + @JvmStatic + fun fromNBT(tag: CompoundTag, world: ServerLevel): ContinuationFrame { + val type = getTypeFromTag(tag) ?: return FrameEvaluate(SpellList.LList(0, listOf()), false) - return (tag.get(HexContinuationTypes.KEY_DATA) as? CompoundTag)?.let { type.deserializeFromNBT(it, world) } - ?: FrameEvaluate(SpellList.LList(0, listOf()), false) - } + return (tag.get(HexContinuationTypes.KEY_DATA) as? CompoundTag)?.let { + type.deserializeFromNBT(it, world) + } ?: FrameEvaluate(SpellList.LList(0, listOf()), false) + } - /** - * Takes a continuation frame and serializes it along with its type. - */ - @JvmStatic - fun toNBT(frame: ContinuationFrame): CompoundTag { - val type = frame.type - val typeId = HexContinuationTypes.REGISTRY.getKey(type) - ?: throw IllegalStateException( - "Tried to serialize an unregistered continuation type. Continuation: " + frame - + " ; Type" + type.javaClass.typeName) + /** Takes a continuation frame and serializes it along with its type. */ + @JvmStatic + fun toNBT(frame: ContinuationFrame): CompoundTag { + val type = frame.type + val typeId = + HexContinuationTypes.REGISTRY.getKey(type) + ?: throw IllegalStateException( + "Tried to serialize an unregistered continuation type. Continuation: " + + frame + + " ; Type" + + type.javaClass.typeName + ) - val data = frame.serializeToNBT() + val data = frame.serializeToNBT() - val out = CompoundTag() - out.putString(HexContinuationTypes.KEY_TYPE, typeId.toString()) - out.put(HexContinuationTypes.KEY_DATA, data) - return out - } + val out = CompoundTag() + out.putString(HexContinuationTypes.KEY_TYPE, typeId.toString()) + out.put(HexContinuationTypes.KEY_DATA, data) + return out + } - /** - * This method attempts to find the type from the `type` key. - * See [ContinuationFrame.serializeToNBT] for the storage format. - * - * @return `null` if it cannot get the type. - */ - private fun getTypeFromTag(tag: CompoundTag): Type<*>? { - if (!tag.contains(HexContinuationTypes.KEY_TYPE, Tag.TAG_STRING.toInt())) { - return null - } + /** + * This method attempts to find the type from the `type` key. See + * [ContinuationFrame.serializeToNBT] for the storage format. + * + * @return `null` if it cannot get the type. + */ + private fun getTypeFromTag(tag: CompoundTag): Type<*>? { + if (!tag.contains(HexContinuationTypes.KEY_TYPE, Tag.TAG_STRING.toInt())) { + return null + } - val typeKey = tag.getString(HexContinuationTypes.KEY_TYPE) - if (!ResourceLocation.isValidResourceLocation(typeKey)) { - return null - } + val typeKey = tag.getString(HexContinuationTypes.KEY_TYPE) + if (!ResourceLocation.isValidResourceLocation(typeKey)) { + return null + } - val typeLoc = ResourceLocation(typeKey) - return HexContinuationTypes.REGISTRY[typeLoc] - } - } + val typeLoc = ResourceLocation(typeKey) + return HexContinuationTypes.REGISTRY[typeLoc] + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt index 141148dfdc..174579bab6 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameEvaluate.kt @@ -16,59 +16,66 @@ import net.minecraft.server.level.ServerLevel /** * A list of patterns to be evaluated in sequence. + * * @property list the *remaining* list of patterns to be evaluated * @property isMetacasting only for sound effects, if this is being cast from a hermes / iris */ data class FrameEvaluate(val list: SpellList, val isMetacasting: Boolean) : ContinuationFrame { - // Discard this frame and keep discarding frames. - override fun breakDownwards(stack: List) = false to stack + // Discard this frame and keep discarding frames. + override fun breakDownwards(stack: List) = false to stack - // Step the list of patterns, evaluating a single one. - override fun evaluate( - continuation: SpellContinuation, - level: ServerLevel, - harness: CastingVM - ): CastResult { - // If there are patterns left... - return if (list.nonEmpty) { - val newCont = if (list.cdr.nonEmpty) { // yay TCO - // ...enqueue the evaluation of the rest of the patterns... - continuation.pushFrame(FrameEvaluate(list.cdr, this.isMetacasting)) - } else continuation - // ...before evaluating the first one in the list. - val update = harness.executeInner(list.car, level, newCont) - if (this.isMetacasting && update.sound != HexEvalSounds.MISHAP) { - update.copy(sound = HexEvalSounds.HERMES) - } else { - update - } - } else { - // If there are no patterns (e.g. empty Hermes), just return OK. - CastResult(ListIota(list), continuation, null, listOf(), ResolvedPatternType.EVALUATED, HexEvalSounds.HERMES) - } - } + // Step the list of patterns, evaluating a single one. + override fun evaluate( + continuation: SpellContinuation, + level: ServerLevel, + harness: CastingVM + ): CastResult { + // If there are patterns left... + return if (list.nonEmpty) { + val newCont = + if (list.cdr.nonEmpty) { // yay TCO + // ...enqueue the evaluation of the rest of the patterns... + continuation.pushFrame(FrameEvaluate(list.cdr, this.isMetacasting)) + } else continuation + // ...before evaluating the first one in the list. + val update = harness.executeInner(list.car, level, newCont) + if (this.isMetacasting && update.sound != HexEvalSounds.MISHAP) { + update.copy(sound = HexEvalSounds.HERMES) + } else { + update + } + } else { + // If there are no patterns (e.g. empty Hermes), just return OK. + CastResult( + ListIota(list), + continuation, + null, + listOf(), + ResolvedPatternType.EVALUATED, + HexEvalSounds.HERMES + ) + } + } - override fun serializeToNBT() = NBTBuilder { - "patterns" %= list.serializeToNBT() - "isMetacasting" %= isMetacasting - } + override fun serializeToNBT() = NBTBuilder { + "patterns" %= list.serializeToNBT() + "isMetacasting" %= isMetacasting + } - override fun size() = list.size() + override fun size() = list.size() - override val type: ContinuationFrame.Type<*> = TYPE + override val type: ContinuationFrame.Type<*> = TYPE - companion object { - @JvmField - val TYPE: ContinuationFrame.Type = object : ContinuationFrame.Type { - override fun deserializeFromNBT(tag: CompoundTag, world: ServerLevel): FrameEvaluate { - return FrameEvaluate( - HexIotaTypes.LIST.deserialize( - tag.getList("patterns", Tag.TAG_COMPOUND), - world - )!!.list, - tag.getBoolean("isMetacasting")) - } - - } - } -} \ No newline at end of file + companion object { + @JvmField + val TYPE: ContinuationFrame.Type = + object : ContinuationFrame.Type { + override fun deserializeFromNBT(tag: CompoundTag, world: ServerLevel): FrameEvaluate { + return FrameEvaluate( + HexIotaTypes.LIST.deserialize(tag.getList("patterns", Tag.TAG_COMPOUND), world)!!.list, + tag.getBoolean("isMetacasting") + ) + } + } + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameFinishEval.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameFinishEval.kt index 72bbb8c80c..f9c54090fa 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameFinishEval.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameFinishEval.kt @@ -4,43 +4,43 @@ import at.petrak.hexcasting.api.casting.eval.CastResult import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.NullIota -import at.petrak.hexcasting.api.utils.NBTBuilder import at.petrak.hexcasting.common.lib.hex.HexEvalSounds import net.minecraft.nbt.CompoundTag import net.minecraft.server.level.ServerLevel /** - * A stack marker representing the end of a Hermes evaluation, - * so that we know when to stop removing frames during a Halt. + * A stack marker representing the end of a Hermes evaluation, so that we know when to stop removing + * frames during a Halt. */ object FrameFinishEval : ContinuationFrame { - // Don't do anything else to the stack, just finish the halt statement. - override fun breakDownwards(stack: List) = true to stack + // Don't do anything else to the stack, just finish the halt statement. + override fun breakDownwards(stack: List) = true to stack - // Evaluating it does nothing; it's only a boundary condition. - override fun evaluate( - continuation: SpellContinuation, - level: ServerLevel, - harness: CastingVM - ): CastResult { - return CastResult( - NullIota(), - continuation, - null, - listOf(), - ResolvedPatternType.EVALUATED, - HexEvalSounds.NOTHING, - ) - } + // Evaluating it does nothing; it's only a boundary condition. + override fun evaluate( + continuation: SpellContinuation, + level: ServerLevel, + harness: CastingVM + ): CastResult { + return CastResult( + NullIota(), + continuation, + null, + listOf(), + ResolvedPatternType.EVALUATED, + HexEvalSounds.NOTHING, + ) + } - override fun serializeToNBT() = CompoundTag() + override fun serializeToNBT() = CompoundTag() - override fun size() = 0 + override fun size() = 0 - @JvmField - val TYPE: ContinuationFrame.Type = object : ContinuationFrame.Type { - override fun deserializeFromNBT(tag: CompoundTag, world: ServerLevel) = FrameFinishEval - } + @JvmField + val TYPE: ContinuationFrame.Type = + object : ContinuationFrame.Type { + override fun deserializeFromNBT(tag: CompoundTag, world: ServerLevel) = FrameFinishEval + } - override val type = TYPE + override val type = TYPE } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt index 0221c1f36d..9e8e8e8c31 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt @@ -16,101 +16,107 @@ import net.minecraft.nbt.Tag import net.minecraft.server.level.ServerLevel /** - * A frame representing all the state for a Thoth evaluation. - * Pushed by an OpForEach. - * @property first whether the input stack state is the first one (since we don't want to save the base-stack before any changes are made) + * A frame representing all the state for a Thoth evaluation. Pushed by an OpForEach. + * + * @property first whether the input stack state is the first one (since we don't want to save the + * base-stack before any changes are made) * @property data list of *remaining* datums to ForEach over * @property code code to run per datum * @property baseStack the stack state at Thoth entry * @property acc concatenated list of final stack states after Thoth exit */ data class FrameForEach( - val data: SpellList, - val code: SpellList, - val baseStack: List?, - val acc: MutableList + val data: SpellList, + val code: SpellList, + val baseStack: List?, + val acc: MutableList ) : ContinuationFrame { - /** When halting, we add the stack state at halt to the stack accumulator, then return the original pre-Thoth stack, plus the accumulator. */ - override fun breakDownwards(stack: List): Pair> { - val newStack = baseStack?.toMutableList() ?: mutableListOf() - acc.addAll(stack) - newStack.add(ListIota(acc)) - return true to newStack - } + /** + * When halting, we add the stack state at halt to the stack accumulator, then return the original + * pre-Thoth stack, plus the accumulator. + */ + override fun breakDownwards(stack: List): Pair> { + val newStack = baseStack?.toMutableList() ?: mutableListOf() + acc.addAll(stack) + newStack.add(ListIota(acc)) + return true to newStack + } - /** Step the Thoth computation, enqueueing one code evaluation. */ - override fun evaluate( - continuation: SpellContinuation, - level: ServerLevel, - harness: CastingVM - ): CastResult { - // If this isn't the very first Thoth step (i.e. no Thoth computations run yet)... - val stack = if (baseStack == null) { - // init stack to the harness stack... - harness.image.stack.toList() - } else { - // else save the stack to the accumulator and reuse the saved base stack. - acc.addAll(harness.image.stack) - baseStack - } + /** Step the Thoth computation, enqueueing one code evaluation. */ + override fun evaluate( + continuation: SpellContinuation, + level: ServerLevel, + harness: CastingVM + ): CastResult { + // If this isn't the very first Thoth step (i.e. no Thoth computations run yet)... + val stack = + if (baseStack == null) { + // init stack to the harness stack... + harness.image.stack.toList() + } else { + // else save the stack to the accumulator and reuse the saved base stack. + acc.addAll(harness.image.stack) + baseStack + } - // If we still have data to process... - val (stackTop, newImage, newCont) = if (data.nonEmpty) { - // push the next datum to the top of the stack, - val cont2 = continuation - // put the next Thoth object back on the stack for the next Thoth cycle, - .pushFrame(FrameForEach(data.cdr, code, stack, acc)) - // and prep the Thoth'd code block for evaluation. - .pushFrame(FrameEvaluate(code, true)) - Triple(data.car, harness.image.withUsedOp(), cont2) - } else { - // Else, dump our final list onto the stack. - Triple(ListIota(acc), harness.image, continuation) - } - val tStack = stack.toMutableList() - tStack.add(stackTop) - return CastResult( - ListIota(code), - newCont, - // reset escapes so they don't carry over to other iterations or out of thoth - newImage.withResetEscape().copy(stack = tStack), - listOf(), - ResolvedPatternType.EVALUATED, - HexEvalSounds.THOTH, - ) - } + // If we still have data to process... + val (stackTop, newImage, newCont) = + if (data.nonEmpty) { + // push the next datum to the top of the stack, + val cont2 = + continuation + // put the next Thoth object back on the stack for the next Thoth cycle, + .pushFrame(FrameForEach(data.cdr, code, stack, acc)) + // and prep the Thoth'd code block for evaluation. + .pushFrame(FrameEvaluate(code, true)) + Triple(data.car, harness.image.withUsedOp(), cont2) + } else { + // Else, dump our final list onto the stack. + Triple(ListIota(acc), harness.image, continuation) + } + val tStack = stack.toMutableList() + tStack.add(stackTop) + return CastResult( + ListIota(code), + newCont, + // reset escapes so they don't carry over to other iterations or out of thoth + newImage.withResetEscape().copy(stack = tStack), + listOf(), + ResolvedPatternType.EVALUATED, + HexEvalSounds.THOTH, + ) + } - override fun serializeToNBT() = NBTBuilder { - "data" %= data.serializeToNBT() - "code" %= code.serializeToNBT() - if (baseStack != null) - "base" %= baseStack.serializeToNBT() - "accumulator" %= acc.serializeToNBT() - } + override fun serializeToNBT() = NBTBuilder { + "data" %= data.serializeToNBT() + "code" %= code.serializeToNBT() + if (baseStack != null) "base" %= baseStack.serializeToNBT() + "accumulator" %= acc.serializeToNBT() + } - override fun size() = data.size() + code.size() + acc.size + (baseStack?.size ?: 0) + override fun size() = data.size() + code.size() + acc.size + (baseStack?.size ?: 0) - override val type: ContinuationFrame.Type<*> = TYPE + override val type: ContinuationFrame.Type<*> = TYPE - companion object { - @JvmField - val TYPE: ContinuationFrame.Type = object : ContinuationFrame.Type { - override fun deserializeFromNBT(tag: CompoundTag, world: ServerLevel): FrameForEach { - return FrameForEach( - HexIotaTypes.LIST.deserialize(tag.getList("data", Tag.TAG_COMPOUND), world)!!.list, - HexIotaTypes.LIST.deserialize(tag.getList("code", Tag.TAG_COMPOUND), world)!!.list, - if (tag.hasList("base", Tag.TAG_COMPOUND)) - HexIotaTypes.LIST.deserialize(tag.getList("base", Tag.TAG_COMPOUND), world)!!.list.toList() - else - null, - HexIotaTypes.LIST.deserialize( - tag.getList("accumulator", Tag.TAG_COMPOUND), - world - )!!.list.toMutableList() - ) - } - - } - } + companion object { + @JvmField + val TYPE: ContinuationFrame.Type = + object : ContinuationFrame.Type { + override fun deserializeFromNBT(tag: CompoundTag, world: ServerLevel): FrameForEach { + return FrameForEach( + HexIotaTypes.LIST.deserialize(tag.getList("data", Tag.TAG_COMPOUND), world)!!.list, + HexIotaTypes.LIST.deserialize(tag.getList("code", Tag.TAG_COMPOUND), world)!!.list, + if (tag.hasList("base", Tag.TAG_COMPOUND)) + HexIotaTypes.LIST.deserialize(tag.getList("base", Tag.TAG_COMPOUND), world)!! + .list + .toList() + else null, + HexIotaTypes.LIST.deserialize(tag.getList("accumulator", Tag.TAG_COMPOUND), world)!! + .list + .toMutableList() + ) + } + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FunctionalData.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FunctionalData.kt index 49f3d149d0..ad6794a2da 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FunctionalData.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FunctionalData.kt @@ -2,14 +2,11 @@ package at.petrak.hexcasting.api.casting.eval.vm import at.petrak.hexcasting.api.casting.iota.Iota -/** - * A change to the data in a CastHarness after a pattern is drawn. - */ +/** A change to the data in a CastHarness after a pattern is drawn. */ data class FunctionalData( - val stack: List, - val parenCount: Int, - val parenthesized: List, - val escapeNext: Boolean, - val ravenmind: Iota? + val stack: List, + val parenCount: Int, + val parenthesized: List, + val escapeNext: Boolean, + val ravenmind: Iota? ) - diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/SpellContinuation.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/SpellContinuation.kt index ec706c2858..67a0381d6f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/SpellContinuation.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/SpellContinuation.kt @@ -6,41 +6,39 @@ import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.Tag import net.minecraft.server.level.ServerLevel -/** - * A continuation during the execution of a spell. - */ +/** A continuation during the execution of a spell. */ sealed interface SpellContinuation { - object Done : SpellContinuation - - data class NotDone(val frame: ContinuationFrame, val next: SpellContinuation) : SpellContinuation - - fun pushFrame(frame: ContinuationFrame): SpellContinuation = NotDone(frame, this) - - fun serializeToNBT() = NBTBuilder { - TAG_FRAME %= list(getNBTFrames()) - } - fun getNBTFrames(): List { - var self = this - val frames = mutableListOf() - while (self is NotDone) { - frames.add(ContinuationFrame.toNBT(self.frame)) - self = self.next - } - return frames - } - companion object { - const val TAG_FRAME = "frame" - - @JvmStatic - fun fromNBT(nbt: CompoundTag, world: ServerLevel): SpellContinuation { - val frames = nbt.getList(TAG_FRAME, Tag.TAG_COMPOUND) - var result: SpellContinuation = Done - for (frame in frames.asReversed()) { - if (frame is CompoundTag) { - result = result.pushFrame(ContinuationFrame.fromNBT(frame, world)) - } - } - return result - } - } + object Done : SpellContinuation + + data class NotDone(val frame: ContinuationFrame, val next: SpellContinuation) : SpellContinuation + + fun pushFrame(frame: ContinuationFrame): SpellContinuation = NotDone(frame, this) + + fun serializeToNBT() = NBTBuilder { TAG_FRAME %= list(getNBTFrames()) } + + fun getNBTFrames(): List { + var self = this + val frames = mutableListOf() + while (self is NotDone) { + frames.add(ContinuationFrame.toNBT(self.frame)) + self = self.next + } + return frames + } + + companion object { + const val TAG_FRAME = "frame" + + @JvmStatic + fun fromNBT(nbt: CompoundTag, world: ServerLevel): SpellContinuation { + val frames = nbt.getList(TAG_FRAME, Tag.TAG_COMPOUND) + var result: SpellContinuation = Done + for (frame in frames.asReversed()) { + if (frame is CompoundTag) { + result = result.pushFrame(ContinuationFrame.fromNBT(frame, world)) + } + } + return result + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/BooleanIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/BooleanIota.java index 91cf0b339d..8f875593f8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/BooleanIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/BooleanIota.java @@ -11,58 +11,57 @@ import org.jetbrains.annotations.Nullable; public class BooleanIota extends Iota { - public BooleanIota(boolean d) { - super(HexIotaTypes.BOOLEAN, d); - } + public BooleanIota(boolean d) { + super(HexIotaTypes.BOOLEAN, d); + } - public boolean getBool() { - return (boolean) this.payload; - } + public boolean getBool() { + return (boolean) this.payload; + } - @Override - public boolean isTruthy() { - return this.getBool(); - } + @Override + public boolean isTruthy() { + return this.getBool(); + } - @Override - public boolean toleratesOther(Iota that) { - return typesMatch(this, that) - && that instanceof BooleanIota b - && this.getBool() == b.getBool(); - } + @Override + public boolean toleratesOther(Iota that) { + return typesMatch(this, that) && that instanceof BooleanIota b && this.getBool() == b.getBool(); + } - @Override - public @NotNull Tag serialize() { - // there is no boolean tag :( - return ByteTag.valueOf(this.getBool()); - } + @Override + public @NotNull Tag serialize() { + // there is no boolean tag :( + return ByteTag.valueOf(this.getBool()); + } - public static IotaType TYPE = new IotaType<>() { - @Nullable - @Override - public BooleanIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { - return BooleanIota.deserialize(tag); - } + public static IotaType TYPE = + new IotaType<>() { + @Nullable @Override + public BooleanIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { + return BooleanIota.deserialize(tag); + } - @Override - public Component display(Tag tag) { - return BooleanIota.display(BooleanIota.deserialize(tag).getBool()); - } + @Override + public Component display(Tag tag) { + return BooleanIota.display(BooleanIota.deserialize(tag).getBool()); + } - @Override - public int color() { - // We can't set red or green ... so do yellow, I guess - return 0xff_ffff55; - } - }; + @Override + public int color() { + // We can't set red or green ... so do yellow, I guess + return 0xff_ffff55; + } + }; - public static BooleanIota deserialize(Tag tag) throws IllegalArgumentException { - var dtag = HexUtils.downcast(tag, ByteTag.TYPE); - return new BooleanIota(dtag.getAsByte() != 0); - } + public static BooleanIota deserialize(Tag tag) throws IllegalArgumentException { + var dtag = HexUtils.downcast(tag, ByteTag.TYPE); + return new BooleanIota(dtag.getAsByte() != 0); + } - public static Component display(boolean b) { - return Component.translatable(b ? "hexcasting.tooltip.boolean_true" : "hexcasting.tooltip.boolean_false") - .withStyle(b ? ChatFormatting.DARK_GREEN : ChatFormatting.DARK_RED); - } + public static Component display(boolean b) { + return Component.translatable( + b ? "hexcasting.tooltip.boolean_true" : "hexcasting.tooltip.boolean_false") + .withStyle(b ? ChatFormatting.DARK_GREEN : ChatFormatting.DARK_RED); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ContinuationIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ContinuationIota.java index 13764c4675..cd1ba9458f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ContinuationIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ContinuationIota.java @@ -7,6 +7,7 @@ import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.common.lib.hex.HexEvalSounds; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import java.util.List; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; @@ -14,76 +15,83 @@ import net.minecraft.server.level.ServerLevel; import org.jetbrains.annotations.NotNull; -import java.util.List; - -/** - * An iota storing a continuation (in essence an execution state). - */ +/** An iota storing a continuation (in essence an execution state). */ public class ContinuationIota extends Iota { - public static final Component DISPLAY = Component.translatable("hexcasting.tooltip.jump_iota").withStyle(ChatFormatting.RED); + public static final Component DISPLAY = + Component.translatable("hexcasting.tooltip.jump_iota").withStyle(ChatFormatting.RED); - public ContinuationIota(SpellContinuation cont) { - super(HexIotaTypes.CONTINUATION, cont); - } + public ContinuationIota(SpellContinuation cont) { + super(HexIotaTypes.CONTINUATION, cont); + } - public SpellContinuation getContinuation() { - return (SpellContinuation) this.payload; - } + public SpellContinuation getContinuation() { + return (SpellContinuation) this.payload; + } - @Override - public boolean isTruthy() { - return true; - } + @Override + public boolean isTruthy() { + return true; + } - @Override - public boolean toleratesOther(Iota that) { - return typesMatch(this, that) && that instanceof ContinuationIota cont && cont.getContinuation().equals(getContinuation()); - } + @Override + public boolean toleratesOther(Iota that) { + return typesMatch(this, that) + && that instanceof ContinuationIota cont + && cont.getContinuation().equals(getContinuation()); + } - @Override - public @NotNull - Tag serialize() { - return getContinuation().serializeToNBT(); - } + @Override + public @NotNull Tag serialize() { + return getContinuation().serializeToNBT(); + } - @Override - public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) { - return new CastResult(this, this.getContinuation(), vm.getImage(), List.of(), ResolvedPatternType.EVALUATED, HexEvalSounds.HERMES); - } + @Override + public @NotNull CastResult execute( + CastingVM vm, ServerLevel world, SpellContinuation continuation) { + return new CastResult( + this, + this.getContinuation(), + vm.getImage(), + List.of(), + ResolvedPatternType.EVALUATED, + HexEvalSounds.HERMES); + } - @Override - public boolean executable() { - return true; - } + @Override + public boolean executable() { + return true; + } - @Override - public int size() { - var continuation = this.getContinuation(); - var size = 0; - while (continuation instanceof SpellContinuation.NotDone notDone) { - size += 1; - size += notDone.component1().size(); - continuation = notDone.component2(); - } + @Override + public int size() { + var continuation = this.getContinuation(); + var size = 0; + while (continuation instanceof SpellContinuation.NotDone notDone) { + size += 1; + size += notDone.component1().size(); + continuation = notDone.component2(); + } - return Math.min(size, 1); - } + return Math.min(size, 1); + } - public static IotaType TYPE = new IotaType<>() { - @Override - public @NotNull ContinuationIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { - var compoundTag = HexUtils.downcast(tag, CompoundTag.TYPE); - return new ContinuationIota(SpellContinuation.fromNBT(compoundTag, world)); - } + public static IotaType TYPE = + new IotaType<>() { + @Override + public @NotNull ContinuationIota deserialize(Tag tag, ServerLevel world) + throws IllegalArgumentException { + var compoundTag = HexUtils.downcast(tag, CompoundTag.TYPE); + return new ContinuationIota(SpellContinuation.fromNBT(compoundTag, world)); + } - @Override - public Component display(Tag tag) { - return DISPLAY; - } + @Override + public Component display(Tag tag) { + return DISPLAY; + } - @Override - public int color() { - return 0xff_cc0000; - } - }; + @Override + public int color() { + return 0xff_cc0000; + } + }; } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/DoubleIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/DoubleIota.java index e9ff20f57c..43e6831cb7 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/DoubleIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/DoubleIota.java @@ -11,61 +11,61 @@ import org.jetbrains.annotations.Nullable; public class DoubleIota extends Iota { - public static final double TOLERANCE = 0.0001; + public static final double TOLERANCE = 0.0001; - public DoubleIota(double d) { - super(HexIotaTypes.DOUBLE, d); - } + public DoubleIota(double d) { + super(HexIotaTypes.DOUBLE, d); + } - public double getDouble() { - return HexUtils.fixNAN((Double) this.payload); - } + public double getDouble() { + return HexUtils.fixNAN((Double) this.payload); + } - @Override - public boolean isTruthy() { - return this.getDouble() != 0.0; - } + @Override + public boolean isTruthy() { + return this.getDouble() != 0.0; + } - @Override - public boolean toleratesOther(Iota that) { - return typesMatch(this, that) - && that instanceof DoubleIota dd - && tolerates(this.getDouble(), dd.getDouble()); - } + @Override + public boolean toleratesOther(Iota that) { + return typesMatch(this, that) + && that instanceof DoubleIota dd + && tolerates(this.getDouble(), dd.getDouble()); + } - public static boolean tolerates(double a, double b) { - return Math.abs(a - b) < TOLERANCE; - } + public static boolean tolerates(double a, double b) { + return Math.abs(a - b) < TOLERANCE; + } - @Override - public @NotNull Tag serialize() { - return DoubleTag.valueOf(this.getDouble()); - } + @Override + public @NotNull Tag serialize() { + return DoubleTag.valueOf(this.getDouble()); + } - public static IotaType TYPE = new IotaType<>() { - @Nullable - @Override - public DoubleIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { - return DoubleIota.deserialize(tag); - } + public static IotaType TYPE = + new IotaType<>() { + @Nullable @Override + public DoubleIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { + return DoubleIota.deserialize(tag); + } - @Override - public Component display(Tag tag) { - return DoubleIota.display(DoubleIota.deserialize(tag).getDouble()); - } + @Override + public Component display(Tag tag) { + return DoubleIota.display(DoubleIota.deserialize(tag).getDouble()); + } - @Override - public int color() { - return 0xff_55ff55; - } - }; + @Override + public int color() { + return 0xff_55ff55; + } + }; - public static DoubleIota deserialize(Tag tag) throws IllegalArgumentException { - var dtag = HexUtils.downcast(tag, DoubleTag.TYPE); - return new DoubleIota(dtag.getAsDouble()); - } + public static DoubleIota deserialize(Tag tag) throws IllegalArgumentException { + var dtag = HexUtils.downcast(tag, DoubleTag.TYPE); + return new DoubleIota(dtag.getAsDouble()); + } - public static Component display(double d) { - return Component.literal(String.format("%.2f", d)).withStyle(ChatFormatting.GREEN); - } + public static Component display(double d) { + return Component.literal(String.format("%.2f", d)).withStyle(ChatFormatting.GREEN); + } } 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 2a8023cf01..1680081206 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 @@ -18,89 +18,91 @@ import org.jetbrains.annotations.Nullable; public class EntityIota extends Iota { - public EntityIota(@NotNull Entity e) { - super(HexIotaTypes.ENTITY, e); - } + public EntityIota(@NotNull Entity e) { + super(HexIotaTypes.ENTITY, e); + } - public Entity getEntity() { - return (Entity) this.payload; - } + public Entity getEntity() { + return (Entity) this.payload; + } - @Override - public boolean toleratesOther(Iota that) { - return typesMatch(this, that) - && that instanceof EntityIota dent - && this.getEntity() == dent.getEntity(); - } + @Override + public boolean toleratesOther(Iota that) { + return typesMatch(this, that) + && that instanceof EntityIota dent + && this.getEntity() == dent.getEntity(); + } - @Override - public boolean isTruthy() { - return true; - } + @Override + public boolean isTruthy() { + return true; + } - @Override - public @NotNull - Tag serialize() { - var out = new CompoundTag(); - out.putUUID("uuid", this.getEntity().getUUID()); - out.putString("name", Component.Serializer.toJson(getEntityNameWithInline(true))); - return out; - } + @Override + public @NotNull Tag serialize() { + var out = new CompoundTag(); + out.putUUID("uuid", this.getEntity().getUUID()); + out.putString("name", Component.Serializer.toJson(getEntityNameWithInline(true))); + return out; + } - @Override - public Component display() { - return getEntityNameWithInline(false).copy().withStyle(ChatFormatting.AQUA); - } + @Override + public Component display() { + 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); - } + 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<>() { - @Nullable - @Override - public EntityIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { - var ctag = HexUtils.downcast(tag, CompoundTag.TYPE); - Tag uuidTag = ctag.get("uuid"); - if (uuidTag == null) { - return null; - } - var uuid = NbtUtils.loadUUID(uuidTag); - var entity = world.getEntity(uuid); - if (entity == null) { - return null; - } - return new EntityIota(entity); - } + public static IotaType TYPE = + new IotaType<>() { + @Nullable @Override + public EntityIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { + var ctag = HexUtils.downcast(tag, CompoundTag.TYPE); + Tag uuidTag = ctag.get("uuid"); + if (uuidTag == null) { + return null; + } + var uuid = NbtUtils.loadUUID(uuidTag); + var entity = world.getEntity(uuid); + if (entity == null) { + return null; + } + return new EntityIota(entity); + } - @Override - public Component display(Tag tag) { - if (!(tag instanceof CompoundTag ctag)) { - return Component.translatable("hexcasting.spelldata.entity.whoknows"); - } - if (!ctag.contains("name", Tag.TAG_STRING)) { - return Component.translatable("hexcasting.spelldata.entity.whoknows"); - } - var nameJson = ctag.getString("name"); -// return Component.literal(nameJson); - return Component.Serializer.fromJsonLenient(nameJson).withStyle(ChatFormatting.AQUA); - } + @Override + public Component display(Tag tag) { + if (!(tag instanceof CompoundTag ctag)) { + return Component.translatable("hexcasting.spelldata.entity.whoknows"); + } + if (!ctag.contains("name", Tag.TAG_STRING)) { + return Component.translatable("hexcasting.spelldata.entity.whoknows"); + } + var nameJson = ctag.getString("name"); + // return Component.literal(nameJson); + return Component.Serializer.fromJsonLenient(nameJson).withStyle(ChatFormatting.AQUA); + } - @Override - public int color() { - return 0xff_55ffff; - } - }; + @Override + public int color() { + return 0xff_55ffff; + } + }; } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/GarbageIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/GarbageIota.java index 0824985852..2cc9d2cb43 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/GarbageIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/GarbageIota.java @@ -1,6 +1,7 @@ package at.petrak.hexcasting.api.casting.iota; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import java.util.Random; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; @@ -9,55 +10,55 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Random; - /** - * this is LITERALLY a copy of NullIota but I can't see how to do it any better, i hate java generics + * this is LITERALLY a copy of NullIota but I can't see how to do it any better, i hate java + * generics */ public class GarbageIota extends Iota { - private static final Object NULL_SUBSTITUTE = new Object(); - - public static final Component DISPLAY = Component.literal("arimfexendrapuse") - .withStyle(ChatFormatting.DARK_GRAY, ChatFormatting.OBFUSCATED); - - private static final Random RANDOM = new Random(); - - public GarbageIota() { - // We have to pass *something* here, but there's nothing that actually needs to go there, - // so we just do this i guess - super(HexIotaTypes.GARBAGE, NULL_SUBSTITUTE); - } - - @Override - public boolean isTruthy() { - return false; - } - - @Override - public boolean toleratesOther(Iota that) { - return typesMatch(this, that); - } - - @Override - public @NotNull Tag serialize() { - return new CompoundTag(); - } - - public static IotaType TYPE = new IotaType<>() { - @Nullable - @Override - public GarbageIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { - return new GarbageIota(); - } - - @Override - public Component display(Tag tag) { - return DISPLAY; - } - - @Override - public int color() { - return 0xff_505050; - } - }; + private static final Object NULL_SUBSTITUTE = new Object(); + + public static final Component DISPLAY = + Component.literal("arimfexendrapuse") + .withStyle(ChatFormatting.DARK_GRAY, ChatFormatting.OBFUSCATED); + + private static final Random RANDOM = new Random(); + + public GarbageIota() { + // We have to pass *something* here, but there's nothing that actually needs to go there, + // so we just do this i guess + super(HexIotaTypes.GARBAGE, NULL_SUBSTITUTE); + } + + @Override + public boolean isTruthy() { + return false; + } + + @Override + public boolean toleratesOther(Iota that) { + return typesMatch(this, that); + } + + @Override + public @NotNull Tag serialize() { + return new CompoundTag(); + } + + public static IotaType TYPE = + new IotaType<>() { + @Nullable @Override + public GarbageIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { + return new GarbageIota(); + } + + @Override + public Component display(Tag tag) { + return DISPLAY; + } + + @Override + public int color() { + return 0xff_505050; + } + }; } 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..68732125ab 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 @@ -11,113 +11,105 @@ import at.petrak.hexcasting.api.casting.mishaps.MishapUnescapedValue; import at.petrak.hexcasting.common.lib.hex.HexEvalSounds; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import java.util.List; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; - public abstract class Iota { - @NotNull - protected final Object payload; - @NotNull - protected final IotaType type; - - protected Iota(@NotNull IotaType type, @NotNull Object payload) { - this.type = type; - this.payload = payload; - } - - public @NotNull IotaType getType() { - return this.type; - } - - abstract public boolean isTruthy(); - - /** - * Compare this to another object, within a tolerance. - */ - abstract protected boolean toleratesOther(Iota that); - - /** - * Serialize this under the {@code data} tag. - *

- * You probably don't want to call this directly; use {@link IotaType#serialize}. - */ - abstract public @NotNull Tag serialize(); - - /** - * This method is called when this iota is executed (i.e. Hermes is run on a list containing it, unescaped). - * By default it will return a {@link CastResult} indicating an error has occurred. - */ - public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) { - return new CastResult( - this, - continuation, - null, // Should never matter - List.of( - new OperatorSideEffect.DoMishap( - new MishapUnescapedValue(this), - new Mishap.Context(new HexPattern(HexDir.WEST, List.of()), null) - ) - ), - ResolvedPatternType.INVALID, - HexEvalSounds.MISHAP); - } - - /** - * Returns whether this iota is possible to execute (i.e. whether {@link Iota#execute} has been overridden. - */ - public boolean executable() { - return false; - } - - /** - * This method is called to determine whether the iota is above the max serialisation depth/serialisation count - * limits. It should return every "iota" that is a subelement of this iota. - * For example, if you implemented a Map<Iota, Iota>, then it should be an iterable over the keys *and* - * values of the map. If you implemented a typed List<Double> iota for some reason, you should instead override - * {@link Iota#size}. - */ - public @Nullable Iterable subIotas() { - return null; - } - - /** - * This method is called to determine whether the iota is above the max serialisation depth/serialisation count limits. - * This is an alternative to deriving subIotas for if your Iota is a datastructure of variable size over something that - * doesn't make sense to convert to an Iota iterable, such as {@link ContinuationIota}, or a typed List<Double>. - * It should return "1" per "iota sized" unit of memory that it would occupy. Easy option, return the element count of - * your data structure. - */ - public int size() { - return 1; - } - - public Component display() { - return this.type.display(this.serialize()); - } - - /** - * Helper method to see if two iotas have the same type. - */ - public static boolean typesMatch(Iota a, Iota b) { - var resA = HexIotaTypes.REGISTRY.getKey(a.getType()); - var resB = HexIotaTypes.REGISTRY.getKey(b.getType()); - return resA != null && resA.equals(resB); - } - - /** - * Helper method to see if either iota tolerates the other. - */ - public static boolean tolerates(Iota a, Iota b) { - return a.toleratesOther(b) || b.toleratesOther(a); - } - - @Override - public int hashCode() { - return payload.hashCode(); - } + @NotNull protected final Object payload; + @NotNull protected final IotaType type; + + protected Iota(@NotNull IotaType type, @NotNull Object payload) { + this.type = type; + this.payload = payload; + } + + public @NotNull IotaType getType() { + return this.type; + } + + public abstract boolean isTruthy(); + + /** Compare this to another object, within a tolerance. */ + protected abstract boolean toleratesOther(Iota that); + + /** + * Serialize this under the {@code data} tag. + * + *

You probably don't want to call this directly; use {@link IotaType#serialize}. + */ + public abstract @NotNull Tag serialize(); + + /** + * This method is called when this iota is executed (i.e. Hermes is run on a list containing it, + * unescaped). By default it will return a {@link CastResult} indicating an error has occurred. + */ + public @NotNull CastResult execute( + CastingVM vm, ServerLevel world, SpellContinuation continuation) { + return new CastResult( + this, + continuation, + null, // Should never matter + List.of( + new OperatorSideEffect.DoMishap( + new MishapUnescapedValue(this), + new Mishap.Context(new HexPattern(HexDir.WEST, List.of()), null))), + ResolvedPatternType.INVALID, + HexEvalSounds.MISHAP); + } + + /** + * Returns whether this iota is possible to execute (i.e. whether {@link Iota#execute} has been + * overridden. + */ + public boolean executable() { + return false; + } + + /** + * This method is called to determine whether the iota is above the max serialisation + * depth/serialisation count limits. It should return every "iota" that is a subelement of this + * iota. For example, if you implemented a Map<Iota, Iota>, then it should be an iterable + * over the keys *and* values of the map. If you implemented a typed List<Double> iota for + * some reason, you should instead override {@link Iota#size}. + */ + public @Nullable Iterable subIotas() { + return null; + } + + /** + * This method is called to determine whether the iota is above the max serialisation + * depth/serialisation count limits. This is an alternative to deriving subIotas for if your Iota + * is a datastructure of variable size over something that doesn't make sense to convert to an + * Iota iterable, such as {@link ContinuationIota}, or a typed List<Double>. It should + * return "1" per "iota sized" unit of memory that it would occupy. Easy option, return the + * element count of your data structure. + */ + public int size() { + return 1; + } + + public Component display() { + return this.type.display(this.serialize()); + } + + /** Helper method to see if two iotas have the same type. */ + public static boolean typesMatch(Iota a, Iota b) { + var resA = HexIotaTypes.REGISTRY.getKey(a.getType()); + var resB = HexIotaTypes.REGISTRY.getKey(b.getType()); + return resA != null && resA.equals(resB); + } + + /** Helper method to see if either iota tolerates the other. */ + public static boolean tolerates(Iota a, Iota b) { + return a.toleratesOther(b) || b.toleratesOther(a); + } + + @Override + public int hashCode() { + return payload.hashCode(); + } } 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..c8b3b165de 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 @@ -4,6 +4,11 @@ import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import com.mojang.datafixers.util.Pair; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.Font; import net.minecraft.nbt.CompoundTag; @@ -14,195 +19,182 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.util.FormattedCharSequence; -import javax.annotation.Nullable; -import java.util.ArrayDeque; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - // Take notes from ForgeRegistryEntry public abstract class IotaType { - /** - * Spell datums are stored as such: {@code { "type": "modid:type", "datum": a_tag }}. - *

- * The {@code type} key is given when registering the spell datum type; this method - * deserializes the tag associated with {@code "datum"}. - *

- * Returning {@code null} makes the resulting datum be {@link NullIota}. - * Throwing an exception raises a mishap. - */ - @Nullable - public abstract T deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException; - - /** - * Get a display of this datum from the {@code data} tag, without the world. - * This is for use on the client. - */ - public abstract Component display(Tag tag); - - /** - * Get the color associated with this datum type. - */ - public abstract int color(); - - /** - * Get a display component that's the name of this iota type. - */ - public Component typeName() { - var key = HexIotaTypes.REGISTRY.getKey(this); - return Component.translatable("hexcasting.iota." + key) - .withStyle(style -> style.withColor(TextColor.fromRgb(color()))); - } - - public static CompoundTag serialize(Iota iota) { - var type = iota.getType(); - var typeId = HexIotaTypes.REGISTRY.getKey(type); - if (typeId == null) { - throw new IllegalStateException( - "Tried to serialize an unregistered iota type. Iota: " + iota - + " ; Type" + type.getClass().getTypeName()); - } - - // We check if it's too big on serialization; if it is we just return a garbage. - if (isTooLargeToSerialize(List.of(iota), 0)) { - // Garbage will never be too large so we just recurse - return serialize(new GarbageIota()); - } - var dataTag = iota.serialize(); - var out = new CompoundTag(); - out.putString(HexIotaTypes.KEY_TYPE, typeId.toString()); - out.put(HexIotaTypes.KEY_DATA, dataTag); - return out; - } - - public static boolean isTooLargeToSerialize(Iterable examinee) { - return isTooLargeToSerialize(examinee, 1); - } - - 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)); - } - } - } - // we made it! - return false; - } - - /** - * This method attempts to find the type from the {@code type} key. - * See {@link IotaType#serialize(Iota)} for the storage format. - * - * @return {@code null} if it cannot get the type. - */ - @org.jetbrains.annotations.Nullable - public static IotaType getTypeFromTag(CompoundTag tag) { - if (!tag.contains(HexIotaTypes.KEY_TYPE, Tag.TAG_STRING)) { - return null; - } - var typeKey = tag.getString(HexIotaTypes.KEY_TYPE); - if (!ResourceLocation.isValidResourceLocation(typeKey)) { - return null; - } - var typeLoc = new ResourceLocation(typeKey); - return HexIotaTypes.REGISTRY.get(typeLoc); - } - - /** - * Attempt to deserialize an iota from a tag. - *
- * Iotas are saved as such: - * - * { - * "type": "hexcasting:atype", - * "data": {...} - * } - * - */ - public static Iota deserialize(CompoundTag tag, ServerLevel world) { - var type = getTypeFromTag(tag); - if (type == null) { - return new GarbageIota(); - } - var data = tag.get(HexIotaTypes.KEY_DATA); - if (data == null) { - return new GarbageIota(); - } - Iota deserialized; - try { - deserialized = Objects.requireNonNullElse(type.deserialize(data, world), new NullIota()); - } catch (IllegalArgumentException exn) { - HexAPI.LOGGER.warn("Caught an exception deserializing an iota", exn); - deserialized = new GarbageIota(); - } - return deserialized; - } - - private static Component brokenIota() { - return Component.translatable("hexcasting.spelldata.unknown") - .withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC); - } - - public static Component getDisplay(CompoundTag tag) { - var type = getTypeFromTag(tag); - if (type == null) { - return brokenIota(); - } - var data = tag.get(HexIotaTypes.KEY_DATA); - if (data == null) { - return brokenIota(); - } - return type.display(data); - } - - public static FormattedCharSequence getDisplayWithMaxWidth(CompoundTag tag, int maxWidth, Font font) { - var type = getTypeFromTag(tag); - if (type == null) { - return brokenIota().getVisualOrderText(); - } - var data = tag.get(HexIotaTypes.KEY_DATA); - if (data == null) { - return brokenIota().getVisualOrderText(); - } - var display = type.display(data); - var splitted = font.split(display, maxWidth - font.width("...")); - if (splitted.isEmpty()) - return FormattedCharSequence.EMPTY; - else if (splitted.size() == 1) - return splitted.get(0); - else { - var first = splitted.get(0); - return FormattedCharSequence.fromPair(first, - Component.literal("...").withStyle(ChatFormatting.GRAY).getVisualOrderText()); - } - } - - public static int getColor(CompoundTag tag) { - var type = getTypeFromTag(tag); - if (type == null) { - return HexUtils.ERROR_COLOR; - } - return type.color(); - } + /** + * Spell datums are stored as such: {@code { "type": "modid:type", "datum": a_tag }}. + * + *

The {@code type} key is given when registering the spell datum type; this method + * deserializes the tag associated with {@code "datum"}. + * + *

Returning {@code null} makes the resulting datum be {@link NullIota}. Throwing an exception + * raises a mishap. + */ + @Nullable public abstract T deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException; + + /** + * Get a display of this datum from the {@code data} tag, without the world. This is for + * use on the client. + */ + public abstract Component display(Tag tag); + + /** Get the color associated with this datum type. */ + public abstract int color(); + + /** Get a display component that's the name of this iota type. */ + public Component typeName() { + var key = HexIotaTypes.REGISTRY.getKey(this); + return Component.translatable("hexcasting.iota." + key) + .withStyle(style -> style.withColor(TextColor.fromRgb(color()))); + } + + public static CompoundTag serialize(Iota iota) { + var type = iota.getType(); + var typeId = HexIotaTypes.REGISTRY.getKey(type); + if (typeId == null) { + throw new IllegalStateException( + "Tried to serialize an unregistered iota type. Iota: " + + iota + + " ; Type" + + type.getClass().getTypeName()); + } + + // We check if it's too big on serialization; if it is we just return a garbage. + if (isTooLargeToSerialize(List.of(iota), 0)) { + // Garbage will never be too large so we just recurse + return serialize(new GarbageIota()); + } + var dataTag = iota.serialize(); + var out = new CompoundTag(); + out.putString(HexIotaTypes.KEY_TYPE, typeId.toString()); + out.put(HexIotaTypes.KEY_DATA, dataTag); + return out; + } + + public static boolean isTooLargeToSerialize(Iterable examinee) { + return isTooLargeToSerialize(examinee, 1); + } + + 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)); + } + } + } + // we made it! + return false; + } + + /** + * This method attempts to find the type from the {@code type} key. See {@link + * IotaType#serialize(Iota)} for the storage format. + * + * @return {@code null} if it cannot get the type. + */ + @org.jetbrains.annotations.Nullable public static IotaType getTypeFromTag(CompoundTag tag) { + if (!tag.contains(HexIotaTypes.KEY_TYPE, Tag.TAG_STRING)) { + return null; + } + var typeKey = tag.getString(HexIotaTypes.KEY_TYPE); + if (!ResourceLocation.isValidResourceLocation(typeKey)) { + return null; + } + var typeLoc = new ResourceLocation(typeKey); + return HexIotaTypes.REGISTRY.get(typeLoc); + } + + /** + * Attempt to deserialize an iota from a tag.
+ * Iotas are saved as such: + * { + * "type": "hexcasting:atype", + * "data": {...} + * } + * + */ + public static Iota deserialize(CompoundTag tag, ServerLevel world) { + var type = getTypeFromTag(tag); + if (type == null) { + return new GarbageIota(); + } + var data = tag.get(HexIotaTypes.KEY_DATA); + if (data == null) { + return new GarbageIota(); + } + Iota deserialized; + try { + deserialized = Objects.requireNonNullElse(type.deserialize(data, world), new NullIota()); + } catch (IllegalArgumentException exn) { + HexAPI.LOGGER.warn("Caught an exception deserializing an iota", exn); + deserialized = new GarbageIota(); + } + return deserialized; + } + + private static Component brokenIota() { + return Component.translatable("hexcasting.spelldata.unknown") + .withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC); + } + + public static Component getDisplay(CompoundTag tag) { + var type = getTypeFromTag(tag); + if (type == null) { + return brokenIota(); + } + var data = tag.get(HexIotaTypes.KEY_DATA); + if (data == null) { + return brokenIota(); + } + return type.display(data); + } + + public static FormattedCharSequence getDisplayWithMaxWidth( + CompoundTag tag, int maxWidth, Font font) { + var type = getTypeFromTag(tag); + if (type == null) { + return brokenIota().getVisualOrderText(); + } + var data = tag.get(HexIotaTypes.KEY_DATA); + if (data == null) { + return brokenIota().getVisualOrderText(); + } + var display = type.display(data); + var splitted = font.split(display, maxWidth - font.width("...")); + if (splitted.isEmpty()) return FormattedCharSequence.EMPTY; + else if (splitted.size() == 1) return splitted.get(0); + else { + var first = splitted.get(0); + return FormattedCharSequence.fromPair( + first, Component.literal("...").withStyle(ChatFormatting.GRAY).getVisualOrderText()); + } + } + + public static int getColor(CompoundTag tag) { + var type = getTypeFromTag(tag); + if (type == null) { + return HexUtils.ERROR_COLOR; + } + return type.color(); + } } 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 251c4e161d..9b24e45beb 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 @@ -3,6 +3,8 @@ import at.petrak.hexcasting.api.casting.SpellList; import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import java.util.ArrayList; +import java.util.List; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; @@ -12,114 +14,113 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.List; - -/** - * This is a wrapper for {@link SpellList}. - */ +/** This is a wrapper for {@link SpellList}. */ public class ListIota extends Iota { - public ListIota(@NotNull SpellList list) { - super(HexIotaTypes.LIST, list); - } - - public ListIota(@NotNull List list) { - this(new SpellList.LList(list)); - } - - public SpellList getList() { - return (SpellList) this.payload; - } - - @Override - public boolean isTruthy() { - return this.getList().getNonEmpty(); - } - - @Override - public boolean toleratesOther(Iota that) { - if (!typesMatch(this, that)) { - return false; - } - var a = this.getList(); - if (!(that instanceof ListIota list)) { - return false; - } - var b = list.getList(); - - SpellList.SpellListIterator aIter = a.iterator(), bIter = b.iterator(); - for (; ; ) { - if (!aIter.hasNext() && !bIter.hasNext()) { - // we ran out together! - return true; - } - if (aIter.hasNext() != bIter.hasNext()) { - // one remains full before the other - return false; - } - Iota x = aIter.next(), y = bIter.next(); - if (!Iota.tolerates(x, y)) { - return false; - } - } - } - - @Override - public @NotNull Tag serialize() { - var out = new ListTag(); - for (var subdatum : this.getList()) { - out.add(IotaType.serialize(subdatum)); - } - return out; - } - - @Override - public @Nullable Iterable subIotas() { - return this.getList(); - } - - public static IotaType TYPE = new IotaType<>() { - @Nullable - @Override - public ListIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { - var listTag = HexUtils.downcast(tag, ListTag.TYPE); - var out = new ArrayList(listTag.size()); - - for (var sub : listTag) { - var csub = HexUtils.downcast(sub, CompoundTag.TYPE); - var subiota = IotaType.deserialize(csub, world); - if (subiota == null) { - return null; - } - out.add(subiota); - } - - return new ListIota(out); - } - - @Override - public Component display(Tag tag) { - var out = Component.empty(); - var list = HexUtils.downcast(tag, ListTag.TYPE); - for (int i = 0; i < list.size(); i++) { - Tag sub = list.get(i); - var csub = HexUtils.downcast(sub, CompoundTag.TYPE); - - out.append(IotaType.getDisplay(csub)); - - // 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(", "); - } - } - return Component.translatable("hexcasting.tooltip.list_contents", out).withStyle(ChatFormatting.DARK_PURPLE); - } - - @Override - public int color() { - return 0xff_aa00aa; - } - }; + public ListIota(@NotNull SpellList list) { + super(HexIotaTypes.LIST, list); + } + + public ListIota(@NotNull List list) { + this(new SpellList.LList(list)); + } + + public SpellList getList() { + return (SpellList) this.payload; + } + + @Override + public boolean isTruthy() { + return this.getList().getNonEmpty(); + } + + @Override + public boolean toleratesOther(Iota that) { + if (!typesMatch(this, that)) { + return false; + } + var a = this.getList(); + if (!(that instanceof ListIota list)) { + return false; + } + var b = list.getList(); + + SpellList.SpellListIterator aIter = a.iterator(), bIter = b.iterator(); + for (; ; ) { + if (!aIter.hasNext() && !bIter.hasNext()) { + // we ran out together! + return true; + } + if (aIter.hasNext() != bIter.hasNext()) { + // one remains full before the other + return false; + } + Iota x = aIter.next(), y = bIter.next(); + if (!Iota.tolerates(x, y)) { + return false; + } + } + } + + @Override + public @NotNull Tag serialize() { + var out = new ListTag(); + for (var subdatum : this.getList()) { + out.add(IotaType.serialize(subdatum)); + } + return out; + } + + @Override + public @Nullable Iterable subIotas() { + return this.getList(); + } + + public static IotaType TYPE = + new IotaType<>() { + @Nullable @Override + public ListIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { + var listTag = HexUtils.downcast(tag, ListTag.TYPE); + var out = new ArrayList(listTag.size()); + + for (var sub : listTag) { + var csub = HexUtils.downcast(sub, CompoundTag.TYPE); + var subiota = IotaType.deserialize(csub, world); + if (subiota == null) { + return null; + } + out.add(subiota); + } + + return new ListIota(out); + } + + @Override + public Component display(Tag tag) { + var out = Component.empty(); + var list = HexUtils.downcast(tag, ListTag.TYPE); + for (int i = 0; i < list.size(); i++) { + Tag sub = list.get(i); + var csub = HexUtils.downcast(sub, CompoundTag.TYPE); + + out.append(IotaType.getDisplay(csub)); + + // 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(", "); + } + } + return Component.translatable("hexcasting.tooltip.list_contents", out) + .withStyle(ChatFormatting.DARK_PURPLE); + } + + @Override + public int color() { + return 0xff_aa00aa; + } + }; } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/NullIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/NullIota.java index 525e6b22a9..581b248521 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/NullIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/NullIota.java @@ -9,51 +9,49 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** - * An iota with no data associated with it. - */ +/** An iota with no data associated with it. */ public class NullIota extends Iota { - private static final Object NULL_SUBSTITUTE = new Object(); - - public static final Component DISPLAY = - Component.translatable("hexcasting.tooltip.null_iota").withStyle(ChatFormatting.GRAY); - - public NullIota() { - // We have to pass *something* here, but there's nothing that actually needs to go there, - // so we just do this i guess - super(HexIotaTypes.NULL, NULL_SUBSTITUTE); - } - - @Override - public boolean isTruthy() { - return false; - } - - @Override - public boolean toleratesOther(Iota that) { - return typesMatch(this, that); - } - - @Override - public @NotNull Tag serialize() { - return new CompoundTag(); - } - - public static IotaType TYPE = new IotaType<>() { - @Nullable - @Override - public NullIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { - return new NullIota(); - } - - @Override - public Component display(Tag tag) { - return DISPLAY; - } - - @Override - public int color() { - return 0xff_aaaaaa; - } - }; + private static final Object NULL_SUBSTITUTE = new Object(); + + public static final Component DISPLAY = + Component.translatable("hexcasting.tooltip.null_iota").withStyle(ChatFormatting.GRAY); + + public NullIota() { + // We have to pass *something* here, but there's nothing that actually needs to go there, + // so we just do this i guess + super(HexIotaTypes.NULL, NULL_SUBSTITUTE); + } + + @Override + public boolean isTruthy() { + return false; + } + + @Override + public boolean toleratesOther(Iota that) { + return typesMatch(this, that); + } + + @Override + public @NotNull Tag serialize() { + return new CompoundTag(); + } + + public static IotaType TYPE = + new IotaType<>() { + @Nullable @Override + public NullIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { + return new NullIota(); + } + + @Override + public Component display(Tag tag) { + return DISPLAY; + } + + @Override + public int color() { + return 0xff_aaaaaa; + } + }; } 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 b86e8541ce..47cf64f1fa 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 @@ -1,5 +1,7 @@ package at.petrak.hexcasting.api.casting.iota; +import static at.petrak.hexcasting.api.utils.HexUtils.isOfTag; + import at.petrak.hexcasting.api.HexAPI; import at.petrak.hexcasting.api.casting.ActionRegistryEntry; import at.petrak.hexcasting.api.casting.PatternShapeMatch; @@ -21,6 +23,9 @@ import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import at.petrak.hexcasting.interop.inline.InlinePatternData; import at.petrak.hexcasting.xplat.IXplatAbstractions; +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; @@ -31,155 +36,157 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; -import java.util.Objects; -import java.util.function.Supplier; - -import static at.petrak.hexcasting.api.utils.HexUtils.isOfTag; - public class PatternIota extends Iota { - public PatternIota(@NotNull HexPattern pattern) { - super(HexIotaTypes.PATTERN, pattern); - } - - public HexPattern getPattern() { - return (HexPattern) this.payload; - } - - protected PatternIota(@NotNull IotaType type, @NotNull Object payload) { - super(type, payload); - } - - @Override - public boolean isTruthy() { - return true; - } - - @Override - public boolean toleratesOther(Iota that) { - return typesMatch(this, that) - && that instanceof PatternIota piota - && this.getPattern().getAngles().equals(piota.getPattern().getAngles()); - } - - @Override - public @NotNull Tag serialize() { - return this.getPattern().serializeToNBT(); - } - - @Override - public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) { - Supplier<@Nullable Component> castedName = () -> null; - try { - var lookup = PatternRegistryManifest.matchPattern(this.getPattern(), vm.getEnv(), false); - vm.getEnv().precheckAction(lookup); - - Action action; - if (lookup instanceof PatternShapeMatch.Normal || lookup instanceof PatternShapeMatch.PerWorld) { - ResourceKey key; - if (lookup instanceof PatternShapeMatch.Normal normal) { - key = normal.key; - } else { - PatternShapeMatch.PerWorld perWorld = (PatternShapeMatch.PerWorld) lookup; - key = perWorld.key; - } - - var reqsEnlightenment = isOfTag(IXplatAbstractions.INSTANCE.getActionRegistry(), key, - HexTags.Actions.REQUIRES_ENLIGHTENMENT); - - castedName = () -> HexAPI.instance().getActionI18n(key, reqsEnlightenment); - action = Objects.requireNonNull(IXplatAbstractions.INSTANCE.getActionRegistry().get(key)).action(); - - if (reqsEnlightenment && !vm.getEnv().isEnlightened()) { - // this gets caught down below - throw new MishapUnenlightened(); - } - } else if (lookup instanceof PatternShapeMatch.Special special) { - castedName = special.handler::getName; - action = special.handler.act(); - } else if (lookup instanceof PatternShapeMatch.Nothing) { - throw new MishapInvalidPattern(); - } else throw new IllegalStateException(); - - // do the actual calculation!! - var result = action.operate( - vm.getEnv(), - vm.getImage(), - continuation - ); - - if (result.getNewImage().getOpsConsumed() > vm.getEnv().maxOpCount()) { - throw new MishapEvalTooMuch(); - } - - var cont2 = result.getNewContinuation(); - // TODO parens also break prescience - var sideEffects = result.getSideEffects(); - - return new CastResult( - this, - cont2, - result.getNewImage(), - sideEffects, - ResolvedPatternType.EVALUATED, - result.getSound()); - - } catch (Mishap mishap) { - return new CastResult( - this, - continuation, - null, - List.of(new OperatorSideEffect.DoMishap(mishap, new Mishap.Context(this.getPattern(), castedName.get()))), - mishap.resolutionType(vm.getEnv()), - HexEvalSounds.MISHAP); - } - } - - @Override - public boolean executable() { - return true; - } - - public static IotaType TYPE = new IotaType<>() { - @Override - public PatternIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { - return PatternIota.deserialize(tag); - } - - @Override - public Component display(Tag tag) { - return PatternIota.display(PatternIota.deserialize(tag).getPattern()); - } - - @Override - public int color() { - return 0xff_ffaa00; - } - }; - - public static PatternIota deserialize(Tag tag) throws IllegalArgumentException { - var patTag = HexUtils.downcast(tag, CompoundTag.TYPE); - HexPattern pat = HexPattern.fromNBT(patTag); - return new PatternIota(pat); - } - - 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()); - - var sig = pat.anglesSignature(); - if (!sig.isEmpty()) { - bob.append(" "); - bob.append(sig); - } - return Component.translatable("hexcasting.tooltip.pattern_iota", - Component.literal(bob.toString()).withStyle(ChatFormatting.WHITE)) - .withStyle(ChatFormatting.GOLD); - } + public PatternIota(@NotNull HexPattern pattern) { + super(HexIotaTypes.PATTERN, pattern); + } + + public HexPattern getPattern() { + return (HexPattern) this.payload; + } + + protected PatternIota(@NotNull IotaType type, @NotNull Object payload) { + super(type, payload); + } + + @Override + public boolean isTruthy() { + return true; + } + + @Override + public boolean toleratesOther(Iota that) { + return typesMatch(this, that) + && that instanceof PatternIota piota + && this.getPattern().getAngles().equals(piota.getPattern().getAngles()); + } + + @Override + public @NotNull Tag serialize() { + return this.getPattern().serializeToNBT(); + } + + @Override + public @NotNull CastResult execute( + CastingVM vm, ServerLevel world, SpellContinuation continuation) { + Supplier<@Nullable Component> castedName = () -> null; + try { + var lookup = PatternRegistryManifest.matchPattern(this.getPattern(), vm.getEnv(), false); + vm.getEnv().precheckAction(lookup); + + Action action; + if (lookup instanceof PatternShapeMatch.Normal + || lookup instanceof PatternShapeMatch.PerWorld) { + ResourceKey key; + if (lookup instanceof PatternShapeMatch.Normal normal) { + key = normal.key; + } else { + PatternShapeMatch.PerWorld perWorld = (PatternShapeMatch.PerWorld) lookup; + key = perWorld.key; + } + + var reqsEnlightenment = + isOfTag( + IXplatAbstractions.INSTANCE.getActionRegistry(), + key, + HexTags.Actions.REQUIRES_ENLIGHTENMENT); + + castedName = () -> HexAPI.instance().getActionI18n(key, reqsEnlightenment); + action = + Objects.requireNonNull(IXplatAbstractions.INSTANCE.getActionRegistry().get(key)) + .action(); + + if (reqsEnlightenment && !vm.getEnv().isEnlightened()) { + // this gets caught down below + throw new MishapUnenlightened(); + } + } else if (lookup instanceof PatternShapeMatch.Special special) { + castedName = special.handler::getName; + action = special.handler.act(); + } else if (lookup instanceof PatternShapeMatch.Nothing) { + throw new MishapInvalidPattern(); + } else throw new IllegalStateException(); + + // do the actual calculation!! + var result = action.operate(vm.getEnv(), vm.getImage(), continuation); + + if (result.getNewImage().getOpsConsumed() > vm.getEnv().maxOpCount()) { + throw new MishapEvalTooMuch(); + } + + var cont2 = result.getNewContinuation(); + // TODO parens also break prescience + var sideEffects = result.getSideEffects(); + + return new CastResult( + this, + cont2, + result.getNewImage(), + sideEffects, + ResolvedPatternType.EVALUATED, + result.getSound()); + + } catch (Mishap mishap) { + return new CastResult( + this, + continuation, + null, + List.of( + new OperatorSideEffect.DoMishap( + mishap, new Mishap.Context(this.getPattern(), castedName.get()))), + mishap.resolutionType(vm.getEnv()), + HexEvalSounds.MISHAP); + } + } + + @Override + public boolean executable() { + return true; + } + + public static IotaType TYPE = + new IotaType<>() { + @Override + public PatternIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { + return PatternIota.deserialize(tag); + } + + @Override + public Component display(Tag tag) { + return PatternIota.display(PatternIota.deserialize(tag).getPattern()); + } + + @Override + public int color() { + return 0xff_ffaa00; + } + }; + + public static PatternIota deserialize(Tag tag) throws IllegalArgumentException { + var patTag = HexUtils.downcast(tag, CompoundTag.TYPE); + HexPattern pat = HexPattern.fromNBT(patTag); + return new PatternIota(pat); + } + + 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()); + + var sig = pat.anglesSignature(); + if (!sig.isEmpty()) { + bob.append(" "); + bob.append(sig); + } + return Component.translatable( + "hexcasting.tooltip.pattern_iota", + Component.literal(bob.toString()).withStyle(ChatFormatting.WHITE)) + .withStyle(ChatFormatting.GOLD); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Vec3Iota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Vec3Iota.java index 9d9d7fbf67..b9cb2756e1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Vec3Iota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/Vec3Iota.java @@ -13,71 +13,67 @@ import org.jetbrains.annotations.Nullable; public class Vec3Iota extends Iota { - public Vec3Iota(@NotNull Vec3 datum) { - super(HexIotaTypes.VEC3, datum); - } + public Vec3Iota(@NotNull Vec3 datum) { + super(HexIotaTypes.VEC3, datum); + } - public Vec3 getVec3() { - var v = (Vec3) this.payload; - return new Vec3( - HexUtils.fixNAN(v.x), - HexUtils.fixNAN(v.y), - HexUtils.fixNAN(v.z) - ); - } + public Vec3 getVec3() { + var v = (Vec3) this.payload; + return new Vec3(HexUtils.fixNAN(v.x), HexUtils.fixNAN(v.y), HexUtils.fixNAN(v.z)); + } - @Override - public boolean isTruthy() { - var v = this.getVec3(); - return !(v.x == 0.0 && v.y == 0.0 && v.z == 0.0); - } + @Override + public boolean isTruthy() { + var v = this.getVec3(); + return !(v.x == 0.0 && v.y == 0.0 && v.z == 0.0); + } - @Override - public boolean toleratesOther(Iota that) { - return typesMatch(this, that) - && that instanceof Vec3Iota viota - && this.getVec3().distanceToSqr(viota.getVec3()) < DoubleIota.TOLERANCE * DoubleIota.TOLERANCE; - } + @Override + public boolean toleratesOther(Iota that) { + return typesMatch(this, that) + && that instanceof Vec3Iota viota + && this.getVec3().distanceToSqr(viota.getVec3()) + < DoubleIota.TOLERANCE * DoubleIota.TOLERANCE; + } - @Override - public @NotNull Tag serialize() { - return HexUtils.serializeToNBT(this.getVec3()); - } + @Override + public @NotNull Tag serialize() { + return HexUtils.serializeToNBT(this.getVec3()); + } - public static IotaType TYPE = new IotaType<>() { - @Nullable - @Override - public Vec3Iota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { - return Vec3Iota.deserialize(tag); - } + public static IotaType TYPE = + new IotaType<>() { + @Nullable @Override + public Vec3Iota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException { + return Vec3Iota.deserialize(tag); + } - @Override - public Component display(Tag tag) { - return Vec3Iota.display(Vec3Iota.deserialize(tag).getVec3()); - } + @Override + public Component display(Tag tag) { + return Vec3Iota.display(Vec3Iota.deserialize(tag).getVec3()); + } - @Override - public int color() { - return 0xff_ff3030; - } - }; + @Override + public int color() { + return 0xff_ff3030; + } + }; - public static Vec3Iota deserialize(Tag tag) throws IllegalArgumentException { - Vec3 vec; - if (tag.getType() == LongArrayTag.TYPE) { - var lat = HexUtils.downcast(tag, LongArrayTag.TYPE); - vec = HexUtils.vecFromNBT(lat.getAsLongArray()); - } else - vec = HexUtils.vecFromNBT(HexUtils.downcast(tag, CompoundTag.TYPE)); - return new Vec3Iota(vec); - } + public static Vec3Iota deserialize(Tag tag) throws IllegalArgumentException { + Vec3 vec; + if (tag.getType() == LongArrayTag.TYPE) { + var lat = HexUtils.downcast(tag, LongArrayTag.TYPE); + vec = HexUtils.vecFromNBT(lat.getAsLongArray()); + } else vec = HexUtils.vecFromNBT(HexUtils.downcast(tag, CompoundTag.TYPE)); + return new Vec3Iota(vec); + } - public static Component display(double x, double y, double z) { - return Component.literal(String.format("(%.2f, %.2f, %.2f)", x, y, z)) - .withStyle(ChatFormatting.RED); - } + public static Component display(double x, double y, double z) { + return Component.literal(String.format("(%.2f, %.2f, %.2f)", x, y, z)) + .withStyle(ChatFormatting.RED); + } - public static Component display(Vec3 v) { - return display(v.x, v.y, v.z); - } + public static Component display(Vec3 v) { + return display(v.x, v.y, v.z); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/EulerPathFinder.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/EulerPathFinder.kt index d79ff002b8..cd74c0e493 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/EulerPathFinder.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/EulerPathFinder.kt @@ -5,84 +5,91 @@ import java.util.* import kotlin.random.Random object EulerPathFinder { - /** - * Find an alternative way to draw the given pattern, based on a random seed. - */ - @JvmStatic - @JvmOverloads - fun findAltDrawing(original: HexPattern, seed: Long, rule: (HexPattern) -> Boolean = { true }): HexPattern { - // http://www.graph-magics.com/articles/euler.php + /** Find an alternative way to draw the given pattern, based on a random seed. */ + @JvmStatic + @JvmOverloads + fun findAltDrawing( + original: HexPattern, + seed: Long, + rule: (HexPattern) -> Boolean = { true } + ): HexPattern { + // http://www.graph-magics.com/articles/euler.php - val rand = Random(seed) + val rand = Random(seed) - // Don't try for too long, in case all paths are exhausted. - // Unlikely to ever actually reach this limit, and can only happen if the same pattern - // is registered both as a Great Pattern and as a special handler or regular pattern, - // or if the random seed has some sort of strange repeating property to it. - var iterationsLeft = 100 - var path: HexPattern - while (iterationsLeft > 0) { - iterationsLeft-- - path = walkPath(original, rand) - if (rule(path)) { - return path - } - } + // Don't try for too long, in case all paths are exhausted. + // Unlikely to ever actually reach this limit, and can only happen if the same pattern + // is registered both as a Great Pattern and as a special handler or regular pattern, + // or if the random seed has some sort of strange repeating property to it. + var iterationsLeft = 100 + var path: HexPattern + while (iterationsLeft > 0) { + iterationsLeft-- + path = walkPath(original, rand) + if (rule(path)) { + return path + } + } - HexAPI.LOGGER.warn("Didn't find alternate path for {} in time", original) - return original - } + HexAPI.LOGGER.warn("Didn't find alternate path for {} in time", original) + return original + } - private fun walkPath(original: HexPattern, rand: Random): HexPattern { - val graph = toGraph(original) - val oddNodes = graph.filter { (_, dirs) -> dirs.size % 2 == 1 } - var current: HexCoord = when (oddNodes.size) { - // An euler-walkable graph must have 0 odd nodes and start anywhere... - 0 -> graph.keys.random(rand) - // or two, and start at one of them - 2 -> oddNodes.keys.random(rand) - else -> throw IllegalStateException() - } + private fun walkPath(original: HexPattern, rand: Random): HexPattern { + val graph = toGraph(original) + val oddNodes = graph.filter { (_, dirs) -> dirs.size % 2 == 1 } + var current: HexCoord = + when (oddNodes.size) { + // An euler-walkable graph must have 0 odd nodes and start anywhere... + 0 -> graph.keys.random(rand) + // or two, and start at one of them + 2 -> oddNodes.keys.random(rand) + else -> throw IllegalStateException() + } - val stack = Stack() - val out = mutableListOf() - do { - val exits = graph[current]!! - if (exits.isEmpty()) { - out.add(current) - current = stack.pop() - } else { - stack.push(current) - // This is where the random part happens, mostly - val burnDir = exits.random(rand) - exits.remove(burnDir) - graph[current + burnDir]?.remove(burnDir * HexAngle.BACK) - current += burnDir - } - } while (graph[current]?.isNotEmpty() == true || stack.isNotEmpty()) - out.add(current) + val stack = Stack() + val out = mutableListOf() + do { + val exits = graph[current]!! + if (exits.isEmpty()) { + out.add(current) + current = stack.pop() + } else { + stack.push(current) + // This is where the random part happens, mostly + val burnDir = exits.random(rand) + exits.remove(burnDir) + graph[current + burnDir]?.remove(burnDir * HexAngle.BACK) + current += burnDir + } + } while (graph[current]?.isNotEmpty() == true || stack.isNotEmpty()) + out.add(current) - val dirs = out.zipWithNext { a, b -> a.immediateDelta(b)!! } - val angles = dirs.zipWithNext { a, b -> b.angleFrom(a) } - return HexPattern(dirs[0], angles.toMutableList()) - } + val dirs = out.zipWithNext { a, b -> a.immediateDelta(b)!! } + val angles = dirs.zipWithNext { a, b -> b.angleFrom(a) } + return HexPattern(dirs[0], angles.toMutableList()) + } - private fun toGraph(pat: HexPattern): HashMap> { - val graph = HashMap>() + private fun toGraph(pat: HexPattern): HashMap> { + val graph = HashMap>() - var compass: HexDir = pat.startDir - var cursor = HexCoord.Origin - for (a in pat.angles) { - // i hate kotlin - graph.getOrPut(cursor) { EnumSet.noneOf(HexDir::class.java) }.add(compass) - graph.getOrPut(cursor + compass) { EnumSet.noneOf(HexDir::class.java) }.add(compass * HexAngle.BACK) + var compass: HexDir = pat.startDir + var cursor = HexCoord.Origin + for (a in pat.angles) { + // i hate kotlin + graph.getOrPut(cursor) { EnumSet.noneOf(HexDir::class.java) }.add(compass) + graph + .getOrPut(cursor + compass) { EnumSet.noneOf(HexDir::class.java) } + .add(compass * HexAngle.BACK) - cursor += compass - compass *= a - } - graph.getOrPut(cursor) { EnumSet.noneOf(HexDir::class.java) }.add(compass) - graph.getOrPut(cursor + compass) { EnumSet.noneOf(HexDir::class.java) }.add(compass * HexAngle.BACK) + cursor += compass + compass *= a + } + graph.getOrPut(cursor) { EnumSet.noneOf(HexDir::class.java) }.add(compass) + graph + .getOrPut(cursor + compass) { EnumSet.noneOf(HexDir::class.java) } + .add(compass * HexAngle.BACK) - return graph - } + return graph + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexAngle.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexAngle.kt index bca8b0b94b..0d9fc999d0 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexAngle.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexAngle.kt @@ -1,8 +1,14 @@ package at.petrak.hexcasting.api.casting.math enum class HexAngle { - FORWARD, RIGHT, RIGHT_BACK, BACK, LEFT_BACK, LEFT; + FORWARD, + RIGHT, + RIGHT_BACK, + BACK, + LEFT_BACK, + LEFT; - fun rotatedBy(a: HexAngle) = values()[(this.ordinal + a.ordinal) % values().size] - operator fun times(a: HexAngle) = this.rotatedBy(a) + fun rotatedBy(a: HexAngle) = values()[(this.ordinal + a.ordinal) % values().size] + + operator fun times(a: HexAngle) = this.rotatedBy(a) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexCoord.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexCoord.kt index 7102bdcce0..5737838b77 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexCoord.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexCoord.kt @@ -4,59 +4,58 @@ import kotlin.math.abs import kotlin.math.max import kotlin.math.min -/** - * Uses axial coordinates as per https://www.redblobgames.com/grids/hexagons/ - */ +/** Uses axial coordinates as per https://www.redblobgames.com/grids/hexagons/ */ data class HexCoord(val q: Int, val r: Int) { - fun s(): Int = -this.q - this.r - - fun shiftedBy(x: HexCoord): HexCoord = HexCoord(this.q + x.q, this.r + x.r) - - fun shiftedBy(d: HexDir) = this.shiftedBy(d.asDelta()) - - fun delta(x: HexCoord): HexCoord = HexCoord(this.q - x.q, this.r - x.r) - - operator fun plus(x: HexCoord) = this.shiftedBy(x) - operator fun plus(d: HexDir) = this.shiftedBy(d) - operator fun minus(x: HexCoord) = this.delta(x) - - fun distanceTo(x: HexCoord) = - (abs(this.q - x.q) + abs(this.q + this.r - x.q - x.r) + abs(this.r - x.r)) / 2 - - fun rangeAround(radius: Int): Iterator = RingIter(this, radius) - - /** Get the direction that would bring you from this to its neighbor */ - fun immediateDelta(neighbor: HexCoord): HexDir? = - when (neighbor - this) { - HexCoord(1, 0) -> HexDir.EAST - HexCoord(0, 1) -> HexDir.SOUTH_EAST - HexCoord(-1, 1) -> HexDir.SOUTH_WEST - HexCoord(-1, 0) -> HexDir.WEST - HexCoord(0, -1) -> HexDir.NORTH_WEST - HexCoord(1, -1) -> HexDir.NORTH_EAST - else -> null - } - - // https://docs.rs/hex2d/1.1.0/src/hex2d/lib.rs.html#785 - private class RingIter(val center: HexCoord, val radius: Int) : Iterator { - var q: Int = -radius - var r: Int = max(-radius, 0) - - override fun hasNext(): Boolean = r <= radius + min(0, -q) || q < radius - - override fun next(): HexCoord { - if (r > radius + min(0, -q)) { - q++ - r = -radius + max(0, -q) - } - val out = HexCoord(center.q + q, center.r + r) - r++ - return out - } - } - - companion object { - @JvmStatic - val Origin = HexCoord(0, 0) - } + fun s(): Int = -this.q - this.r + + fun shiftedBy(x: HexCoord): HexCoord = HexCoord(this.q + x.q, this.r + x.r) + + fun shiftedBy(d: HexDir) = this.shiftedBy(d.asDelta()) + + fun delta(x: HexCoord): HexCoord = HexCoord(this.q - x.q, this.r - x.r) + + operator fun plus(x: HexCoord) = this.shiftedBy(x) + + operator fun plus(d: HexDir) = this.shiftedBy(d) + + operator fun minus(x: HexCoord) = this.delta(x) + + fun distanceTo(x: HexCoord) = + (abs(this.q - x.q) + abs(this.q + this.r - x.q - x.r) + abs(this.r - x.r)) / 2 + + fun rangeAround(radius: Int): Iterator = RingIter(this, radius) + + /** Get the direction that would bring you from this to its neighbor */ + fun immediateDelta(neighbor: HexCoord): HexDir? = + when (neighbor - this) { + HexCoord(1, 0) -> HexDir.EAST + HexCoord(0, 1) -> HexDir.SOUTH_EAST + HexCoord(-1, 1) -> HexDir.SOUTH_WEST + HexCoord(-1, 0) -> HexDir.WEST + HexCoord(0, -1) -> HexDir.NORTH_WEST + HexCoord(1, -1) -> HexDir.NORTH_EAST + else -> null + } + + // https://docs.rs/hex2d/1.1.0/src/hex2d/lib.rs.html#785 + private class RingIter(val center: HexCoord, val radius: Int) : Iterator { + var q: Int = -radius + var r: Int = max(-radius, 0) + + override fun hasNext(): Boolean = r <= radius + min(0, -q) || q < radius + + override fun next(): HexCoord { + if (r > radius + min(0, -q)) { + q++ + r = -radius + max(0, -q) + } + val out = HexCoord(center.q + q, center.r + r) + r++ + return out + } + } + + companion object { + @JvmStatic val Origin = HexCoord(0, 0) + } } 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 03f4ca5a56..786500b167 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 @@ -4,37 +4,38 @@ 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; - - fun rotatedBy(a: HexAngle): HexDir = - values()[(this.ordinal + a.ordinal).mod(values().size)] - - operator fun times(a: HexAngle) = this.rotatedBy(a) - - fun angleFrom(other: HexDir): HexAngle = - HexAngle.values()[(this.ordinal - other.ordinal).mod(HexAngle.values().size)] - - operator fun minus(other: HexDir) = this.angleFrom(other) - - fun asDelta(): HexCoord = - when (this) { - NORTH_EAST -> HexCoord(1, -1) - EAST -> HexCoord(1, 0) - SOUTH_EAST -> HexCoord(0, 1) - SOUTH_WEST -> HexCoord(-1, 1) - WEST -> HexCoord(-1, 0) - NORTH_WEST -> HexCoord(0, -1) - } - - companion object { - val CODEC: Codec = Codec.STRING.xmap( - HexDir::fromString, - HexDir::name - ) - - @JvmStatic - fun fromString(key: String): HexDir { - return values().getSafe(key, WEST) - } - } + NORTH_EAST, + EAST, + SOUTH_EAST, + SOUTH_WEST, + WEST, + NORTH_WEST; + + fun rotatedBy(a: HexAngle): HexDir = values()[(this.ordinal + a.ordinal).mod(values().size)] + + operator fun times(a: HexAngle) = this.rotatedBy(a) + + fun angleFrom(other: HexDir): HexAngle = + HexAngle.values()[(this.ordinal - other.ordinal).mod(HexAngle.values().size)] + + operator fun minus(other: HexDir) = this.angleFrom(other) + + fun asDelta(): HexCoord = + when (this) { + NORTH_EAST -> HexCoord(1, -1) + EAST -> HexCoord(1, 0) + SOUTH_EAST -> HexCoord(0, 1) + SOUTH_WEST -> HexCoord(-1, 1) + WEST -> HexCoord(-1, 0) + NORTH_WEST -> HexCoord(0, -1) + } + + 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 001cfdeab4..643ad9cbf3 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 @@ -10,169 +10,166 @@ import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.Tag import net.minecraft.world.phys.Vec2 -/** - * Sequence of angles to define a pattern traced. - */ +/** Sequence of angles to define a pattern traced. */ data class HexPattern(val startDir: HexDir, val angles: MutableList = arrayListOf()) { - /** - * @return True if it successfully appended, false if not. - */ - fun tryAppendDir(newDir: HexDir): Boolean { - // Two restrictions: - // - No adding a pos/dir pair we previously added - // - No backtracking - val linesSeen = mutableSetOf>() - - var compass = this.startDir - var cursor = HexCoord.Origin - for (a in this.angles) { - linesSeen.add(cursor to compass) - // Line from here to there also blocks there to here - linesSeen.add(cursor + compass to compass.rotatedBy(HexAngle.BACK)) - cursor += compass - compass *= a - } - cursor += compass - - val potentialNewLine = cursor to newDir - if (potentialNewLine in linesSeen) return false - val nextAngle = newDir - compass - if (nextAngle == HexAngle.BACK) return false - - this.angles.add(nextAngle) - return true - } - - @JvmOverloads - fun positions(start: HexCoord = HexCoord.Origin): List { - val out: ArrayList = ArrayList(this.angles.size + 2) - out.add(start) - var compass: HexDir = this.startDir - var cursor = start - for (a in this.angles) { - cursor += compass - out.add(cursor) - compass *= a - } - out.add(cursor + compass) - return out - } - - fun directions(): List { - val out = ArrayList(this.angles.size + 1) - out.add(this.startDir) - - var compass: HexDir = this.startDir - for (a in this.angles) { - compass *= a - out.add(compass) - } - return out - } - - fun finalDir(): HexDir = - this.angles.fold(this.startDir) { acc, angle -> acc * angle } - - - fun serializeToNBT() = NBTBuilder { - TAG_START_DIR %= byte(startDir.ordinal) - TAG_ANGLES %= byteArray(angles.map(HexAngle::ordinal)) - } - - // Terrible shorthand method for easy matching - fun anglesSignature(): String { - return buildString { - for (a in this@HexPattern.angles) { - append( - when (a) { - HexAngle.FORWARD -> "w" - HexAngle.RIGHT -> "e" - HexAngle.RIGHT_BACK -> "d" - HexAngle.BACK -> "s" - HexAngle.LEFT_BACK -> "a" - HexAngle.LEFT -> "q" - } - ) - } - } - } - - /** - * Return the "center of mass" of the pattern. - * Drawing the pattern with the returned vector as the origin will center the pattern around it. - */ - @JvmOverloads - fun getCenter(hexRadius: Float, origin: HexCoord = HexCoord.Origin): Vec2 { - val originPx = coordToPx(origin, hexRadius, Vec2.ZERO) - val points = this.toLines(hexRadius, originPx) - return findCenter(points) - } - - - /** - * Convert a hex pattern into a sequence of straight linePoints spanning its points. - */ - fun toLines(hexSize: Float, origin: Vec2): List = - this.positions().map { coordToPx(it, hexSize, origin) } - - fun sigsEqual(that: HexPattern) = this.angles == that.angles - - override fun toString(): String = buildString { - append("HexPattern[") - append(this@HexPattern.startDir) - append(", ") - append(this@HexPattern.anglesSignature()) - append("]") - } - - companion object { - 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()) - && tag.contains(TAG_ANGLES, Tag.TAG_BYTE_ARRAY.toInt()) - } - - @JvmStatic - fun fromNBT(tag: CompoundTag): HexPattern { - val startDir = HexDir.values().getSafe(tag.getByte(TAG_START_DIR)) - val angles = tag.getByteArray(TAG_ANGLES).map(HexAngle.values()::getSafe) - return HexPattern(startDir, angles.toMutableList()) - } - - @JvmStatic - fun fromAngles(signature: String, startDir: HexDir): HexPattern { - val out = HexPattern(startDir) - var compass = startDir - - for ((idx, c) in signature.withIndex()) { - val angle = when (c) { - 'w' -> HexAngle.FORWARD - 'e' -> HexAngle.RIGHT - 'd' -> HexAngle.RIGHT_BACK - // for completeness ... - 's' -> HexAngle.BACK - 'a' -> HexAngle.LEFT_BACK - 'q' -> HexAngle.LEFT - else -> throw IllegalArgumentException("Cannot match $c at idx $idx to a direction") - } - compass *= angle - val success = out.tryAppendDir(compass) - if (!success) { - throw IllegalStateException("Adding the angle $c at index $idx made the pattern invalid by looping back on itself") - } - } - return out - } - - } + /** @return True if it successfully appended, false if not. */ + fun tryAppendDir(newDir: HexDir): Boolean { + // Two restrictions: + // - No adding a pos/dir pair we previously added + // - No backtracking + val linesSeen = mutableSetOf>() + + var compass = this.startDir + var cursor = HexCoord.Origin + for (a in this.angles) { + linesSeen.add(cursor to compass) + // Line from here to there also blocks there to here + linesSeen.add(cursor + compass to compass.rotatedBy(HexAngle.BACK)) + cursor += compass + compass *= a + } + cursor += compass + + val potentialNewLine = cursor to newDir + if (potentialNewLine in linesSeen) return false + val nextAngle = newDir - compass + if (nextAngle == HexAngle.BACK) return false + + this.angles.add(nextAngle) + return true + } + + @JvmOverloads + fun positions(start: HexCoord = HexCoord.Origin): List { + val out: ArrayList = ArrayList(this.angles.size + 2) + out.add(start) + var compass: HexDir = this.startDir + var cursor = start + for (a in this.angles) { + cursor += compass + out.add(cursor) + compass *= a + } + out.add(cursor + compass) + return out + } + + fun directions(): List { + val out = ArrayList(this.angles.size + 1) + out.add(this.startDir) + + var compass: HexDir = this.startDir + for (a in this.angles) { + compass *= a + out.add(compass) + } + return out + } + + fun finalDir(): HexDir = this.angles.fold(this.startDir) { acc, angle -> acc * angle } + + fun serializeToNBT() = NBTBuilder { + TAG_START_DIR %= byte(startDir.ordinal) + TAG_ANGLES %= byteArray(angles.map(HexAngle::ordinal)) + } + + // Terrible shorthand method for easy matching + fun anglesSignature(): String { + return buildString { + for (a in this@HexPattern.angles) { + append( + when (a) { + HexAngle.FORWARD -> "w" + HexAngle.RIGHT -> "e" + HexAngle.RIGHT_BACK -> "d" + HexAngle.BACK -> "s" + HexAngle.LEFT_BACK -> "a" + HexAngle.LEFT -> "q" + } + ) + } + } + } + + /** + * Return the "center of mass" of the pattern. Drawing the pattern with the returned vector as the + * origin will center the pattern around it. + */ + @JvmOverloads + fun getCenter(hexRadius: Float, origin: HexCoord = HexCoord.Origin): Vec2 { + val originPx = coordToPx(origin, hexRadius, Vec2.ZERO) + val points = this.toLines(hexRadius, originPx) + return findCenter(points) + } + + /** Convert a hex pattern into a sequence of straight linePoints spanning its points. */ + fun toLines(hexSize: Float, origin: Vec2): List = + this.positions().map { coordToPx(it, hexSize, origin) } + + fun sigsEqual(that: HexPattern) = this.angles == that.angles + + override fun toString(): String = buildString { + append("HexPattern[") + append(this@HexPattern.startDir) + append(", ") + append(this@HexPattern.anglesSignature()) + append("]") + } + + companion object { + 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()) && + tag.contains(TAG_ANGLES, Tag.TAG_BYTE_ARRAY.toInt()) + } + + @JvmStatic + fun fromNBT(tag: CompoundTag): HexPattern { + val startDir = HexDir.values().getSafe(tag.getByte(TAG_START_DIR)) + val angles = tag.getByteArray(TAG_ANGLES).map(HexAngle.values()::getSafe) + return HexPattern(startDir, angles.toMutableList()) + } + + @JvmStatic + fun fromAngles(signature: String, startDir: HexDir): HexPattern { + val out = HexPattern(startDir) + var compass = startDir + + for ((idx, c) in signature.withIndex()) { + val angle = + when (c) { + 'w' -> HexAngle.FORWARD + 'e' -> HexAngle.RIGHT + 'd' -> HexAngle.RIGHT_BACK + // for completeness ... + 's' -> HexAngle.BACK + 'a' -> HexAngle.LEFT_BACK + 'q' -> HexAngle.LEFT + else -> throw IllegalArgumentException("Cannot match $c at idx $idx to a direction") + } + compass *= angle + val success = out.tryAppendDir(compass) + if (!success) { + throw IllegalStateException( + "Adding the angle $c at index $idx made the pattern invalid by looping back on itself" + ) + } + } + return out + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/Mishap.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/Mishap.kt index b9462fdda9..9facc058f4 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/Mishap.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/Mishap.kt @@ -20,98 +20,100 @@ import net.minecraft.world.item.ItemStack import net.minecraft.world.phys.Vec3 abstract class Mishap : Throwable() { - /** Mishaps spray half-red, half-this-color. */ - abstract fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment - - open fun particleSpray(ctx: CastingEnvironment): ParticleSpray { - return ParticleSpray( - ctx.mishapSprayPos().add(0.0, 0.2, 0.0), - Vec3(0.0, 2.0, 0.0), - 0.2, Math.PI / 4, 40) - } - - open fun resolutionType(ctx: CastingEnvironment): ResolvedPatternType = ResolvedPatternType.ERRORED - - /** - * Execute the actual effect, not any sfx. - * - * You can also mess up the stack with this. - */ - abstract fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) - - protected abstract fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component? - - fun executeReturnStack(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList): List { - execute(ctx, errorCtx, stack) - return stack - } - - /** - * Every error message should be prefixed with the name of the action... - */ - fun errorMessageWithName(ctx: CastingEnvironment, errorCtx: Context): Component? { - return if (errorCtx.name != null) { - "hexcasting.mishap".asTranslatedComponent(errorCtx.name, this.errorMessage(ctx, errorCtx) ?: return null) - } else { - this.errorMessage(ctx, errorCtx) - } - } - - // Useful helper functions - - protected fun dyeColor(color: DyeColor): FrozenPigment = - FrozenPigment( - ItemStack(HexItems.DYE_PIGMENTS[color]!!), - Util.NIL_UUID - ) - - protected fun error(stub: String, vararg args: Any): Component = - "hexcasting.mishap.$stub".asTranslatedComponent(*args) - - protected fun actionName(name: Component?): Component = - name ?: "hexcasting.spell.null".asTranslatedComponent.lightPurple - - protected fun blockAtPos(ctx: CastingEnvironment, pos: BlockPos): Component { - return ctx.world.getBlockState(pos).block.name - } - - data class Context(val pattern: HexPattern?, val name: Component?) - - companion object { - @JvmStatic - fun trulyHurt(entity: LivingEntity, source: DamageSource, amount: Float) { - entity.setHurtWithStamp(source, entity.level().gameTime) - - val targetHealth = entity.health - amount - if (entity.invulnerableTime > 10) { - val lastHurt = entity.lastHurt - if (lastHurt < amount) - entity.invulnerableTime = 0 - else - entity.lastHurt -= amount - } - if (!entity.hurt(source, amount) && - !entity.isInvulnerableTo(source) && - !entity.level().isClientSide && - !entity.isDeadOrDying - ) { - - // Ok, if you REALLY don't want to play nice... - entity.health = targetHealth - entity.markHurt() - - if (entity.isDeadOrDying) { - if (!entity.checkTotemDeathProtection(source)) { - val sound = entity.deathSoundAccessor - if (sound != null) { - entity.playSound(sound, entity.soundVolumeAccessor, entity.voicePitch) - } - entity.die(source) - } - } else { - entity.playHurtSound(source) - } - } - } - } + /** Mishaps spray half-red, half-this-color. */ + abstract fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment + + open fun particleSpray(ctx: CastingEnvironment): ParticleSpray { + return ParticleSpray( + ctx.mishapSprayPos().add(0.0, 0.2, 0.0), + Vec3(0.0, 2.0, 0.0), + 0.2, + Math.PI / 4, + 40 + ) + } + + open fun resolutionType(ctx: CastingEnvironment): ResolvedPatternType = + ResolvedPatternType.ERRORED + + /** + * Execute the actual effect, not any sfx. + * + * You can also mess up the stack with this. + */ + abstract fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) + + protected abstract fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component? + + fun executeReturnStack( + ctx: CastingEnvironment, + errorCtx: Context, + stack: MutableList + ): List { + execute(ctx, errorCtx, stack) + return stack + } + + /** Every error message should be prefixed with the name of the action... */ + fun errorMessageWithName(ctx: CastingEnvironment, errorCtx: Context): Component? { + return if (errorCtx.name != null) { + "hexcasting.mishap" + .asTranslatedComponent(errorCtx.name, this.errorMessage(ctx, errorCtx) ?: return null) + } else { + this.errorMessage(ctx, errorCtx) + } + } + + // Useful helper functions + + protected fun dyeColor(color: DyeColor): FrozenPigment = + FrozenPigment(ItemStack(HexItems.DYE_PIGMENTS[color]!!), Util.NIL_UUID) + + protected fun error(stub: String, vararg args: Any): Component = + "hexcasting.mishap.$stub".asTranslatedComponent(*args) + + protected fun actionName(name: Component?): Component = + name ?: "hexcasting.spell.null".asTranslatedComponent.lightPurple + + protected fun blockAtPos(ctx: CastingEnvironment, pos: BlockPos): Component { + return ctx.world.getBlockState(pos).block.name + } + + data class Context(val pattern: HexPattern?, val name: Component?) + + companion object { + @JvmStatic + fun trulyHurt(entity: LivingEntity, source: DamageSource, amount: Float) { + entity.setHurtWithStamp(source, entity.level().gameTime) + + val targetHealth = entity.health - amount + if (entity.invulnerableTime > 10) { + val lastHurt = entity.lastHurt + if (lastHurt < amount) entity.invulnerableTime = 0 else entity.lastHurt -= amount + } + if ( + !entity.hurt(source, amount) && + !entity.isInvulnerableTo(source) && + !entity.level().isClientSide && + !entity.isDeadOrDying + ) { + + // Ok, if you REALLY don't want to play nice... + entity.health = targetHealth + entity.markHurt() + + if (entity.isDeadOrDying) { + if (!entity.checkTotemDeathProtection(source)) { + val sound = entity.deathSoundAccessor + if (sound != null) { + entity.playSound(sound, entity.soundVolumeAccessor, entity.voicePitch) + } + entity.die(source) + } + } else { + entity.playHurtSound(source) + } + } + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapAlreadyBrainswept.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapAlreadyBrainswept.kt index fcfe86b2d7..8d602d911f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapAlreadyBrainswept.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapAlreadyBrainswept.kt @@ -9,17 +9,15 @@ import net.minecraft.world.entity.Mob import net.minecraft.world.item.DyeColor class MishapAlreadyBrainswept(val mob: Mob) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.GREEN) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.GREEN) - override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList) { - mob.hurt(mob.damageSources().source(HexDamageTypes.OVERCAST, ctx.castingEntity), mob.health) - } + override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList) { + mob.hurt(mob.damageSources().source(HexDamageTypes.OVERCAST, ctx.castingEntity), mob.health) + } - override fun particleSpray(ctx: CastingEnvironment) = - ParticleSpray.burst(mob.eyePosition, 1.0) - - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("already_brainswept") + override fun particleSpray(ctx: CastingEnvironment) = ParticleSpray.burst(mob.eyePosition, 1.0) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("already_brainswept") } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadBlock.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadBlock.kt index b97a1b72f6..3e1848f19a 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadBlock.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadBlock.kt @@ -8,28 +8,34 @@ import at.petrak.hexcasting.api.utils.asTranslatedComponent import net.minecraft.core.BlockPos import net.minecraft.network.chat.Component import net.minecraft.world.item.DyeColor -import net.minecraft.world.level.Explosion import net.minecraft.world.level.Level import net.minecraft.world.phys.Vec3 class MishapBadBlock(val pos: BlockPos, val expected: Component) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.LIME) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.LIME) - override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList) { - ctx.world.explode(null, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, 0.25f, Level.ExplosionInteraction.NONE) - } + override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList) { + ctx.world.explode( + null, + pos.x + 0.5, + pos.y + 0.5, + pos.z + 0.5, + 0.25f, + Level.ExplosionInteraction.NONE + ) + } - override fun particleSpray(ctx: CastingEnvironment) = - ParticleSpray.burst(Vec3.atCenterOf(pos), 1.0) + override fun particleSpray(ctx: CastingEnvironment) = + ParticleSpray.burst(Vec3.atCenterOf(pos), 1.0) - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("bad_block", expected, this.pos.toShortString(), blockAtPos(ctx, this.pos)) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("bad_block", expected, this.pos.toShortString(), blockAtPos(ctx, this.pos)) - companion object { - @JvmStatic - fun of(pos: BlockPos, stub: String): MishapBadBlock { - return MishapBadBlock(pos, "hexcasting.mishap.bad_block.$stub".asTranslatedComponent) - } - } + companion object { + @JvmStatic + fun of(pos: BlockPos, stub: String): MishapBadBlock { + return MishapBadBlock(pos, "hexcasting.mishap.bad_block.$stub".asTranslatedComponent) + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadBrainsweep.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadBrainsweep.kt index 601273fbb0..e3ead09400 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadBrainsweep.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadBrainsweep.kt @@ -11,17 +11,17 @@ import net.minecraft.world.item.DyeColor import net.minecraft.world.phys.Vec3 class MishapBadBrainsweep(val mob: Mob, val pos: BlockPos) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.GREEN) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.GREEN) - override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList) { - trulyHurt(mob, mob.damageSources().source(HexDamageTypes.OVERCAST, ctx.castingEntity), 1f) - } + override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList) { + trulyHurt(mob, mob.damageSources().source(HexDamageTypes.OVERCAST, ctx.castingEntity), 1f) + } - override fun particleSpray(ctx: CastingEnvironment): ParticleSpray { - return ParticleSpray.burst(Vec3.atCenterOf(pos), 1.0) - } + override fun particleSpray(ctx: CastingEnvironment): ParticleSpray { + return ParticleSpray.burst(Vec3.atCenterOf(pos), 1.0) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("bad_brainsweep", blockAtPos(ctx, this.pos)) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("bad_brainsweep", blockAtPos(ctx, this.pos)) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadEntity.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadEntity.kt index 336a4fa18e..0adfe3aa31 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadEntity.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadEntity.kt @@ -11,23 +11,22 @@ import net.minecraft.world.entity.item.ItemEntity import net.minecraft.world.item.DyeColor class MishapBadEntity(val entity: Entity, val wanted: Component) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.BROWN) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.BROWN) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - env.mishapEnvironment.yeetHeldItemsTowards(entity.position()) - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + env.mishapEnvironment.yeetHeldItemsTowards(entity.position()) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("bad_entity", wanted, entity.displayName.plainCopy().aqua) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("bad_entity", wanted, entity.displayName.plainCopy().aqua) - companion object { - @JvmStatic - fun of(entity: Entity, stub: String): Mishap { - val component = "hexcasting.mishap.bad_item.$stub".asTranslatedComponent - if (entity is ItemEntity) - return MishapBadItem(entity, component) - return MishapBadEntity(entity, component) - } - } + companion object { + @JvmStatic + fun of(entity: Entity, stub: String): Mishap { + val component = "hexcasting.mishap.bad_item.$stub".asTranslatedComponent + if (entity is ItemEntity) return MishapBadItem(entity, component) + return MishapBadEntity(entity, component) + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadItem.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadItem.kt index d41cc88f32..42fdfac933 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadItem.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadItem.kt @@ -9,22 +9,22 @@ import net.minecraft.world.entity.item.ItemEntity import net.minecraft.world.item.DyeColor class MishapBadItem(val item: ItemEntity, val wanted: Component) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.BROWN) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.BROWN) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - item.deltaMovement = item.deltaMovement.add((Math.random() - 0.5) * 0.05, 0.75, (Math.random() - 0.5) * 0.05) - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + item.deltaMovement = + item.deltaMovement.add((Math.random() - 0.5) * 0.05, 0.75, (Math.random() - 0.5) * 0.05) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = if (item.item.isEmpty) - error("no_item", wanted) - else - error("bad_item", wanted, item.item.count, item.item.displayName) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + if (item.item.isEmpty) error("no_item", wanted) + else error("bad_item", wanted, item.item.count, item.item.displayName) - companion object { - @JvmStatic - fun of(item: ItemEntity, stub: String): MishapBadItem { - return MishapBadItem(item, "hexcasting.mishap.bad_item.$stub".asTranslatedComponent) - } - } + companion object { + @JvmStatic + fun of(item: ItemEntity, stub: String): MishapBadItem { + return MishapBadItem(item, "hexcasting.mishap.bad_item.$stub".asTranslatedComponent) + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadLocation.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadLocation.kt index 06955885a0..a15f7bfb17 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadLocation.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadLocation.kt @@ -9,13 +9,13 @@ import net.minecraft.world.item.DyeColor import net.minecraft.world.phys.Vec3 class MishapBadLocation(val location: Vec3, val type: String = "too_far") : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.MAGENTA) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.MAGENTA) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - env.mishapEnvironment.yeetHeldItemsTowards(this.location) - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + env.mishapEnvironment.yeetHeldItemsTowards(this.location) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = - error("location_$type", Vec3Iota.display(location)) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = + error("location_$type", Vec3Iota.display(location)) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadOffhandItem.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadOffhandItem.kt index d35104dab4..5557b0edc2 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadOffhandItem.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapBadOffhandItem.kt @@ -5,27 +5,28 @@ 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.network.chat.Component -import net.minecraft.world.InteractionHand import net.minecraft.world.item.DyeColor import net.minecraft.world.item.ItemStack class MishapBadOffhandItem(val item: ItemStack?, val wanted: Component) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.BROWN) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.BROWN) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - env.mishapEnvironment.dropHeldItems() - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + env.mishapEnvironment.dropHeldItems() + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = if (item?.isEmpty == false) - error("bad_item.offhand", wanted, item.count, item.displayName) - else - error("no_item.offhand", wanted) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + if (item?.isEmpty == false) error("bad_item.offhand", wanted, item.count, item.displayName) + else error("no_item.offhand", wanted) - companion object { - @JvmStatic - fun of(item: ItemStack?, stub: String, vararg args: Any): MishapBadOffhandItem { - return MishapBadOffhandItem(item, "hexcasting.mishap.bad_item.$stub".asTranslatedComponent(*args)) - } - } + companion object { + @JvmStatic + fun of(item: ItemStack?, stub: String, vararg args: Any): MishapBadOffhandItem { + return MishapBadOffhandItem( + item, + "hexcasting.mishap.bad_item.$stub".asTranslatedComponent(*args) + ) + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapDisallowedSpell.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapDisallowedSpell.kt index d3118e5590..25c682f5d3 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapDisallowedSpell.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapDisallowedSpell.kt @@ -7,15 +7,14 @@ import at.petrak.hexcasting.api.pigment.FrozenPigment import net.minecraft.world.item.DyeColor class MishapDisallowedSpell(val type: String = "disallowed") : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.BLACK) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.BLACK) - override fun resolutionType(ctx: CastingEnvironment) = ResolvedPatternType.INVALID + override fun resolutionType(ctx: CastingEnvironment) = ResolvedPatternType.INVALID - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - // NO-OP - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + // NO-OP + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error(type) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = error(type) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapDivideByZero.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapDivideByZero.kt index 98050ee244..4aedbb5cc5 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapDivideByZero.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapDivideByZero.kt @@ -11,80 +11,93 @@ import net.minecraft.network.chat.Component import net.minecraft.world.item.DyeColor import net.minecraft.world.phys.Vec3 -class MishapDivideByZero(val operand1: Component, val operand2: Component, val suffix: String = "divide") : Mishap() { - - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.RED) - - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - stack.add(GarbageIota()) - env.mishapEnvironment.damage(0.5f) - } - - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("divide_by_zero.$suffix", operand1, operand2) - - companion object { - private const val PREFIX = "hexcasting.mishap.divide_by_zero" - - @JvmStatic - fun of(operand1: Double, operand2: Double, suffix: String = "divide"): MishapDivideByZero { - if (suffix == "exponent") - return MishapDivideByZero(translate(DoubleIota(operand1)), powerOf(DoubleIota(operand2)), suffix) - return MishapDivideByZero(translate(DoubleIota(operand1)), translate(DoubleIota(operand2)), suffix) - } - - @JvmStatic - fun of(operand1: Iota, operand2: Iota, suffix: String = "divide"): MishapDivideByZero { - if (suffix == "exponent") - return MishapDivideByZero(translate(operand1), powerOf(operand2), suffix) - return MishapDivideByZero(translate(operand1), translate(operand2), suffix) - } - - @JvmStatic - fun tan(angle: Double): MishapDivideByZero { - val translatedAngle = translate(DoubleIota(angle)) - return MishapDivideByZero( - "$PREFIX.sin".asTranslatedComponent(translatedAngle), - "$PREFIX.cos".asTranslatedComponent(translatedAngle) - ) - } - - @JvmStatic - fun tan(angle: DoubleIota): MishapDivideByZero { - val translatedAngle = translate(angle) - return MishapDivideByZero( - "$PREFIX.sin".asTranslatedComponent(translatedAngle), - "$PREFIX.cos".asTranslatedComponent(translatedAngle) - ) - } - - @JvmStatic - val zero - get() = "$PREFIX.zero".asTranslatedComponent - - @JvmStatic - val zerothPower - get() = "$PREFIX.zero.power".asTranslatedComponent - - @JvmStatic - val zeroVector - get() = "$PREFIX.zero.vec".asTranslatedComponent - - @JvmStatic - fun powerOf(power: Component) = "$PREFIX.power".asTranslatedComponent(power) - - @JvmStatic - fun powerOf(datum: Iota): Component = when { - datum is DoubleIota && datum.double == 0.0 -> zerothPower - else -> datum.display() - } - - @JvmStatic - fun translate(datum: Iota): Component = when { - datum is DoubleIota && datum.double == 0.0 -> zero - datum is Vec3Iota && datum.vec3 == Vec3.ZERO -> zeroVector - else -> datum.display() - } - } +class MishapDivideByZero( + val operand1: Component, + val operand2: Component, + val suffix: String = "divide" +) : Mishap() { + + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.RED) + + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + stack.add(GarbageIota()) + env.mishapEnvironment.damage(0.5f) + } + + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("divide_by_zero.$suffix", operand1, operand2) + + companion object { + private const val PREFIX = "hexcasting.mishap.divide_by_zero" + + @JvmStatic + fun of(operand1: Double, operand2: Double, suffix: String = "divide"): MishapDivideByZero { + if (suffix == "exponent") + return MishapDivideByZero( + translate(DoubleIota(operand1)), + powerOf(DoubleIota(operand2)), + suffix + ) + return MishapDivideByZero( + translate(DoubleIota(operand1)), + translate(DoubleIota(operand2)), + suffix + ) + } + + @JvmStatic + fun of(operand1: Iota, operand2: Iota, suffix: String = "divide"): MishapDivideByZero { + if (suffix == "exponent") + return MishapDivideByZero(translate(operand1), powerOf(operand2), suffix) + return MishapDivideByZero(translate(operand1), translate(operand2), suffix) + } + + @JvmStatic + fun tan(angle: Double): MishapDivideByZero { + val translatedAngle = translate(DoubleIota(angle)) + return MishapDivideByZero( + "$PREFIX.sin".asTranslatedComponent(translatedAngle), + "$PREFIX.cos".asTranslatedComponent(translatedAngle) + ) + } + + @JvmStatic + fun tan(angle: DoubleIota): MishapDivideByZero { + val translatedAngle = translate(angle) + return MishapDivideByZero( + "$PREFIX.sin".asTranslatedComponent(translatedAngle), + "$PREFIX.cos".asTranslatedComponent(translatedAngle) + ) + } + + @JvmStatic + val zero + get() = "$PREFIX.zero".asTranslatedComponent + + @JvmStatic + val zerothPower + get() = "$PREFIX.zero.power".asTranslatedComponent + + @JvmStatic + val zeroVector + get() = "$PREFIX.zero.vec".asTranslatedComponent + + @JvmStatic fun powerOf(power: Component) = "$PREFIX.power".asTranslatedComponent(power) + + @JvmStatic + fun powerOf(datum: Iota): Component = + when { + datum is DoubleIota && datum.double == 0.0 -> zerothPower + else -> datum.display() + } + + @JvmStatic + fun translate(datum: Iota): Component = + when { + datum is DoubleIota && datum.double == 0.0 -> zero + datum is Vec3Iota && datum.vec3 == Vec3.ZERO -> zeroVector + else -> datum.display() + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapEntityTooFarAway.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapEntityTooFarAway.kt index 41671a219c..7ba9b0fdf0 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapEntityTooFarAway.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapEntityTooFarAway.kt @@ -8,13 +8,13 @@ import net.minecraft.world.entity.Entity import net.minecraft.world.item.DyeColor class MishapEntityTooFarAway(val entity: Entity) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.PINK) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.PINK) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - env.mishapEnvironment.yeetHeldItemsTowards(entity.position()) - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + env.mishapEnvironment.yeetHeldItemsTowards(entity.position()) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = - error("entity_too_far", entity.displayName) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = + error("entity_too_far", entity.displayName) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapEvalTooMuch.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapEvalTooMuch.kt index 24ecda81e8..90882770f6 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapEvalTooMuch.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapEvalTooMuch.kt @@ -6,13 +6,12 @@ import at.petrak.hexcasting.api.pigment.FrozenPigment import net.minecraft.world.item.DyeColor class MishapEvalTooMuch : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.BLUE) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.BLUE) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - env.mishapEnvironment.drown() - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + env.mishapEnvironment.drown() + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("eval_too_deep") + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = error("eval_too_deep") } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapImmuneEntity.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapImmuneEntity.kt index a722efb2ec..cf09d200ee 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapImmuneEntity.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapImmuneEntity.kt @@ -8,13 +8,13 @@ import net.minecraft.world.entity.Entity import net.minecraft.world.item.DyeColor class MishapImmuneEntity(val entity: Entity) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.BLUE) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.BLUE) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - env.mishapEnvironment.yeetHeldItemsTowards(entity.position()) - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + env.mishapEnvironment.yeetHeldItemsTowards(entity.position()) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("immune_entity", entity.displayName.plainCopy().aqua) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("immune_entity", entity.displayName.plainCopy().aqua) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInternalException.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInternalException.kt index 7301c01127..cc04180e90 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInternalException.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInternalException.kt @@ -6,13 +6,13 @@ import at.petrak.hexcasting.api.pigment.FrozenPigment import net.minecraft.world.item.DyeColor class MishapInternalException(val exception: Exception) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.BLACK) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.BLACK) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - // NO-OP - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + // NO-OP + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("unknown", exception) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("unknown", exception) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidIota.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidIota.kt index 17e71d6044..fb9ea88ddc 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidIota.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidIota.kt @@ -8,37 +8,34 @@ import at.petrak.hexcasting.api.utils.asTranslatedComponent import net.minecraft.network.chat.Component import net.minecraft.world.item.DyeColor -/** - * The value failed some kind of predicate. - */ -class MishapInvalidIota( - val perpetrator: Iota, - val reverseIdx: Int, - val expected: Component -) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.GRAY) +/** The value failed some kind of predicate. */ +class MishapInvalidIota(val perpetrator: Iota, val reverseIdx: Int, val expected: Component) : + Mishap() { + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.GRAY) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - stack[stack.size - 1 - reverseIdx] = GarbageIota(); - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + stack[stack.size - 1 - reverseIdx] = GarbageIota() + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error( - "invalid_value", expected, reverseIdx, - perpetrator.display() - ) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("invalid_value", expected, reverseIdx, perpetrator.display()) - companion object { - @JvmStatic - fun ofType(perpetrator: Iota, reverseIdx: Int, name: String): MishapInvalidIota { - return of(perpetrator, reverseIdx, "class.$name") - } + companion object { + @JvmStatic + fun ofType(perpetrator: Iota, reverseIdx: Int, name: String): MishapInvalidIota { + return of(perpetrator, reverseIdx, "class.$name") + } - @JvmStatic - fun of(perpetrator: Iota, reverseIdx: Int, name: String, vararg translations: Any): MishapInvalidIota { - val key = "hexcasting.mishap.invalid_value.$name" - return MishapInvalidIota(perpetrator, reverseIdx, key.asTranslatedComponent(*translations)) - } - } + @JvmStatic + fun of( + perpetrator: Iota, + reverseIdx: Int, + name: String, + vararg translations: Any + ): MishapInvalidIota { + val key = "hexcasting.mishap.invalid_value.$name" + return MishapInvalidIota(perpetrator, reverseIdx, key.asTranslatedComponent(*translations)) + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidOperatorArgs.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidOperatorArgs.kt index a16c083f4c..7d0f14b5c0 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidOperatorArgs.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidOperatorArgs.kt @@ -9,34 +9,28 @@ import net.minecraft.network.chat.Component import net.minecraft.network.chat.ComponentUtils import net.minecraft.world.item.DyeColor -/** - * The value failed some kind of predicate. - */ +/** The value failed some kind of predicate. */ class MishapInvalidOperatorArgs(val perpetrators: List) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.GRAY) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.GRAY) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - for (i in perpetrators.indices) { - stack[stack.size - 1 - i] = GarbageIota() - } - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + for (i in perpetrators.indices) { + stack[stack.size - 1 - i] = GarbageIota() + } + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component { - return if (perpetrators.size == 1) { - error( - "invalid_operator_args.one", - 0, - perpetrators[0].display() - ) - } else { - error( - "invalid_operator_args.many", - perpetrators.size, - 0, - perpetrators.lastIndex, - ComponentUtils.formatList(perpetrators.map { it.display() }, ", ".asTextComponent) - ) - } - } + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component { + return if (perpetrators.size == 1) { + error("invalid_operator_args.one", 0, perpetrators[0].display()) + } else { + error( + "invalid_operator_args.many", + perpetrators.size, + 0, + perpetrators.lastIndex, + ComponentUtils.formatList(perpetrators.map { it.display() }, ", ".asTextComponent) + ) + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidPattern.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidPattern.kt index 20fbd31fb5..1244c81149 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidPattern.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidPattern.kt @@ -8,15 +8,14 @@ import at.petrak.hexcasting.api.pigment.FrozenPigment import net.minecraft.world.item.DyeColor class MishapInvalidPattern : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.YELLOW) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.YELLOW) - override fun resolutionType(ctx: CastingEnvironment) = ResolvedPatternType.INVALID + override fun resolutionType(ctx: CastingEnvironment) = ResolvedPatternType.INVALID - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - stack.add(GarbageIota()) - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + stack.add(GarbageIota()) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("invalid_pattern") + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = error("invalid_pattern") } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidSpellDatumType.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidSpellDatumType.kt index 92384b0792..142c544a92 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidSpellDatumType.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapInvalidSpellDatumType.kt @@ -5,17 +5,19 @@ import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.pigment.FrozenPigment import net.minecraft.world.item.DyeColor -/** - * this is bad - */ +/** this is bad */ class MishapInvalidSpellDatumType(val perpetrator: Any) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.BLACK) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.BLACK) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - // NO-OP - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + // NO-OP + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("invalid_spell_datum_type", this.perpetrator.toString(), this.perpetrator.javaClass.typeName) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error( + "invalid_spell_datum_type", + this.perpetrator.toString(), + this.perpetrator.javaClass.typeName + ) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapLocationInWrongDimension.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapLocationInWrongDimension.kt index 2d0e721838..87a80979bf 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapLocationInWrongDimension.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapLocationInWrongDimension.kt @@ -9,16 +9,17 @@ import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.DyeColor class MishapLocationInWrongDimension(val properDimension: ResourceLocation) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.MAGENTA) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.MAGENTA) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - stack.add(GarbageIota()) - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + stack.add(GarbageIota()) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = - error( - "wrong_dimension", properDimension.toString(), - ctx.world.dimension().location().toString() - ) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = + error( + "wrong_dimension", + properDimension.toString(), + ctx.world.dimension().location().toString() + ) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNoAkashicRecord.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNoAkashicRecord.kt index 05eb6f5837..424ffbc1e8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNoAkashicRecord.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNoAkashicRecord.kt @@ -7,13 +7,13 @@ import net.minecraft.core.BlockPos import net.minecraft.world.item.DyeColor class MishapNoAkashicRecord(val pos: BlockPos) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.PURPLE) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.PURPLE) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - env.mishapEnvironment.removeXp(100) - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + env.mishapEnvironment.removeXp(100) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("no_akashic_record", pos.toShortString()) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("no_akashic_record", pos.toShortString()) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNotEnoughArgs.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNotEnoughArgs.kt index 646f42c927..9f85e8b178 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNotEnoughArgs.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapNotEnoughArgs.kt @@ -7,16 +7,13 @@ import at.petrak.hexcasting.api.pigment.FrozenPigment import net.minecraft.world.item.DyeColor class MishapNotEnoughArgs(val expected: Int, val got: Int) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.LIGHT_GRAY) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.LIGHT_GRAY) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - repeat(expected - got) { stack.add(GarbageIota()) } - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + repeat(expected - got) { stack.add(GarbageIota()) } + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - if (got == 0) - error("no_args", expected) - else - error("not_enough_args", expected, got) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + if (got == 0) error("no_args", expected) else error("not_enough_args", expected, got) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapOthersName.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapOthersName.kt index e6ea0326ed..e7da7b3952 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapOthersName.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapOthersName.kt @@ -3,55 +3,53 @@ package at.petrak.hexcasting.api.casting.mishaps import at.petrak.hexcasting.api.casting.eval.CastingEnvironment import at.petrak.hexcasting.api.casting.iota.EntityIota import at.petrak.hexcasting.api.casting.iota.Iota -import at.petrak.hexcasting.api.casting.iota.ListIota import at.petrak.hexcasting.api.pigment.FrozenPigment import net.minecraft.world.entity.player.Player import net.minecraft.world.item.DyeColor -/** - * Also throwable for your *own* name, for cases like Chronicler's Gambit - */ +/** Also throwable for your *own* name, for cases like Chronicler's Gambit */ class MishapOthersName(val confidant: Player) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.BLACK) - - override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList) { - val seconds = if (this.confidant == ctx.castingEntity) 5 else 60 - ctx.mishapEnvironment.blind(seconds * 20) - } - - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - if (this.confidant == ctx.castingEntity) - error("others_name.self") - else - error("others_name", confidant.name) - - companion object { - /** - * Return any true names found in this iota. - * - * If `caster` is non-null, it will ignore that when checking. - */ - @JvmStatic - fun getTrueNameFromDatum(datum: Iota, caster: Player?): Player? { - val poolToSearch = ArrayDeque() - poolToSearch.addLast(datum) - - while (poolToSearch.isNotEmpty()) { - val datumToCheck = poolToSearch.removeFirst() - if (datumToCheck is EntityIota && datumToCheck.entity is Player && datumToCheck.entity != caster) - return datumToCheck.entity as Player - val datumSubIotas = datumToCheck.subIotas() - if (datumSubIotas != null) - poolToSearch.addAll(datumSubIotas) - } - - return null - } - - @JvmStatic - fun getTrueNameFromArgs(datums: List, caster: Player?): Player? { - return datums.firstNotNullOfOrNull { getTrueNameFromDatum(it, caster) } - } - } + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.BLACK) + + override fun execute(ctx: CastingEnvironment, errorCtx: Context, stack: MutableList) { + val seconds = if (this.confidant == ctx.castingEntity) 5 else 60 + ctx.mishapEnvironment.blind(seconds * 20) + } + + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + if (this.confidant == ctx.castingEntity) error("others_name.self") + else error("others_name", confidant.name) + + companion object { + /** + * Return any true names found in this iota. + * + * If `caster` is non-null, it will ignore that when checking. + */ + @JvmStatic + fun getTrueNameFromDatum(datum: Iota, caster: Player?): Player? { + val poolToSearch = ArrayDeque() + poolToSearch.addLast(datum) + + while (poolToSearch.isNotEmpty()) { + val datumToCheck = poolToSearch.removeFirst() + if ( + datumToCheck is EntityIota && + datumToCheck.entity is Player && + datumToCheck.entity != caster + ) + return datumToCheck.entity as Player + val datumSubIotas = datumToCheck.subIotas() + if (datumSubIotas != null) poolToSearch.addAll(datumSubIotas) + } + + return null + } + + @JvmStatic + fun getTrueNameFromArgs(datums: List, caster: Player?): Player? { + return datums.firstNotNullOfOrNull { getTrueNameFromDatum(it, caster) } + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapStackSize.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapStackSize.kt index 571e39bcd9..5de4ec3b7c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapStackSize.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapStackSize.kt @@ -5,20 +5,18 @@ import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType import at.petrak.hexcasting.api.casting.iota.GarbageIota import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.pigment.FrozenPigment -import at.petrak.hexcasting.common.lib.HexDamageTypes import net.minecraft.world.item.DyeColor class MishapStackSize() : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.BLACK) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.BLACK) - override fun resolutionType(ctx: CastingEnvironment) = ResolvedPatternType.ERRORED + override fun resolutionType(ctx: CastingEnvironment) = ResolvedPatternType.ERRORED - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - stack.clear() - stack.add(GarbageIota()) - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + stack.clear() + stack.add(GarbageIota()) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("stack_size") + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = error("stack_size") } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapTooManyCloseParens.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapTooManyCloseParens.kt index dfa159bda7..da0f82a1d2 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapTooManyCloseParens.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapTooManyCloseParens.kt @@ -7,15 +7,14 @@ import at.petrak.hexcasting.api.pigment.FrozenPigment import net.minecraft.world.item.DyeColor class MishapTooManyCloseParens : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.ORANGE) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.ORANGE) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - // TODO this is a kinda shitty mishap - if (errorCtx.pattern != null) - stack.add(PatternIota(errorCtx.pattern)) - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + // TODO this is a kinda shitty mishap + if (errorCtx.pattern != null) stack.add(PatternIota(errorCtx.pattern)) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("too_many_close_parens") + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("too_many_close_parens") } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapUnenlightened.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapUnenlightened.kt index e0dde2150b..81219512c0 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapUnenlightened.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapUnenlightened.kt @@ -12,24 +12,35 @@ import net.minecraft.sounds.SoundSource import net.minecraft.world.item.DyeColor class MishapUnenlightened : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.RED) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.RED) - override fun resolutionType(ctx: CastingEnvironment) = ResolvedPatternType.INVALID + override fun resolutionType(ctx: CastingEnvironment) = ResolvedPatternType.INVALID - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - env.mishapEnvironment.dropHeldItems() - env.castingEntity?.sendSystemMessage("hexcasting.message.cant_great_spell".asTranslatedComponent) + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + env.mishapEnvironment.dropHeldItems() + env.castingEntity?.sendSystemMessage( + "hexcasting.message.cant_great_spell".asTranslatedComponent + ) - // add some non-zero level of juice I guess - val pos = env.mishapSprayPos() - env.world.playSound(null, pos.x, pos.y, pos.z, SoundEvents.GLASS_BREAK, SoundSource.PLAYERS, 0.5f, 0.7f) + // add some non-zero level of juice I guess + val pos = env.mishapSprayPos() + env.world.playSound( + null, + pos.x, + pos.y, + pos.z, + SoundEvents.GLASS_BREAK, + SoundSource.PLAYERS, + 0.5f, + 0.7f + ) - val castingPlayer = env.castingEntity as? ServerPlayer - if (castingPlayer != null) { - HexAdvancementTriggers.FAIL_GREAT_SPELL_TRIGGER.trigger(castingPlayer) - } - } + val castingPlayer = env.castingEntity as? ServerPlayer + if (castingPlayer != null) { + HexAdvancementTriggers.FAIL_GREAT_SPELL_TRIGGER.trigger(castingPlayer) + } + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = null + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = null } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapUnescapedValue.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapUnescapedValue.kt index 6a30a8221e..8c9ae24f09 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapUnescapedValue.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/MishapUnescapedValue.kt @@ -5,31 +5,27 @@ import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.pigment.FrozenPigment import net.minecraft.world.item.DyeColor -/** - * The value was a naked iota without being Considered or Retrospected. - */ -class MishapUnescapedValue( - val perpetrator: Iota -) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.GRAY) +/** The value was a naked iota without being Considered or Retrospected. */ +class MishapUnescapedValue(val perpetrator: Iota) : Mishap() { + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.GRAY) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - // TODO - /* - val idx = stack.indexOfLast { it.getType() == DatumType.LIST } - if (idx != -1) { - val list = stack[idx].payload as SpellList - val idxOfIota = list.indexOfFirst { it == perpetrator } - if (idxOfIota != -1) { - stack[idx] = SpellDatum.make(list.modifyAt(idxOfIota) { - SpellList.LPair(SpellDatum.make(Widget.GARBAGE), it.cdr) - }) - } - } - */ - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + // TODO + /* + val idx = stack.indexOfLast { it.getType() == DatumType.LIST } + if (idx != -1) { + val list = stack[idx].payload as SpellList + val idxOfIota = list.indexOfFirst { it == perpetrator } + if (idxOfIota != -1) { + stack[idx] = SpellDatum.make(list.modifyAt(idxOfIota) { + SpellList.LPair(SpellDatum.make(Widget.GARBAGE), it.cdr) + }) + } + } + */ + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("unescaped", perpetrator.display()) + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = + error("unescaped", perpetrator.display()) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixEmptyStack.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixEmptyStack.kt index ae27403f23..1f92fe0a08 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixEmptyStack.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixEmptyStack.kt @@ -10,15 +10,15 @@ import net.minecraft.world.item.DyeColor // what a mouthful class MishapBoolDirectrixEmptyStack( - val pos: BlockPos, + val pos: BlockPos, ) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.GRAY) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.GRAY) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - env.world.destroyBlock(this.pos, true) - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + env.world.destroyBlock(this.pos, true) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = - error("circle.bool_directrix.empty_stack", pos.toShortString()) -} \ No newline at end of file + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = + error("circle.bool_directrix.empty_stack", pos.toShortString()) +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt index 7c3d7b1fb5..f17ccc842d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt @@ -10,16 +10,16 @@ import net.minecraft.world.item.DyeColor // what a mouthful class MishapBoolDirectrixNotBool( - val perpetrator: Iota, - val pos: BlockPos, + val perpetrator: Iota, + val pos: BlockPos, ) : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.GRAY) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.GRAY) - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - env.world.destroyBlock(this.pos, true) - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + env.world.destroyBlock(this.pos, true) + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = - error("circle.bool_directrix_no_bool", pos.toShortString(), perpetrator.display()) -} \ No newline at end of file + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = + error("circle.bool_directrix_no_bool", pos.toShortString(), perpetrator.display()) +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapNoSpellCircle.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapNoSpellCircle.kt index 9eeae0f2b5..ce68126276 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapNoSpellCircle.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapNoSpellCircle.kt @@ -11,32 +11,33 @@ import net.minecraft.world.item.ItemStack import net.minecraft.world.item.enchantment.EnchantmentHelper class MishapNoSpellCircle : Mishap() { - override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = - dyeColor(DyeColor.LIGHT_BLUE) + override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment = + dyeColor(DyeColor.LIGHT_BLUE) - // FIXME: make me work with any entity and not just players - private inline fun dropAll(player: Player, stacks: MutableList, filter: (ItemStack) -> Boolean = { true }) { - for (index in stacks.indices) { - val item = stacks[index] - if (!item.isEmpty && filter(item)) { - player.drop(item, true, false) - stacks[index] = ItemStack.EMPTY - } - } - } + // FIXME: make me work with any entity and not just players + private inline fun dropAll( + player: Player, + stacks: MutableList, + filter: (ItemStack) -> Boolean = { true } + ) { + for (index in stacks.indices) { + val item = stacks[index] + if (!item.isEmpty && filter(item)) { + player.drop(item, true, false) + stacks[index] = ItemStack.EMPTY + } + } + } - override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { - val caster = env.castingEntity as? ServerPlayer - if (caster != null) { - // FIXME: handle null caster case - dropAll(caster, caster.inventory.items) - dropAll(caster, caster.inventory.offhand) - dropAll(caster, caster.inventory.armor) { - !EnchantmentHelper.hasBindingCurse(it) - } - } - } + override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList) { + val caster = env.castingEntity as? ServerPlayer + if (caster != null) { + // FIXME: handle null caster case + dropAll(caster, caster.inventory.items) + dropAll(caster, caster.inventory.offhand) + dropAll(caster, caster.inventory.armor) { !EnchantmentHelper.hasBindingCurse(it) } + } + } - override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = - error("no_spell_circle") + override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = error("no_spell_circle") } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/client/ClientCastingStack.kt b/Common/src/main/java/at/petrak/hexcasting/api/client/ClientCastingStack.kt index 249adc092b..5b880927bf 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/client/ClientCastingStack.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/client/ClientCastingStack.kt @@ -3,59 +3,62 @@ package at.petrak.hexcasting.api.client import at.petrak.hexcasting.api.casting.math.HexPattern import kotlin.math.min - class ClientCastingStack { - private var patterns = ArrayList() - private var toRemove = mutableSetOf() - - private var toAdd = ArrayList() - - fun addPattern(pattern: HexPattern?, lifetime: Int) { - if (pattern == null) return - if (patterns.stream().anyMatch { patternRenderHolder -> patternRenderHolder.pattern.hashCode() == pattern.hashCode() }) { - return - } - if (patterns.size > 100) { - patterns.removeAt(0) - } - patterns.add(HexPatternRenderHolder(pattern, lifetime)) - } - - fun slowClear() { - patterns.forEach { it.lifetime = min(it.lifetime, 140) } - } - - fun getPatterns(): List { - return patterns - } - - fun getPattern(index: Int): HexPattern? = patterns.getOrNull(index)?.pattern - - fun getPatternHolder(index: Int): HexPatternRenderHolder? = patterns.getOrNull(index) - - fun size(): Int { - return patterns.size - } - - fun tick() { - // tick without getting a cme - toAdd.forEach { pattern -> - if (patterns.size > 100) { - patterns.removeAt(0) - } - patterns.add(pattern) - } - - toAdd.clear() - - patterns.forEach { pattern -> - pattern.tick() - if (pattern.lifetime <= 0) { - toRemove.add(pattern) - } - } - - patterns.removeAll(toRemove) - toRemove.clear() - } -} \ No newline at end of file + private var patterns = ArrayList() + private var toRemove = mutableSetOf() + + private var toAdd = ArrayList() + + fun addPattern(pattern: HexPattern?, lifetime: Int) { + if (pattern == null) return + if ( + patterns.stream().anyMatch { patternRenderHolder -> + patternRenderHolder.pattern.hashCode() == pattern.hashCode() + } + ) { + return + } + if (patterns.size > 100) { + patterns.removeAt(0) + } + patterns.add(HexPatternRenderHolder(pattern, lifetime)) + } + + fun slowClear() { + patterns.forEach { it.lifetime = min(it.lifetime, 140) } + } + + fun getPatterns(): List { + return patterns + } + + fun getPattern(index: Int): HexPattern? = patterns.getOrNull(index)?.pattern + + fun getPatternHolder(index: Int): HexPatternRenderHolder? = patterns.getOrNull(index) + + fun size(): Int { + return patterns.size + } + + fun tick() { + // tick without getting a cme + toAdd.forEach { pattern -> + if (patterns.size > 100) { + patterns.removeAt(0) + } + patterns.add(pattern) + } + + toAdd.clear() + + patterns.forEach { pattern -> + pattern.tick() + if (pattern.lifetime <= 0) { + toRemove.add(pattern) + } + } + + patterns.removeAll(toRemove) + toRemove.clear() + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/client/ClientRenderHelper.kt b/Common/src/main/java/at/petrak/hexcasting/api/client/ClientRenderHelper.kt index 374e89c932..9602f19275 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/client/ClientRenderHelper.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/client/ClientRenderHelper.kt @@ -1,4 +1,5 @@ @file:JvmName("ClientRenderHelper") + package at.petrak.hexcasting.api.client import at.petrak.hexcasting.client.ClientTickCounter @@ -11,76 +12,101 @@ import at.petrak.hexcasting.xplat.IXplatAbstractions 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.world.entity.player.Player -import net.minecraft.world.phys.Vec2 import kotlin.math.abs import kotlin.math.cos import kotlin.math.floor import kotlin.math.sin - +import net.minecraft.client.renderer.GameRenderer +import net.minecraft.world.entity.player.Player +import net.minecraft.world.phys.Vec2 fun renderCastingStack(ps: PoseStack, player: Player, pticks: Float) { - val stack = IClientXplatAbstractions.INSTANCE.getClientCastingStack(player) + val stack = IClientXplatAbstractions.INSTANCE.getClientCastingStack(player) - for (k in 0 until stack.getPatterns().size) { - val patternRenderHolder = stack.getPatternHolder(k) ?: continue - val pattern = patternRenderHolder.pattern - val lifetime = patternRenderHolder.lifetime - val lifetimeOffset = if (lifetime <= 5f) (5f - lifetime) / 5f else 0f + for (k in 0 until stack.getPatterns().size) { + val patternRenderHolder = stack.getPatternHolder(k) ?: continue + val pattern = patternRenderHolder.pattern + val lifetime = patternRenderHolder.lifetime + val lifetimeOffset = if (lifetime <= 5f) (5f - lifetime) / 5f else 0f - ps.pushPose() - ps.mulPose(Axis.YP.rotationDegrees(((player.level().gameTime + pticks) * (sin(k * 12.543565f) * 3.4f) * (k / 12.43f) % 360 + (1 + k) * 45f))) - ps.translate(0.0, 1 + sin(k.toDouble()) * 0.75, 0.75 + cos((k / 8.0)) * 0.25 + cos((player.level().gameTime + pticks) / (7 + k / 4)) * 0.065) - ps.scale(1 / 24f * (1 - lifetimeOffset), 1 / 24f * (1 - lifetimeOffset), 1 / 24f * (1 - lifetimeOffset)) - ps.translate(0.0, floor((k / 8.0)), 0.0) - ps.translate(0.0, sin((player.level().gameTime + pticks) / (7.0 + k / 8.0)), 0.0) + ps.pushPose() + ps.mulPose( + Axis.YP.rotationDegrees( + ((player.level().gameTime + pticks) * (sin(k * 12.543565f) * 3.4f) * (k / 12.43f) % 360 + + (1 + k) * 45f) + ) + ) + ps.translate( + 0.0, + 1 + sin(k.toDouble()) * 0.75, + 0.75 + cos((k / 8.0)) * 0.25 + cos((player.level().gameTime + pticks) / (7 + k / 4)) * 0.065 + ) + ps.scale( + 1 / 24f * (1 - lifetimeOffset), + 1 / 24f * (1 - lifetimeOffset), + 1 / 24f * (1 - lifetimeOffset) + ) + ps.translate(0.0, floor((k / 8.0)), 0.0) + ps.translate(0.0, sin((player.level().gameTime + pticks) / (7.0 + k / 8.0)), 0.0) - val oldShader = RenderSystem.getShader() - RenderSystem.setShader { GameRenderer.getPositionColorShader() } - RenderSystem.enableDepthTest() - RenderSystem.disableCull() - val com1 = pattern.getCenter(1f) - val lines1 = pattern.toLines(1f, Vec2.ZERO) - var maxDx = -1f - var maxDy = -1f - for (line in lines1) { - val dx = abs(line.x - com1.x) - if (dx > maxDx) { - maxDx = dx - } - val dy = abs(line.y - com1.y) - if (dy > maxDy) { - maxDy = dy - } - } - val scale = 3.8f.coerceAtMost((16 / 2.5f / maxDx).coerceAtMost(16 / 2.5f / maxDy)) - val com2 = pattern.getCenter(scale) - val lines2 = pattern.toLines(scale, com2.negated()).toMutableList() - for (i in lines2.indices) { - val line = lines2[i] - lines2[i] = Vec2(line.x, -line.y) - } - val variance = 0.65f - val speed = 0.1f - val stupidHash = player.hashCode().toDouble() - val zappy: List = makeZappy(lines2, findDupIndices(pattern.positions()), - 5, variance, speed, 0.2f, 0f, - 1f, stupidHash) - val outer: Int = IXplatAbstractions.INSTANCE.getPigment(player).colorProvider.getColor( - ClientTickCounter.getTotal() / 2f, - patternRenderHolder.getColourPos(player.random)) - val rgbOnly = outer and 0x00FFFFFF - var newAlpha = outer ushr 24 - if (lifetime <= 60) { - newAlpha = floor((lifetime / 60f * 255).toDouble()).toInt() - } - val newARGB = newAlpha shl 24 or rgbOnly - val inner: Int = screenCol(newARGB) - drawLineSeq(ps.last().pose(), zappy, 0.35f, 0f, newARGB, newARGB) - drawLineSeq(ps.last().pose(), zappy, 0.14f, 0.01f, inner, inner) - ps.popPose() - RenderSystem.setShader { oldShader } - RenderSystem.enableCull() - } -} \ No newline at end of file + val oldShader = RenderSystem.getShader() + RenderSystem.setShader { GameRenderer.getPositionColorShader() } + RenderSystem.enableDepthTest() + RenderSystem.disableCull() + val com1 = pattern.getCenter(1f) + val lines1 = pattern.toLines(1f, Vec2.ZERO) + var maxDx = -1f + var maxDy = -1f + for (line in lines1) { + val dx = abs(line.x - com1.x) + if (dx > maxDx) { + maxDx = dx + } + val dy = abs(line.y - com1.y) + if (dy > maxDy) { + maxDy = dy + } + } + val scale = 3.8f.coerceAtMost((16 / 2.5f / maxDx).coerceAtMost(16 / 2.5f / maxDy)) + val com2 = pattern.getCenter(scale) + val lines2 = pattern.toLines(scale, com2.negated()).toMutableList() + for (i in lines2.indices) { + val line = lines2[i] + lines2[i] = Vec2(line.x, -line.y) + } + val variance = 0.65f + val speed = 0.1f + val stupidHash = player.hashCode().toDouble() + val zappy: List = + makeZappy( + lines2, + findDupIndices(pattern.positions()), + 5, + variance, + speed, + 0.2f, + 0f, + 1f, + stupidHash + ) + val outer: Int = + IXplatAbstractions.INSTANCE.getPigment(player) + .colorProvider + .getColor( + ClientTickCounter.getTotal() / 2f, + patternRenderHolder.getColourPos(player.random) + ) + val rgbOnly = outer and 0x00FFFFFF + var newAlpha = outer ushr 24 + if (lifetime <= 60) { + newAlpha = floor((lifetime / 60f * 255).toDouble()).toInt() + } + val newARGB = newAlpha shl 24 or rgbOnly + val inner: Int = screenCol(newARGB) + drawLineSeq(ps.last().pose(), zappy, 0.35f, 0f, newARGB, newARGB) + drawLineSeq(ps.last().pose(), zappy, 0.14f, 0.01f, inner, inner) + ps.popPose() + RenderSystem.setShader { oldShader } + RenderSystem.enableCull() + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/client/HexPatternRenderHolder.kt b/Common/src/main/java/at/petrak/hexcasting/api/client/HexPatternRenderHolder.kt index 3092f3727a..7ae515ea44 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/client/HexPatternRenderHolder.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/client/HexPatternRenderHolder.kt @@ -5,15 +5,19 @@ import net.minecraft.util.RandomSource import net.minecraft.world.phys.Vec3 data class HexPatternRenderHolder(val pattern: HexPattern, var lifetime: Int) { - private var colourPos: Vec3? = null + private var colourPos: Vec3? = null - fun getColourPos(random: RandomSource): Vec3 { - return colourPos ?: let { - Vec3(random.nextDouble(), random.nextDouble(), random.nextDouble()).normalize().scale(3.0).also { colourPos = it } - } - } + fun getColourPos(random: RandomSource): Vec3 { + return colourPos + ?: let { + Vec3(random.nextDouble(), random.nextDouble(), random.nextDouble()) + .normalize() + .scale(3.0) + .also { colourPos = it } + } + } - fun tick() { - lifetime -= 1 - } -} \ No newline at end of file + fun tick() { + lifetime -= 1 + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/client/ScryingLensOverlayRegistry.java b/Common/src/main/java/at/petrak/hexcasting/api/client/ScryingLensOverlayRegistry.java index da72b78b42..1153ea3a8b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/client/ScryingLensOverlayRegistry.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/client/ScryingLensOverlayRegistry.java @@ -2,9 +2,12 @@ import com.google.common.collect.Lists; import com.mojang.datafixers.util.Pair; +import java.util.List; +import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; @@ -15,93 +18,86 @@ import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.NotNull; -import java.util.List; -import java.util.Vector; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - /** * Use this to make things display when the player looks at things with a Scrying Lens. - *

- * Client-side only. + * + *

Client-side only. */ public final class ScryingLensOverlayRegistry { - private static final ConcurrentMap ID_LOOKUP = new ConcurrentHashMap<>(); - // vectors are thread-safe! - private static final List> PREDICATE_LOOKUP = new Vector<>(); + private static final ConcurrentMap ID_LOOKUP = + new ConcurrentHashMap<>(); + // vectors are thread-safe! + private static final List> PREDICATE_LOOKUP = + new Vector<>(); - /** - * Add the block to display things when the player is holding a lens and looking at it. - * - * @throws IllegalArgumentException if the block is already registered. - */ - public static void addDisplayer(Block block, OverlayBuilder displayer) { - addDisplayer(BuiltInRegistries.BLOCK.getKey(block), displayer); - } + /** + * Add the block to display things when the player is holding a lens and looking at it. + * + * @throws IllegalArgumentException if the block is already registered. + */ + public static void addDisplayer(Block block, OverlayBuilder displayer) { + addDisplayer(BuiltInRegistries.BLOCK.getKey(block), displayer); + } - /** - * Add the block to display things when the player is holding a lens and looking at it. - * - * @throws IllegalArgumentException if the block ID is already registered. - */ - public static void addDisplayer(ResourceLocation blockID, OverlayBuilder displayer) { - if (ID_LOOKUP.containsKey(blockID)) { - throw new IllegalArgumentException("Already have a displayer for " + blockID); - } - ID_LOOKUP.put(blockID, displayer); - } + /** + * Add the block to display things when the player is holding a lens and looking at it. + * + * @throws IllegalArgumentException if the block ID is already registered. + */ + public static void addDisplayer(ResourceLocation blockID, OverlayBuilder displayer) { + if (ID_LOOKUP.containsKey(blockID)) { + throw new IllegalArgumentException("Already have a displayer for " + blockID); + } + ID_LOOKUP.put(blockID, displayer); + } - /** - * Display things when the player is holding a lens and looking at some block via a predicate. - *

- * These have a lower priority than the standard ID-based displays, so if an ID and predicate both match, - * this won't be displayed. - */ - public static void addPredicateDisplayer(OverlayPredicate predicate, OverlayBuilder displayer) { - PREDICATE_LOOKUP.add(new Pair<>(predicate, displayer)); - } + /** + * Display things when the player is holding a lens and looking at some block via a predicate. + * + *

These have a lower priority than the standard ID-based displays, so if an ID and predicate + * both match, this won't be displayed. + */ + public static void addPredicateDisplayer(OverlayPredicate predicate, OverlayBuilder displayer) { + PREDICATE_LOOKUP.add(new Pair<>(predicate, displayer)); + } - /** - * Internal use only. - */ - public static @NotNull List> getLines(BlockState state, BlockPos pos, - Player observer, Level world, - Direction hitFace) { - List> lines = Lists.newArrayList(); - var idLookedup = ID_LOOKUP.get(BuiltInRegistries.BLOCK.getKey(state.getBlock())); - if (idLookedup != null) { - idLookedup.addLines(lines, state, pos, observer, world, hitFace); - } + /** Internal use only. */ + public static @NotNull List> getLines( + BlockState state, BlockPos pos, Player observer, Level world, Direction hitFace) { + List> lines = Lists.newArrayList(); + var idLookedup = ID_LOOKUP.get(BuiltInRegistries.BLOCK.getKey(state.getBlock())); + if (idLookedup != null) { + idLookedup.addLines(lines, state, pos, observer, world, hitFace); + } - for (var pair : PREDICATE_LOOKUP) { - if (pair.getFirst().test(state, pos, observer, world, hitFace)) { - pair.getSecond().addLines(lines, state, pos, observer, world, hitFace); - } - } + for (var pair : PREDICATE_LOOKUP) { + if (pair.getFirst().test(state, pos, observer, world, hitFace)) { + pair.getSecond().addLines(lines, state, pos, observer, world, hitFace); + } + } - return lines; - } + return lines; + } - /** - * Return the lines displayed by the cursor: an item and some text. - *

- * The ItemStack can be empty; if it is, the text isn't shifted over for it. - */ - @FunctionalInterface - public interface OverlayBuilder { - void addLines(List> lines, - BlockState state, BlockPos pos, Player observer, - Level world, - Direction hitFace); - } + /** + * Return the lines displayed by the cursor: an item and some text. + * + *

The ItemStack can be empty; if it is, the text isn't shifted over for it. + */ + @FunctionalInterface + public interface OverlayBuilder { + void addLines( + List> lines, + BlockState state, + BlockPos pos, + Player observer, + Level world, + Direction hitFace); + } - /** - * Predicate for matching on a block state. - */ - @FunctionalInterface - public interface OverlayPredicate { - boolean test(BlockState state, BlockPos pos, Player observer, - Level world, - Direction hitFace); - } + /** Predicate for matching on a block state. */ + @FunctionalInterface + public interface OverlayPredicate { + boolean test(BlockState state, BlockPos pos, Player observer, Level world, Direction hitFace); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/item/HexHolderItem.java b/Common/src/main/java/at/petrak/hexcasting/api/item/HexHolderItem.java index a27ef900f1..9f1d334cc8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/item/HexHolderItem.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/item/HexHolderItem.java @@ -2,32 +2,30 @@ import at.petrak.hexcasting.api.casting.iota.Iota; import at.petrak.hexcasting.api.pigment.FrozenPigment; +import java.util.List; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import java.util.List; - /** * Items which can cast a packaged Hex can implement this interface. - *

- * On both the Forge and Fabric sides, the registry will be scanned for all items which implement this interface, - * and the appropriate cap/CC will be attached. + * + *

On both the Forge and Fabric sides, the registry will be scanned for all items which implement + * this interface, and the appropriate cap/CC will be attached. */ @ApiStatus.OverrideOnly public interface HexHolderItem extends MediaHolderItem { - boolean canDrawMediaFromInventory(ItemStack stack); + boolean canDrawMediaFromInventory(ItemStack stack); - boolean hasHex(ItemStack stack); + boolean hasHex(ItemStack stack); - @Nullable - List getHex(ItemStack stack, ServerLevel level); + @Nullable List getHex(ItemStack stack, ServerLevel level); - void writeHex(ItemStack stack, List program, @Nullable FrozenPigment pigment, long media); + void writeHex(ItemStack stack, List program, @Nullable FrozenPigment pigment, long media); - void clearHex(ItemStack stack); + void clearHex(ItemStack stack); - @Nullable FrozenPigment getPigment(ItemStack stack); + @Nullable FrozenPigment getPigment(ItemStack stack); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/item/IotaHolderItem.java b/Common/src/main/java/at/petrak/hexcasting/api/item/IotaHolderItem.java index c312383129..4e11f81f98 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/item/IotaHolderItem.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/item/IotaHolderItem.java @@ -6,6 +6,7 @@ import at.petrak.hexcasting.api.utils.NBTHelper; import at.petrak.hexcasting.client.ClientTickCounter; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import java.util.List; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; @@ -17,102 +18,96 @@ import net.minecraft.world.item.TooltipFlag; import org.jetbrains.annotations.Nullable; -import java.util.List; - /** * Items that store an iota to their tag can implement this interface. - *

- * On both the Forge and Fabric sides, the registry will be scanned for all items which implement this interface, - * and the appropriate cap/CC will be attached. + * + *

On both the Forge and Fabric sides, the registry will be scanned for all items which implement + * this interface, and the appropriate cap/CC will be attached. */ public interface IotaHolderItem { - /** - * If this key is set on the item, we ignore the rest of the item and render this as if it were of the - * {@link at.petrak.hexcasting.api.casting.iota.IotaType IotaType} given by the resource location. - *

- * This is not useful to the player at all. - */ - String TAG_OVERRIDE_VISUALLY = "VisualOverride"; - - @Nullable - CompoundTag readIotaTag(ItemStack stack); - - @Nullable - default Iota readIota(ItemStack stack, ServerLevel world) { - if (!(stack.getItem() instanceof IotaHolderItem dh)) { - // this should be checked via mishap beforehand - throw new IllegalArgumentException("stack's item must be an IotaHolderItem but was " + stack.getItem()); - } - - var tag = dh.readIotaTag(stack); - if (tag != null) { - return IotaType.deserialize(tag, world); - } else { - return null; - } - } - - /** - * What is this considered to contain when nothing can be read? - */ - @Nullable - default Iota emptyIota(ItemStack stack) { - return null; - } - - default int getColor(ItemStack stack) { - if (NBTHelper.hasString(stack, TAG_OVERRIDE_VISUALLY)) { - var override = NBTHelper.getString(stack, TAG_OVERRIDE_VISUALLY); - - if (override != null && ResourceLocation.isValidResourceLocation(override)) { - var key = new ResourceLocation(override); - if (HexIotaTypes.REGISTRY.containsKey(key)) { - var iotaType = HexIotaTypes.REGISTRY.get(key); - if (iotaType != null) { - return iotaType.color(); - } - } - } - - return 0xFF000000 | Mth.hsvToRgb(ClientTickCounter.getTotal() * 2 % 360 / 360F, 0.75F, 1F); - } - - var tag = this.readIotaTag(stack); - if (tag == null) { - return HexUtils.ERROR_COLOR; - } - - return IotaType.getColor(tag); - } - - /** - * @return whether it is possible to write to this IotaHolder - */ - boolean writeable(ItemStack stack); - - /** - * Write {@code null} to indicate erasing - */ - boolean canWrite(ItemStack stack, @Nullable Iota iota); - - /** - * Write {@code null} to indicate erasing - */ - void writeDatum(ItemStack stack, @Nullable Iota iota); - - static void appendHoverText(IotaHolderItem self, ItemStack stack, List components, - TooltipFlag flag) { - var datumTag = self.readIotaTag(stack); - if (datumTag != null) { - var cmp = IotaType.getDisplay(datumTag); - components.add(Component.translatable("hexcasting.spelldata.onitem", cmp)); - - if (flag.isAdvanced()) { - components.add(Component.literal("").append(NbtUtils.toPrettyComponent(datumTag))); - } - } else if (NBTHelper.hasString(stack, IotaHolderItem.TAG_OVERRIDE_VISUALLY)) { - components.add(Component.translatable("hexcasting.spelldata.onitem", - Component.translatable("hexcasting.spelldata.anything").withStyle(ChatFormatting.LIGHT_PURPLE))); - } - } + /** + * If this key is set on the item, we ignore the rest of the item and render this as if it were of + * the {@link at.petrak.hexcasting.api.casting.iota.IotaType IotaType} given by the resource + * location. + * + *

This is not useful to the player at all. + */ + String TAG_OVERRIDE_VISUALLY = "VisualOverride"; + + @Nullable CompoundTag readIotaTag(ItemStack stack); + + @Nullable default Iota readIota(ItemStack stack, ServerLevel world) { + if (!(stack.getItem() instanceof IotaHolderItem dh)) { + // this should be checked via mishap beforehand + throw new IllegalArgumentException( + "stack's item must be an IotaHolderItem but was " + stack.getItem()); + } + + var tag = dh.readIotaTag(stack); + if (tag != null) { + return IotaType.deserialize(tag, world); + } else { + return null; + } + } + + /** What is this considered to contain when nothing can be read? */ + @Nullable default Iota emptyIota(ItemStack stack) { + return null; + } + + default int getColor(ItemStack stack) { + if (NBTHelper.hasString(stack, TAG_OVERRIDE_VISUALLY)) { + var override = NBTHelper.getString(stack, TAG_OVERRIDE_VISUALLY); + + if (override != null && ResourceLocation.isValidResourceLocation(override)) { + var key = new ResourceLocation(override); + if (HexIotaTypes.REGISTRY.containsKey(key)) { + var iotaType = HexIotaTypes.REGISTRY.get(key); + if (iotaType != null) { + return iotaType.color(); + } + } + } + + return 0xFF000000 | Mth.hsvToRgb(ClientTickCounter.getTotal() * 2 % 360 / 360F, 0.75F, 1F); + } + + var tag = this.readIotaTag(stack); + if (tag == null) { + return HexUtils.ERROR_COLOR; + } + + return IotaType.getColor(tag); + } + + /** + * @return whether it is possible to write to this IotaHolder + */ + boolean writeable(ItemStack stack); + + /** Write {@code null} to indicate erasing */ + boolean canWrite(ItemStack stack, @Nullable Iota iota); + + /** Write {@code null} to indicate erasing */ + void writeDatum(ItemStack stack, @Nullable Iota iota); + + static void appendHoverText( + IotaHolderItem self, ItemStack stack, List components, TooltipFlag flag) { + var datumTag = self.readIotaTag(stack); + if (datumTag != null) { + var cmp = IotaType.getDisplay(datumTag); + components.add(Component.translatable("hexcasting.spelldata.onitem", cmp)); + + if (flag.isAdvanced()) { + components.add(Component.literal("").append(NbtUtils.toPrettyComponent(datumTag))); + } + } else if (NBTHelper.hasString(stack, IotaHolderItem.TAG_OVERRIDE_VISUALLY)) { + components.add( + Component.translatable( + "hexcasting.spelldata.onitem", + Component.translatable("hexcasting.spelldata.anything") + .withStyle(ChatFormatting.LIGHT_PURPLE))); + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/item/MediaHolderItem.java b/Common/src/main/java/at/petrak/hexcasting/api/item/MediaHolderItem.java index a4aa3cfc0a..d11a5df891 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/item/MediaHolderItem.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/item/MediaHolderItem.java @@ -6,62 +6,62 @@ /** * Items which can store Media can implement this interface. - *

- * On both the Forge and Fabric sides, the registry will be scanned for all items which implement this interface, - * and the appropriate cap/CC will be attached. + * + *

On both the Forge and Fabric sides, the registry will be scanned for all items which implement + * this interface, and the appropriate cap/CC will be attached. */ @ApiStatus.OverrideOnly public interface MediaHolderItem { - long getMedia(ItemStack stack); + long getMedia(ItemStack stack); - long getMaxMedia(ItemStack stack); + long getMaxMedia(ItemStack stack); - void setMedia(ItemStack stack, long media); + void setMedia(ItemStack stack, long media); - boolean canProvideMedia(ItemStack stack); + boolean canProvideMedia(ItemStack stack); - boolean canRecharge(ItemStack stack); + boolean canRecharge(ItemStack stack); - default float getMediaFullness(ItemStack stack) { - long max = getMaxMedia(stack); - if (max == 0) { - return 0; - } - return (float) getMedia(stack) / (float) max; - } + default float getMediaFullness(ItemStack stack) { + long max = getMaxMedia(stack); + if (max == 0) { + return 0; + } + return (float) getMedia(stack) / (float) max; + } - default long withdrawMedia(ItemStack stack, long cost, boolean simulate) { - var mediaHere = getMedia(stack); - if (cost < 0) { - cost = mediaHere; - } - if (!simulate) { - var mediaLeft = mediaHere - cost; - setMedia(stack, mediaLeft); - } - return Math.min(cost, mediaHere); - } + default long withdrawMedia(ItemStack stack, long cost, boolean simulate) { + var mediaHere = getMedia(stack); + if (cost < 0) { + cost = mediaHere; + } + if (!simulate) { + var mediaLeft = mediaHere - cost; + setMedia(stack, mediaLeft); + } + return Math.min(cost, mediaHere); + } - default long insertMedia(ItemStack stack, long amount, boolean simulate) { - var mediaHere = getMedia(stack); - long emptySpace = getMaxMedia(stack) - mediaHere; - if (emptySpace <= 0) { - return 0; - } - if (amount < 0) { - amount = emptySpace; - } + default long insertMedia(ItemStack stack, long amount, boolean simulate) { + var mediaHere = getMedia(stack); + long emptySpace = getMaxMedia(stack) - mediaHere; + if (emptySpace <= 0) { + return 0; + } + if (amount < 0) { + amount = emptySpace; + } - long inserting = Math.min(amount, emptySpace); + long inserting = Math.min(amount, emptySpace); - if (!simulate) { - var newMedia = mediaHere + inserting; - setMedia(stack, newMedia); - } - return inserting; - } + if (!simulate) { + var newMedia = mediaHere + inserting; + setMedia(stack, newMedia); + } + return inserting; + } - default int getConsumptionPriority(ItemStack stack) { - return ADMediaHolder.BATTERY_PRIORITY; - } + default int getConsumptionPriority(ItemStack stack) { + return ADMediaHolder.BATTERY_PRIORITY; + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/item/PigmentItem.java b/Common/src/main/java/at/petrak/hexcasting/api/item/PigmentItem.java index 06829c4b16..b7c38d23ba 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/item/PigmentItem.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/item/PigmentItem.java @@ -1,18 +1,17 @@ package at.petrak.hexcasting.api.item; import at.petrak.hexcasting.api.pigment.ColorProvider; +import java.util.UUID; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.ApiStatus; -import java.util.UUID; - /** * Items which can be used as a colorizer can implement this interface. - *

- * On both the Forge and Fabric sides, the registry will be scanned for all items which implement this interface, - * and the appropriate cap/CC will be attached. + * + *

On both the Forge and Fabric sides, the registry will be scanned for all items which implement + * this interface, and the appropriate cap/CC will be attached. */ @ApiStatus.OverrideOnly public interface PigmentItem { - ColorProvider provideColor(ItemStack stack, UUID owner); + ColorProvider provideColor(ItemStack stack, UUID owner); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/item/VariantItem.java b/Common/src/main/java/at/petrak/hexcasting/api/item/VariantItem.java index 6bdca2d2d7..d091ac3bc5 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/item/VariantItem.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/item/VariantItem.java @@ -4,29 +4,28 @@ import net.minecraft.world.item.ItemStack; /** - * Items that have multiple different otherwise identical visual variants can implement this interface. - *

- * On both the Forge and Fabric sides, the registry will be scanned for all items which implement this interface, - * and the appropriate cap/CC will be attached. + * Items that have multiple different otherwise identical visual variants can implement this + * interface. + * + *

On both the Forge and Fabric sides, the registry will be scanned for all items which implement + * this interface, and the appropriate cap/CC will be attached. */ public interface VariantItem { - String TAG_VARIANT = "variant"; + String TAG_VARIANT = "variant"; - int numVariants(); + int numVariants(); - default int getVariant(ItemStack stack) { - return NBTHelper.getInt(stack, TAG_VARIANT, 0); - } + default int getVariant(ItemStack stack) { + return NBTHelper.getInt(stack, TAG_VARIANT, 0); + } - default void setVariant(ItemStack stack, int variant) { - NBTHelper.putInt(stack, TAG_VARIANT, clampVariant(variant)); - } + default void setVariant(ItemStack stack, int variant) { + NBTHelper.putInt(stack, TAG_VARIANT, clampVariant(variant)); + } - default int clampVariant(int variant) { - if (variant < 0) - return 0; - if (variant >= numVariants()) - return numVariants() - 1; - return variant; - } + default int clampVariant(int variant) { + if (variant < 0) return 0; + if (variant >= numVariants()) return numVariants() - 1; + return variant; + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/misc/DiscoveryHandlers.java b/Common/src/main/java/at/petrak/hexcasting/api/misc/DiscoveryHandlers.java index d94f526066..8e502f4e65 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/misc/DiscoveryHandlers.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/misc/DiscoveryHandlers.java @@ -1,26 +1,26 @@ package at.petrak.hexcasting.api.misc; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; - import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; public class DiscoveryHandlers { - private static final List> DEBUG_DISCOVERER = new ArrayList<>(); + private static final List> DEBUG_DISCOVERER = + new ArrayList<>(); - public static ItemStack findDebugItem(Player player, String type) { - for (var discoverer : DEBUG_DISCOVERER) { - var stack = discoverer.apply(player, type); - if (!stack.isEmpty()) { - return stack; - } - } - return ItemStack.EMPTY; - } + public static ItemStack findDebugItem(Player player, String type) { + for (var discoverer : DEBUG_DISCOVERER) { + var stack = discoverer.apply(player, type); + if (!stack.isEmpty()) { + return stack; + } + } + return ItemStack.EMPTY; + } - public static void addDebugItemDiscoverer(BiFunction discoverer) { - DEBUG_DISCOVERER.add(discoverer); - } + public static void addDebugItemDiscoverer(BiFunction discoverer) { + DEBUG_DISCOVERER.add(discoverer); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/misc/MediaConstants.java b/Common/src/main/java/at/petrak/hexcasting/api/misc/MediaConstants.java index f09571070e..9d8586f1c7 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/misc/MediaConstants.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/misc/MediaConstants.java @@ -1,10 +1,10 @@ package at.petrak.hexcasting.api.misc; public final class MediaConstants { - public static final long DUST_UNIT = 10000; - public static final long SHARD_UNIT = 5 * DUST_UNIT; - public static final long CRYSTAL_UNIT = 10 * DUST_UNIT; + public static final long DUST_UNIT = 10000; + public static final long SHARD_UNIT = 5 * DUST_UNIT; + public static final long CRYSTAL_UNIT = 10 * DUST_UNIT; - public static final long QUENCHED_SHARD_UNIT = 3 * CRYSTAL_UNIT; - public static final long QUENCHED_BLOCK_UNIT = 4 * QUENCHED_SHARD_UNIT; + public static final long QUENCHED_SHARD_UNIT = 3 * CRYSTAL_UNIT; + public static final long QUENCHED_BLOCK_UNIT = 4 * QUENCHED_SHARD_UNIT; } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/misc/Result.java b/Common/src/main/java/at/petrak/hexcasting/api/misc/Result.java index 3dfcc73c23..e4bc06e599 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/misc/Result.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/misc/Result.java @@ -1,84 +1,80 @@ package at.petrak.hexcasting.api.misc; import com.mojang.datafixers.util.Unit; - import java.util.function.Consumer; import java.util.function.Function; -/** - * I'm sick and tired of not having a result class god dammit - */ +/** I'm sick and tired of not having a result class god dammit */ public abstract sealed class Result { - public static final class Ok extends Result { - public final T ok; + public static final class Ok extends Result { + public final T ok; - public Ok(T ok) { - this.ok = ok; - } - } + public Ok(T ok) { + this.ok = ok; + } + } - public static final class Err extends Result { - public final E err; + public static final class Err extends Result { + public final E err; - public Err(E err) { - this.err = err; - } - } + public Err(E err) { + this.err = err; + } + } - public boolean isOk() { - return this instanceof Ok; - } + public boolean isOk() { + return this instanceof Ok; + } - public boolean isErr() { - return this instanceof Err; - } + public boolean isErr() { + return this instanceof Err; + } - public T unwrap() { - if (this instanceof Ok ok) { - return ok.ok; - } else { - throw new IllegalStateException("tried to unwrap an Err"); - } - } + public T unwrap() { + if (this instanceof Ok ok) { + return ok.ok; + } else { + throw new IllegalStateException("tried to unwrap an Err"); + } + } - public E unwrapErr() { - if (this instanceof Err err) { - return err.err; - } else { - throw new IllegalStateException("tried to unwrapErr an Ok"); - } - } + public E unwrapErr() { + if (this instanceof Err err) { + return err.err; + } else { + throw new IllegalStateException("tried to unwrapErr an Ok"); + } + } - public Result match(Function okBranch, Function errBranch) { - if (this instanceof Ok ok) { - return new Result.Ok<>(okBranch.apply(ok.ok)); - } else if (this instanceof Err err) { - return new Result.Err<>(errBranch.apply(err.err)); - } else { - throw new IllegalStateException(); - } - } + public Result match(Function okBranch, Function errBranch) { + if (this instanceof Ok ok) { + return new Result.Ok<>(okBranch.apply(ok.ok)); + } else if (this instanceof Err err) { + return new Result.Err<>(errBranch.apply(err.err)); + } else { + throw new IllegalStateException(); + } + } - public void matchVoid(Consumer okBranch, Consumer errBranch) { - this.match( - ok -> { - okBranch.accept(ok); - return Unit.INSTANCE; - }, - err -> { - errBranch.accept(err); - return Unit.INSTANCE; - } - ); - } + public void matchVoid(Consumer okBranch, Consumer errBranch) { + this.match( + ok -> { + okBranch.accept(ok); + return Unit.INSTANCE; + }, + err -> { + errBranch.accept(err); + return Unit.INSTANCE; + }); + } - public U collapse(Function okBranch, Function errBranch) { - if (this instanceof Ok ok) { - return okBranch.apply(ok.ok); - } else if (this instanceof Err err) { - return errBranch.apply(err.err); - } else { - throw new IllegalStateException(); - } - } + public U collapse(Function okBranch, Function errBranch) { + if (this instanceof Ok ok) { + return okBranch.apply(ok.ok); + } else if (this instanceof Err err) { + return errBranch.apply(err.err); + } else { + throw new IllegalStateException(); + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/misc/TriPredicate.java b/Common/src/main/java/at/petrak/hexcasting/api/misc/TriPredicate.java index 04f70220ee..4da3ae414e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/misc/TriPredicate.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/misc/TriPredicate.java @@ -1,9 +1,7 @@ package at.petrak.hexcasting.api.misc; -/** - * Society if java actually had first-class function support - */ +/** Society if java actually had first-class function support */ @FunctionalInterface public interface TriPredicate { - boolean test(A a, B b, C c); + boolean test(A a, B b, C c); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexApiMessages.java b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexApiMessages.java index 16dbbb6d0f..c6bbcd1364 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexApiMessages.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexApiMessages.java @@ -14,39 +14,39 @@ @ApiStatus.Internal public final class HexApiMessages { - private static SimpleChannel channel; - private static Function sentinelMessage; - private static Function colorizerMessage; - private static BiFunction particleSprayMessage; - - public static void setSyncChannel(SimpleChannel channel, - Function sentinelMessage, - Function colorizerMessage, - BiFunction particleSprayMessage) { - if (HexApiMessages.channel != null) - throw new IllegalStateException("Already set sync channel! If you're not Hex, you shouldn't be calling - this."); - HexApiMessages.channel = channel; - HexApiMessages.sentinelMessage = sentinelMessage; - HexApiMessages.colorizerMessage = colorizerMessage; - HexApiMessages.particleSprayMessage = particleSprayMessage; - } - - public static SimpleChannel getChannel() { - return channel; - } - - public static Object getColorizerMessage(FrozenColorizer colorizer) { - return colorizerMessage.apply(colorizer); - } - - public static Object getSentinelMessage(Sentinel colorizer) { - return sentinelMessage.apply(colorizer); - } - - public static Object getParticleSprayMessage(ParticleSpray spray, FrozenColorizer colorizer) { - return particleSprayMessage.apply(spray, colorizer); - } + private static SimpleChannel channel; + private static Function sentinelMessage; + private static Function colorizerMessage; + private static BiFunction particleSprayMessage; + + public static void setSyncChannel(SimpleChannel channel, + Function sentinelMessage, + Function colorizerMessage, + BiFunction particleSprayMessage) { + if (HexApiMessages.channel != null) + throw new IllegalStateException("Already set sync channel! If you're not Hex, you shouldn't be calling + this."); + HexApiMessages.channel = channel; + HexApiMessages.sentinelMessage = sentinelMessage; + HexApiMessages.colorizerMessage = colorizerMessage; + HexApiMessages.particleSprayMessage = particleSprayMessage; + } + + public static SimpleChannel getChannel() { + return channel; + } + + public static Object getColorizerMessage(FrozenColorizer colorizer) { + return colorizerMessage.apply(colorizer); + } + + public static Object getSentinelMessage(Sentinel colorizer) { + return sentinelMessage.apply(colorizer); + } + + public static Object getParticleSprayMessage(ParticleSpray spray, FrozenColorizer colorizer) { + return particleSprayMessage.apply(spray, colorizer); + } } */ 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 6f17226b16..cf068cd27f 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 @@ -2,159 +2,167 @@ import at.petrak.hexcasting.api.HexAPI; import at.petrak.hexcasting.api.misc.MediaConstants; +import java.util.List; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Tier; import net.minecraft.world.item.Tiers; import net.minecraft.world.level.Level; -import java.util.List; - public class HexConfig { - public interface CommonConfigAccess { - - long dustMediaAmount(); - - long shardMediaAmount(); - - long chargedCrystalMediaAmount(); - - double mediaToHealthRate(); + public interface CommonConfigAccess { - int cypherCooldown(); + long dustMediaAmount(); - int trinketCooldown(); + long shardMediaAmount(); - int artifactCooldown(); + long chargedCrystalMediaAmount(); - long DEFAULT_DUST_MEDIA_AMOUNT = MediaConstants.DUST_UNIT; - long DEFAULT_SHARD_MEDIA_AMOUNT = MediaConstants.SHARD_UNIT; - long DEFAULT_CHARGED_MEDIA_AMOUNT = MediaConstants.CRYSTAL_UNIT; - double DEFAULT_MEDIA_TO_HEALTH_RATE = 2 * MediaConstants.CRYSTAL_UNIT / 20.0; + double mediaToHealthRate(); - int DEFAULT_CYPHER_COOLDOWN = 8; - int DEFAULT_TRINKET_COOLDOWN = 5; - int DEFAULT_ARTIFACT_COOLDOWN = 3; + int cypherCooldown(); - } + int trinketCooldown(); - public interface ClientConfigAccess { - boolean ctrlTogglesOffStrokeOrder(); + int artifactCooldown(); - boolean invertSpellbookScrollDirection(); + long DEFAULT_DUST_MEDIA_AMOUNT = MediaConstants.DUST_UNIT; + long DEFAULT_SHARD_MEDIA_AMOUNT = MediaConstants.SHARD_UNIT; + long DEFAULT_CHARGED_MEDIA_AMOUNT = MediaConstants.CRYSTAL_UNIT; + double DEFAULT_MEDIA_TO_HEALTH_RATE = 2 * MediaConstants.CRYSTAL_UNIT / 20.0; - boolean invertAbacusScrollDirection(); + int DEFAULT_CYPHER_COOLDOWN = 8; + int DEFAULT_TRINKET_COOLDOWN = 5; + int DEFAULT_ARTIFACT_COOLDOWN = 3; + } - double gridSnapThreshold(); + public interface ClientConfigAccess { + boolean ctrlTogglesOffStrokeOrder(); - boolean clickingTogglesDrawing(); + boolean invertSpellbookScrollDirection(); - 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; - } + boolean invertAbacusScrollDirection(); - public interface ServerConfigAccess { - int opBreakHarvestLevelBecauseForgeThoughtItWasAGoodIdeaToImplementHarvestTiersUsingAnHonestToGodTopoSort(); + double gridSnapThreshold(); - int maxOpCount(); + boolean clickingTogglesDrawing(); - int maxSpellCircleLength(); + 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; + } - boolean isActionAllowed(ResourceLocation actionID); + public interface ServerConfigAccess { + int + opBreakHarvestLevelBecauseForgeThoughtItWasAGoodIdeaToImplementHarvestTiersUsingAnHonestToGodTopoSort(); - boolean isActionAllowedInCircles(ResourceLocation actionID); + int maxOpCount(); - boolean doVillagersTakeOffenseAtMindMurder(); + int maxSpellCircleLength(); - // fun fact, although dimension keys are a RegistryHolder, they aren't a registry, so i can't do tags - boolean canTeleportInThisDimension(ResourceKey dimension); + boolean isActionAllowed(ResourceLocation actionID); - boolean trueNameHasAmbit(); + boolean isActionAllowedInCircles(ResourceLocation actionID); - int DEFAULT_MAX_OP_COUNT = 1_000_000; - int DEFAULT_MAX_SPELL_CIRCLE_LENGTH = 1024; - int DEFAULT_OP_BREAK_HARVEST_LEVEL = 3; + boolean doVillagersTakeOffenseAtMindMurder(); - boolean DEFAULT_VILLAGERS_DISLIKE_MIND_MURDER = true; + // fun fact, although dimension keys are a RegistryHolder, they aren't a registry, so i can't do + // tags + boolean canTeleportInThisDimension(ResourceKey dimension); - List DEFAULT_DIM_TP_DENYLIST = List.of("twilightforest:twilight_forest"); + boolean trueNameHasAmbit(); - boolean DEFAULT_TRUE_NAME_HAS_AMBIT = true; + int DEFAULT_MAX_OP_COUNT = 1_000_000; + int DEFAULT_MAX_SPELL_CIRCLE_LENGTH = 1024; + int DEFAULT_OP_BREAK_HARVEST_LEVEL = 3; - default Tier opBreakHarvestLevel() { - return switch (this.opBreakHarvestLevelBecauseForgeThoughtItWasAGoodIdeaToImplementHarvestTiersUsingAnHonestToGodTopoSort()) { - case 0 -> Tiers.WOOD; - case 1 -> Tiers.STONE; - case 2 -> Tiers.IRON; - case 3 -> Tiers.DIAMOND; - case 4 -> Tiers.NETHERITE; - default -> throw new RuntimeException("please only return a value in 0<=x<=4"); - }; - } - } + boolean DEFAULT_VILLAGERS_DISLIKE_MIND_MURDER = true; - // Simple extensions for resource location configs - public static boolean anyMatch(List keys, ResourceLocation key) { - for (String s : keys) { - if (ResourceLocation.isValidResourceLocation(s)) { - var rl = new ResourceLocation(s); - if (rl.equals(key)) { - return true; - } - } - } - return false; - } + List DEFAULT_DIM_TP_DENYLIST = List.of("twilightforest:twilight_forest"); - public static boolean noneMatch(List keys, ResourceLocation key) { - return !anyMatch(keys, key); - } + boolean DEFAULT_TRUE_NAME_HAS_AMBIT = true; - public static boolean anyMatchResLoc(List keys, ResourceLocation key) { - return keys.stream().anyMatch(key::equals); - } + default Tier opBreakHarvestLevel() { + return switch (this + .opBreakHarvestLevelBecauseForgeThoughtItWasAGoodIdeaToImplementHarvestTiersUsingAnHonestToGodTopoSort()) { + case 0 -> Tiers.WOOD; + case 1 -> Tiers.STONE; + case 2 -> Tiers.IRON; + case 3 -> Tiers.DIAMOND; + case 4 -> Tiers.NETHERITE; + default -> throw new RuntimeException("please only return a value in 0<=x<=4"); + }; + } + } - // oh man this is aesthetically pleasing - private static CommonConfigAccess common = null; - private static ClientConfigAccess client = null; - private static ServerConfigAccess server = null; + // Simple extensions for resource location configs + public static boolean anyMatch(List keys, ResourceLocation key) { + for (String s : keys) { + if (ResourceLocation.isValidResourceLocation(s)) { + var rl = new ResourceLocation(s); + if (rl.equals(key)) { + return true; + } + } + } + return false; + } - public static CommonConfigAccess common() { - return common; - } - - public static ClientConfigAccess client() { - return client; - } - - public static ServerConfigAccess server() { - return server; - } - - public static void setCommon(CommonConfigAccess access) { - if (common != null) { - HexAPI.LOGGER.warn("CommonConfigAccess was replaced! Old {} New {}", - common.getClass().getName(), access.getClass().getName()); - } - common = access; - } - - public static void setClient(ClientConfigAccess access) { - if (client != null) { - HexAPI.LOGGER.warn("ClientConfigAccess was replaced! Old {} New {}", - client.getClass().getName(), access.getClass().getName()); - } - client = access; - } - - public static void setServer(ServerConfigAccess access) { - if (server != null) { - HexAPI.LOGGER.warn("ServerConfigAccess was replaced! Old {} New {}", - server.getClass().getName(), access.getClass().getName()); - } - server = access; - } + public static boolean noneMatch(List keys, ResourceLocation key) { + return !anyMatch(keys, key); + } + + public static boolean anyMatchResLoc( + List keys, ResourceLocation key) { + return keys.stream().anyMatch(key::equals); + } + + // oh man this is aesthetically pleasing + private static CommonConfigAccess common = null; + private static ClientConfigAccess client = null; + private static ServerConfigAccess server = null; + + public static CommonConfigAccess common() { + return common; + } + + public static ClientConfigAccess client() { + return client; + } + + public static ServerConfigAccess server() { + return server; + } + + public static void setCommon(CommonConfigAccess access) { + if (common != null) { + HexAPI.LOGGER.warn( + "CommonConfigAccess was replaced! Old {} New {}", + common.getClass().getName(), + access.getClass().getName()); + } + common = access; + } + + public static void setClient(ClientConfigAccess access) { + if (client != null) { + HexAPI.LOGGER.warn( + "ClientConfigAccess was replaced! Old {} New {}", + client.getClass().getName(), + access.getClass().getName()); + } + client = access; + } + + public static void setServer(ServerConfigAccess access) { + if (server != null) { + HexAPI.LOGGER.warn( + "ServerConfigAccess was replaced! Old {} New {}", + server.getClass().getName(), + access.getClass().getName()); + } + server = access; + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexStatistics.java b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexStatistics.java index fd38fdc7bb..808931cc56 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexStatistics.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexStatistics.java @@ -1,5 +1,7 @@ package at.petrak.hexcasting.api.mod; +import static at.petrak.hexcasting.api.HexAPI.modLoc; + import at.petrak.hexcasting.api.misc.MediaConstants; import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; @@ -7,24 +9,30 @@ import net.minecraft.stats.StatFormatter; import net.minecraft.stats.Stats; -import static at.petrak.hexcasting.api.HexAPI.modLoc; - public class HexStatistics { - public static final ResourceLocation MEDIA_USED = makeCustomStat("media_used", - mediamount -> StatFormatter.DEFAULT.format((int) (mediamount / MediaConstants.DUST_UNIT))); - public static final ResourceLocation MEDIA_OVERCAST = makeCustomStat("media_overcast", - mediamount -> StatFormatter.DEFAULT.format((int) (mediamount / MediaConstants.DUST_UNIT))); - public static final ResourceLocation PATTERNS_DRAWN = makeCustomStat("patterns_drawn", StatFormatter.DEFAULT); - public static final ResourceLocation SPELLS_CAST = makeCustomStat("spells_cast", StatFormatter.DEFAULT); + public static final ResourceLocation MEDIA_USED = + makeCustomStat( + "media_used", + mediamount -> + StatFormatter.DEFAULT.format((int) (mediamount / MediaConstants.DUST_UNIT))); + public static final ResourceLocation MEDIA_OVERCAST = + makeCustomStat( + "media_overcast", + mediamount -> + StatFormatter.DEFAULT.format((int) (mediamount / MediaConstants.DUST_UNIT))); + public static final ResourceLocation PATTERNS_DRAWN = + makeCustomStat("patterns_drawn", StatFormatter.DEFAULT); + public static final ResourceLocation SPELLS_CAST = + makeCustomStat("spells_cast", StatFormatter.DEFAULT); - public static void register() { - // wake up java - } + public static void register() { + // wake up java + } - private static ResourceLocation makeCustomStat(String key, StatFormatter formatter) { - ResourceLocation resourcelocation = modLoc(key); - Registry.register(BuiltInRegistries.CUSTOM_STAT, key, resourcelocation); - Stats.CUSTOM.get(resourcelocation, formatter); - return resourcelocation; - } + private static ResourceLocation makeCustomStat(String key, StatFormatter formatter) { + ResourceLocation resourcelocation = modLoc(key); + Registry.register(BuiltInRegistries.CUSTOM_STAT, key, resourcelocation); + Stats.CUSTOM.get(resourcelocation, formatter); + return resourcelocation; + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexTags.java b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexTags.java index 238afae719..6f3e64a179 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexTags.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexTags.java @@ -1,5 +1,7 @@ package at.petrak.hexcasting.api.mod; +import static at.petrak.hexcasting.api.HexAPI.modLoc; + import at.petrak.hexcasting.api.casting.ActionRegistryEntry; import at.petrak.hexcasting.xplat.IXplatAbstractions; import net.minecraft.core.registries.Registries; @@ -9,78 +11,76 @@ import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; -import static at.petrak.hexcasting.api.HexAPI.modLoc; - public class HexTags { - public static final class Items { - public static final TagKey EDIFIED_LOGS = create("edified_logs"); - public static final TagKey EDIFIED_PLANKS = create("edified_planks"); - public static final TagKey STAVES = create("staves"); - public static final TagKey PHIAL_BASE = create("phial_base"); - public static final TagKey GRANTS_ROOT_ADVANCEMENT = create("grants_root_advancement"); - public static final TagKey SEAL_MATERIALS = create("seal_materials"); - - public static final TagKey IMPETI = create("impeti"); - public static final TagKey DIRECTRICES = create("directrices"); - public static final TagKey MINDFLAYED_CIRCLE_COMPONENTS = create("brainswept_circle_components"); - - public static TagKey create(String name) { - return create(modLoc(name)); - } - - public static TagKey create(ResourceLocation id) { - return TagKey.create(Registries.ITEM, id); - } - } - - public static final class Blocks { - public static final TagKey EDIFIED_LOGS = create("edified_logs"); - public static final TagKey EDIFIED_PLANKS = create("edified_planks"); - - - public static final TagKey IMPETI = create("impeti"); - public static final TagKey DIRECTRICES = create("directrices"); - public static final TagKey MINDFLAYED_CIRCLE_COMPONENTS = create("brainswept_circle_components"); - - // Used to determine what blocks should be replaced with air by OpDestroyFluid - public static final TagKey WATER_PLANTS = create("water_plants"); - - public static final TagKey CHEAP_TO_BREAK_BLOCK = create("cheap_to_break_block"); - - public static TagKey create(String name) { - return TagKey.create(Registries.BLOCK, modLoc(name)); - } - } - - public static final class Entities { - public static final TagKey> STICKY_TELEPORTERS = create("sticky_teleporters"); - public static final TagKey> CANNOT_TELEPORT = create("cannot_teleport"); - - public static final TagKey> NO_BRAINSWEEPING = create("cannot_brainsweep"); - - public static TagKey> create(String name) { - return TagKey.create(Registries.ENTITY_TYPE, modLoc(name)); - } - } - - public static final class Actions { - /** - * Actions with this tag can't be used until the caster is enlightened and send the - * "am I not skilled enough" message - */ - public static final TagKey REQUIRES_ENLIGHTENMENT = create("requires_enlightenment"); - /** - * Actions where the pattern is calculated per-world - */ - public static final TagKey PER_WORLD_PATTERN = create("per_world_pattern"); - - /** - * Actions that can cause Blind Diversion - */ - public static final TagKey CAN_START_ENLIGHTEN = create("can_start_enlighten"); - - public static TagKey create(String name) { - return TagKey.create(IXplatAbstractions.INSTANCE.getActionRegistry().key(), modLoc(name)); - } - } + public static final class Items { + public static final TagKey EDIFIED_LOGS = create("edified_logs"); + public static final TagKey EDIFIED_PLANKS = create("edified_planks"); + public static final TagKey STAVES = create("staves"); + public static final TagKey PHIAL_BASE = create("phial_base"); + public static final TagKey GRANTS_ROOT_ADVANCEMENT = create("grants_root_advancement"); + public static final TagKey SEAL_MATERIALS = create("seal_materials"); + + public static final TagKey IMPETI = create("impeti"); + public static final TagKey DIRECTRICES = create("directrices"); + public static final TagKey MINDFLAYED_CIRCLE_COMPONENTS = + create("brainswept_circle_components"); + + public static TagKey create(String name) { + return create(modLoc(name)); + } + + public static TagKey create(ResourceLocation id) { + return TagKey.create(Registries.ITEM, id); + } + } + + public static final class Blocks { + public static final TagKey EDIFIED_LOGS = create("edified_logs"); + public static final TagKey EDIFIED_PLANKS = create("edified_planks"); + + public static final TagKey IMPETI = create("impeti"); + public static final TagKey DIRECTRICES = create("directrices"); + public static final TagKey MINDFLAYED_CIRCLE_COMPONENTS = + create("brainswept_circle_components"); + + // Used to determine what blocks should be replaced with air by OpDestroyFluid + public static final TagKey WATER_PLANTS = create("water_plants"); + + public static final TagKey CHEAP_TO_BREAK_BLOCK = create("cheap_to_break_block"); + + public static TagKey create(String name) { + return TagKey.create(Registries.BLOCK, modLoc(name)); + } + } + + public static final class Entities { + public static final TagKey> STICKY_TELEPORTERS = create("sticky_teleporters"); + public static final TagKey> CANNOT_TELEPORT = create("cannot_teleport"); + + public static final TagKey> NO_BRAINSWEEPING = create("cannot_brainsweep"); + + public static TagKey> create(String name) { + return TagKey.create(Registries.ENTITY_TYPE, modLoc(name)); + } + } + + public static final class Actions { + /** + * Actions with this tag can't be used until the caster is enlightened and send the "am I not + * skilled enough" message + */ + public static final TagKey REQUIRES_ENLIGHTENMENT = + create("requires_enlightenment"); + + /** Actions where the pattern is calculated per-world */ + public static final TagKey PER_WORLD_PATTERN = create("per_world_pattern"); + + /** Actions that can cause Blind Diversion */ + public static final TagKey CAN_START_ENLIGHTEN = + create("can_start_enlighten"); + + public static TagKey create(String name) { + return TagKey.create(IXplatAbstractions.INSTANCE.getActionRegistry().key(), modLoc(name)); + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/pigment/ColorProvider.java b/Common/src/main/java/at/petrak/hexcasting/api/pigment/ColorProvider.java index 8c0611db2e..86e54a0e16 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/pigment/ColorProvider.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/pigment/ColorProvider.java @@ -5,46 +5,47 @@ import net.minecraft.world.phys.Vec3; public abstract class ColorProvider { - /** - * Implers, impl this function - */ - protected abstract int getRawColor(float time, Vec3 position); - - private static final int[] MINIMUM_LUMINANCE_COLOR_WHEEL = { - 0xFF200000, 0xFF202000, 0xFF002000, 0xFF002020, 0xFF000020, 0xFF200020 - }; - - /** - * Gets a color with a minimum luminance applied. - * - * @param time absolute world time in ticks - * @param position a position for the icosahedron, a randomish number for particles. - * @return an AARRGGBB color. - */ - public final int getColor(float time, Vec3 position) { - int raw = this.getRawColor(time, position); - - var r = FastColor.ARGB32.red(raw); - var g = FastColor.ARGB32.green(raw); - var b = FastColor.ARGB32.blue(raw); - double luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 0xFF; // Standard relative luminance calculation - - if (luminance < 0.05) { - int rawMod = ADPigment.morphBetweenColors(MINIMUM_LUMINANCE_COLOR_WHEEL, new Vec3(0.1, 0.1, 0.1), - time / 20 / 20, position); - - r += FastColor.ARGB32.red(rawMod); - g += FastColor.ARGB32.green(rawMod); - b += FastColor.ARGB32.blue(rawMod); - } - - return 0xff_000000 | (r << 16) | (g << 8) | b; - } - - public static final ColorProvider MISSING = new ColorProvider() { - @Override - protected int getRawColor(float time, Vec3 position) { - return 0xFF_ff00dc; - } - }; + /** Implers, impl this function */ + protected abstract int getRawColor(float time, Vec3 position); + + private static final int[] MINIMUM_LUMINANCE_COLOR_WHEEL = { + 0xFF200000, 0xFF202000, 0xFF002000, 0xFF002020, 0xFF000020, 0xFF200020 + }; + + /** + * Gets a color with a minimum luminance applied. + * + * @param time absolute world time in ticks + * @param position a position for the icosahedron, a randomish number for particles. + * @return an AARRGGBB color. + */ + public final int getColor(float time, Vec3 position) { + int raw = this.getRawColor(time, position); + + var r = FastColor.ARGB32.red(raw); + var g = FastColor.ARGB32.green(raw); + var b = FastColor.ARGB32.blue(raw); + double luminance = + (0.2126 * r + 0.7152 * g + 0.0722 * b) / 0xFF; // Standard relative luminance calculation + + if (luminance < 0.05) { + int rawMod = + ADPigment.morphBetweenColors( + MINIMUM_LUMINANCE_COLOR_WHEEL, new Vec3(0.1, 0.1, 0.1), time / 20 / 20, position); + + r += FastColor.ARGB32.red(rawMod); + g += FastColor.ARGB32.green(rawMod); + b += FastColor.ARGB32.blue(rawMod); + } + + return 0xff_000000 | (r << 16) | (g << 8) | b; + } + + public static final ColorProvider MISSING = + new ColorProvider() { + @Override + protected int getRawColor(float time, Vec3 position) { + return 0xFF_ff00dc; + } + }; } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/pigment/FrozenPigment.java b/Common/src/main/java/at/petrak/hexcasting/api/pigment/FrozenPigment.java index f5cd478e0e..790222b90b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/pigment/FrozenPigment.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/pigment/FrozenPigment.java @@ -2,49 +2,48 @@ import at.petrak.hexcasting.common.lib.HexItems; import at.petrak.hexcasting.xplat.IXplatAbstractions; +import java.util.UUID; +import java.util.function.Supplier; import net.minecraft.Util; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.item.ItemStack; -import java.util.UUID; -import java.util.function.Supplier; - /** * A snapshot of a pigment item and its owner. - *

- * Due to capabilities being really slow to query many times a tick on Forge, this returns a colorizer supplier. - * Get it once, and then query it a lot. + * + *

Due to capabilities being really slow to query many times a tick on Forge, this returns a + * colorizer supplier. Get it once, and then query it a lot. */ public record FrozenPigment(ItemStack item, UUID owner) { - public static final String TAG_STACK = "stack"; - public static final String TAG_OWNER = "owner"; - - public static final Supplier DEFAULT = - () -> new FrozenPigment(new ItemStack(HexItems.DEFAULT_PIGMENT), Util.NIL_UUID); - - public CompoundTag serializeToNBT() { - var out = new CompoundTag(); - out.put(TAG_STACK, this.item.save(new CompoundTag())); - out.putUUID(TAG_OWNER, this.owner); - return out; - } - - public static FrozenPigment fromNBT(CompoundTag tag) { - if (tag.isEmpty()) { - return FrozenPigment.DEFAULT.get(); - } - try { - CompoundTag stackTag = tag.getCompound(TAG_STACK); - var stack = ItemStack.of(stackTag); - var uuid = tag.getUUID(TAG_OWNER); - return new FrozenPigment(stack, uuid); - } catch (NullPointerException exn) { - return FrozenPigment.DEFAULT.get(); - } - } - - public ColorProvider getColorProvider() { - return IXplatAbstractions.INSTANCE.getColorProvider(this); - } + public static final String TAG_STACK = "stack"; + public static final String TAG_OWNER = "owner"; + + public static final Supplier DEFAULT = + () -> new FrozenPigment(new ItemStack(HexItems.DEFAULT_PIGMENT), Util.NIL_UUID); + + public CompoundTag serializeToNBT() { + var out = new CompoundTag(); + out.put(TAG_STACK, this.item.save(new CompoundTag())); + out.putUUID(TAG_OWNER, this.owner); + return out; + } + + public static FrozenPigment fromNBT(CompoundTag tag) { + if (tag.isEmpty()) { + return FrozenPigment.DEFAULT.get(); + } + try { + CompoundTag stackTag = tag.getCompound(TAG_STACK); + var stack = ItemStack.of(stackTag); + var uuid = tag.getUUID(TAG_OWNER); + return new FrozenPigment(stack, uuid); + } catch (NullPointerException exn) { + return FrozenPigment.DEFAULT.get(); + } + } + + public ColorProvider getColorProvider() { + return IXplatAbstractions.INSTANCE.getColorProvider(this); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/player/AltioraAbility.java b/Common/src/main/java/at/petrak/hexcasting/api/player/AltioraAbility.java index 304a02121d..be7bb9fac1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/player/AltioraAbility.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/player/AltioraAbility.java @@ -1,10 +1,10 @@ package at.petrak.hexcasting.api.player; /** - * Note that this just keeps track of state, actually giving the player the elytra ability is handled - * differently per platform + * Note that this just keeps track of state, actually giving the player the elytra ability is + * handled differently per platform * - * @param gracePeriod so the flight isn't immediately removed because the player started on the ground + * @param gracePeriod so the flight isn't immediately removed because the player started on the + * ground */ -public record AltioraAbility(int gracePeriod) { -} +public record AltioraAbility(int gracePeriod) {} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/player/FlightAbility.java b/Common/src/main/java/at/petrak/hexcasting/api/player/FlightAbility.java index 7980a561be..81a145f039 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/player/FlightAbility.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/player/FlightAbility.java @@ -6,7 +6,7 @@ /** * @param timeLeft sentinel of -1 for infinite - * @param radius sentinel of negative for infinite + * @param radius sentinel of negative for infinite */ -public record FlightAbility(int timeLeft, ResourceKey dimension, Vec3 origin, double radius) { -} +public record FlightAbility( + int timeLeft, ResourceKey dimension, Vec3 origin, double radius) {} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/player/Sentinel.java b/Common/src/main/java/at/petrak/hexcasting/api/player/Sentinel.java index 867b4fca34..c0c795dfee 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/player/Sentinel.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/player/Sentinel.java @@ -4,8 +4,5 @@ import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; -/** - * A null sentinel means no sentinel - */ -public record Sentinel(boolean extendsRange, Vec3 position, ResourceKey dimension) { -} +/** A null sentinel means no sentinel */ +public record Sentinel(boolean extendsRange, Vec3 position, ResourceKey dimension) {} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt b/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt index 209297766a..31243644d9 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt @@ -6,6 +6,13 @@ import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.IotaType import at.petrak.hexcasting.api.casting.iota.ListIota import at.petrak.hexcasting.api.casting.math.HexCoord +import java.lang.ref.WeakReference +import java.util.* +import kotlin.math.absoluteValue +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToInt +import kotlin.reflect.KProperty import net.minecraft.ChatFormatting import net.minecraft.core.Registry import net.minecraft.nbt.* @@ -19,233 +26,279 @@ import net.minecraft.world.InteractionHand import net.minecraft.world.item.ItemStack import net.minecraft.world.phys.Vec2 import net.minecraft.world.phys.Vec3 -import java.lang.ref.WeakReference -import java.util.* -import kotlin.math.absoluteValue -import kotlin.math.max -import kotlin.math.min -import kotlin.math.roundToInt -import kotlin.reflect.KProperty const val TAU = Math.PI * 2.0 const val SQRT_3 = 1.7320508f fun Vec3.serializeToNBT(): CompoundTag { - val tag = CompoundTag() - tag.putDouble("x", this.x) - tag.putDouble("y", this.y) - tag.putDouble("z", this.z) - return tag + val tag = CompoundTag() + tag.putDouble("x", this.x) + tag.putDouble("y", this.y) + tag.putDouble("z", this.z) + return tag } -fun vecFromNBT(tag: LongArray): Vec3 = if (tag.size != 3) Vec3.ZERO else - Vec3( - Double.fromBits(tag[0]), - Double.fromBits(tag[1]), - Double.fromBits(tag[2]) - ) +fun vecFromNBT(tag: LongArray): Vec3 = + if (tag.size != 3) Vec3.ZERO + else Vec3(Double.fromBits(tag[0]), Double.fromBits(tag[1]), Double.fromBits(tag[2])) + fun vecFromNBT(tag: CompoundTag): Vec3 { - return if (!tag.contains("x") || !tag.contains("y") || !tag.contains("z")) - Vec3.ZERO - else - Vec3(tag.getDouble("x"), tag.getDouble("y"), tag.getDouble("z")) + return if (!tag.contains("x") || !tag.contains("y") || !tag.contains("z")) Vec3.ZERO + else Vec3(tag.getDouble("x"), tag.getDouble("y"), tag.getDouble("z")) } fun Vec2.serializeToNBT(): LongArrayTag = - LongArrayTag(longArrayOf(this.x.toDouble().toRawBits(), this.y.toDouble().toRawBits())) + LongArrayTag(longArrayOf(this.x.toDouble().toRawBits(), this.y.toDouble().toRawBits())) -fun vec2FromNBT(tag: LongArray): Vec2 = if (tag.size != 2) Vec2.ZERO else - Vec2( - Double.fromBits(tag[0]).toFloat(), - Double.fromBits(tag[1]).toFloat(), - ) +fun vec2FromNBT(tag: LongArray): Vec2 = + if (tag.size != 2) Vec2.ZERO + else + Vec2( + Double.fromBits(tag[0]).toFloat(), + Double.fromBits(tag[1]).toFloat(), + ) fun otherHand(hand: InteractionHand) = - if (hand == InteractionHand.MAIN_HAND) InteractionHand.OFF_HAND else InteractionHand.MAIN_HAND + if (hand == InteractionHand.MAIN_HAND) InteractionHand.OFF_HAND else InteractionHand.MAIN_HAND fun fixNAN(n: Double): Double = if (n.isFinite()) n else 0.0 fun findCenter(points: List): Vec2 { - var minX = Float.POSITIVE_INFINITY - var minY = Float.POSITIVE_INFINITY - var maxX = Float.NEGATIVE_INFINITY - var maxY = Float.NEGATIVE_INFINITY - - for (pos in points) { - minX = min(minX, pos.x) - minY = min(minY, pos.y) - maxX = max(maxX, pos.x) - maxY = max(maxY, pos.y) - } - return Vec2( - (minX + maxX) / 2f, - (minY + maxY) / 2f - ) + var minX = Float.POSITIVE_INFINITY + var minY = Float.POSITIVE_INFINITY + var maxX = Float.NEGATIVE_INFINITY + var maxY = Float.NEGATIVE_INFINITY + + for (pos in points) { + minX = min(minX, pos.x) + minY = min(minY, pos.y) + maxX = max(maxX, pos.x) + maxY = max(maxY, pos.y) + } + return Vec2((minX + maxX) / 2f, (minY + maxY) / 2f) } fun coordToPx(coord: HexCoord, size: Float, offset: Vec2): Vec2 = - Vec2( - SQRT_3 * coord.q.toFloat() + SQRT_3 / 2.0f * coord.r.toFloat(), - 1.5f * coord.r.toFloat() - ).scale(size).add(offset) + Vec2(SQRT_3 * coord.q.toFloat() + SQRT_3 / 2.0f * coord.r.toFloat(), 1.5f * coord.r.toFloat()) + .scale(size) + .add(offset) fun pxToCoord(px: Vec2, size: Float, offset: Vec2): HexCoord { - val offsetted = px.add(offset.negated()) - var qf = (SQRT_3 / 3.0f * offsetted.x - 0.33333f * offsetted.y) / size - var rf = (0.66666f * offsetted.y) / size - - val q = qf.roundToInt() - val r = rf.roundToInt() - qf -= q - rf -= r - return if (q.absoluteValue >= r.absoluteValue) - HexCoord(q + (qf + 0.5f * rf).roundToInt(), r) - else - HexCoord(q, r + (rf + 0.5 * qf).roundToInt()) + val offsetted = px.add(offset.negated()) + var qf = (SQRT_3 / 3.0f * offsetted.x - 0.33333f * offsetted.y) / size + var rf = (0.66666f * offsetted.y) / size + + val q = qf.roundToInt() + val r = rf.roundToInt() + qf -= q + rf -= r + return if (q.absoluteValue >= r.absoluteValue) HexCoord(q + (qf + 0.5f * rf).roundToInt(), r) + else HexCoord(q, r + (rf + 0.5 * qf).roundToInt()) } @JvmOverloads fun > Array.getSafe(key: String, default: T = this[0]): T { - val lowercaseKey = key.lowercase(Locale.ROOT) - return firstOrNull { it.name.lowercase(Locale.ROOT) == lowercaseKey } ?: default + val lowercaseKey = key.lowercase(Locale.ROOT) + return firstOrNull { it.name.lowercase(Locale.ROOT) == lowercaseKey } ?: default } @JvmOverloads -fun > Array.getSafe(index: Byte, default: T = this[0]) = getSafe(index.toInt(), default) +fun > Array.getSafe(index: Byte, default: T = this[0]) = + getSafe(index.toInt(), default) @JvmOverloads -fun > Array.getSafe(index: Int, default: T = this[0]) = if (index in indices) this[index] else default +fun > Array.getSafe(index: Int, default: T = this[0]) = + if (index in indices) this[index] else default fun String.withStyle(op: (Style) -> Style): MutableComponent = asTextComponent.withStyle(op) -fun String.withStyle(style: Style): MutableComponent = asTextComponent.withStyle(style) -fun String.withStyle(formatting: ChatFormatting): MutableComponent = asTextComponent.withStyle(formatting) -fun String.withStyle(vararg formatting: ChatFormatting): MutableComponent = asTextComponent.withStyle(*formatting) - -infix fun String.styledWith(op: (Style) -> Style) = withStyle(op) -infix fun String.styledWith(style: Style) = withStyle(style) -infix fun String.styledWith(formatting: ChatFormatting) = withStyle(formatting) - -infix fun MutableComponent.styledWith(op: (Style) -> Style): MutableComponent = withStyle(op) -infix fun MutableComponent.styledWith(style: Style): MutableComponent = withStyle(style) -infix fun MutableComponent.styledWith(formatting: ChatFormatting): MutableComponent = withStyle(formatting) - -val String.black get() = this styledWith ChatFormatting.BLACK -val MutableComponent.black get() = this styledWith ChatFormatting.BLACK - -val String.darkBlue get() = this styledWith ChatFormatting.DARK_BLUE -val MutableComponent.darkBlue get() = this styledWith ChatFormatting.DARK_BLUE - -val String.darkGreen get() = this styledWith ChatFormatting.DARK_GREEN -val MutableComponent.darkGreen get() = this styledWith ChatFormatting.DARK_GREEN - -val String.darkAqua get() = this styledWith ChatFormatting.DARK_AQUA -val MutableComponent.darkAqua get() = this styledWith ChatFormatting.DARK_AQUA - -val String.darkRed get() = this styledWith ChatFormatting.DARK_RED -val MutableComponent.darkRed get() = this styledWith ChatFormatting.DARK_RED - -val String.darkPurple get() = this styledWith ChatFormatting.DARK_PURPLE -val MutableComponent.darkPurple get() = this styledWith ChatFormatting.DARK_PURPLE - -val String.gold get() = this styledWith ChatFormatting.GOLD -val MutableComponent.gold get() = this styledWith ChatFormatting.GOLD - -val String.gray get() = this styledWith ChatFormatting.GRAY -val MutableComponent.gray get() = this styledWith ChatFormatting.GRAY - -val String.darkGray get() = this styledWith ChatFormatting.DARK_GRAY -val MutableComponent.darkGray get() = this styledWith ChatFormatting.DARK_GRAY - -val String.blue get() = this styledWith ChatFormatting.BLUE -val MutableComponent.blue get() = this styledWith ChatFormatting.BLUE -val String.green get() = this styledWith ChatFormatting.GREEN -val MutableComponent.green get() = this styledWith ChatFormatting.GREEN - -val String.aqua get() = this styledWith ChatFormatting.AQUA -val MutableComponent.aqua get() = this styledWith ChatFormatting.AQUA - -val String.red get() = this styledWith ChatFormatting.RED -val MutableComponent.red get() = this styledWith ChatFormatting.RED +fun String.withStyle(style: Style): MutableComponent = asTextComponent.withStyle(style) -val String.lightPurple get() = this styledWith ChatFormatting.LIGHT_PURPLE -val MutableComponent.lightPurple get() = this styledWith ChatFormatting.LIGHT_PURPLE +fun String.withStyle(formatting: ChatFormatting): MutableComponent = + asTextComponent.withStyle(formatting) -val String.yellow get() = this styledWith ChatFormatting.YELLOW -val MutableComponent.yellow get() = this styledWith ChatFormatting.YELLOW +fun String.withStyle(vararg formatting: ChatFormatting): MutableComponent = + asTextComponent.withStyle(*formatting) -val String.white get() = this styledWith ChatFormatting.WHITE -val MutableComponent.white get() = this styledWith ChatFormatting.WHITE +infix fun String.styledWith(op: (Style) -> Style) = withStyle(op) -val String.obfuscated get() = this styledWith ChatFormatting.OBFUSCATED -val MutableComponent.obfuscated get() = this styledWith ChatFormatting.OBFUSCATED +infix fun String.styledWith(style: Style) = withStyle(style) -val String.bold get() = this styledWith ChatFormatting.BOLD -val MutableComponent.bold get() = this styledWith ChatFormatting.BOLD +infix fun String.styledWith(formatting: ChatFormatting) = withStyle(formatting) -val String.strikethrough get() = this styledWith ChatFormatting.STRIKETHROUGH -val MutableComponent.strikethrough get() = this styledWith ChatFormatting.STRIKETHROUGH +infix fun MutableComponent.styledWith(op: (Style) -> Style): MutableComponent = withStyle(op) -val String.underline get() = this styledWith ChatFormatting.UNDERLINE -val MutableComponent.underline get() = this styledWith ChatFormatting.UNDERLINE +infix fun MutableComponent.styledWith(style: Style): MutableComponent = withStyle(style) -val String.italic get() = this styledWith ChatFormatting.ITALIC -val MutableComponent.italic get() = this styledWith ChatFormatting.ITALIC +infix fun MutableComponent.styledWith(formatting: ChatFormatting): MutableComponent = + withStyle(formatting) + +val String.black + get() = this styledWith ChatFormatting.BLACK +val MutableComponent.black + get() = this styledWith ChatFormatting.BLACK + +val String.darkBlue + get() = this styledWith ChatFormatting.DARK_BLUE +val MutableComponent.darkBlue + get() = this styledWith ChatFormatting.DARK_BLUE + +val String.darkGreen + get() = this styledWith ChatFormatting.DARK_GREEN +val MutableComponent.darkGreen + get() = this styledWith ChatFormatting.DARK_GREEN + +val String.darkAqua + get() = this styledWith ChatFormatting.DARK_AQUA +val MutableComponent.darkAqua + get() = this styledWith ChatFormatting.DARK_AQUA + +val String.darkRed + get() = this styledWith ChatFormatting.DARK_RED +val MutableComponent.darkRed + get() = this styledWith ChatFormatting.DARK_RED + +val String.darkPurple + get() = this styledWith ChatFormatting.DARK_PURPLE +val MutableComponent.darkPurple + get() = this styledWith ChatFormatting.DARK_PURPLE + +val String.gold + get() = this styledWith ChatFormatting.GOLD +val MutableComponent.gold + get() = this styledWith ChatFormatting.GOLD + +val String.gray + get() = this styledWith ChatFormatting.GRAY +val MutableComponent.gray + get() = this styledWith ChatFormatting.GRAY + +val String.darkGray + get() = this styledWith ChatFormatting.DARK_GRAY +val MutableComponent.darkGray + get() = this styledWith ChatFormatting.DARK_GRAY + +val String.blue + get() = this styledWith ChatFormatting.BLUE +val MutableComponent.blue + get() = this styledWith ChatFormatting.BLUE + +val String.green + get() = this styledWith ChatFormatting.GREEN +val MutableComponent.green + get() = this styledWith ChatFormatting.GREEN + +val String.aqua + get() = this styledWith ChatFormatting.AQUA +val MutableComponent.aqua + get() = this styledWith ChatFormatting.AQUA + +val String.red + get() = this styledWith ChatFormatting.RED +val MutableComponent.red + get() = this styledWith ChatFormatting.RED + +val String.lightPurple + get() = this styledWith ChatFormatting.LIGHT_PURPLE +val MutableComponent.lightPurple + get() = this styledWith ChatFormatting.LIGHT_PURPLE + +val String.yellow + get() = this styledWith ChatFormatting.YELLOW +val MutableComponent.yellow + get() = this styledWith ChatFormatting.YELLOW + +val String.white + get() = this styledWith ChatFormatting.WHITE +val MutableComponent.white + get() = this styledWith ChatFormatting.WHITE + +val String.obfuscated + get() = this styledWith ChatFormatting.OBFUSCATED +val MutableComponent.obfuscated + get() = this styledWith ChatFormatting.OBFUSCATED + +val String.bold + get() = this styledWith ChatFormatting.BOLD +val MutableComponent.bold + get() = this styledWith ChatFormatting.BOLD + +val String.strikethrough + get() = this styledWith ChatFormatting.STRIKETHROUGH +val MutableComponent.strikethrough + get() = this styledWith ChatFormatting.STRIKETHROUGH + +val String.underline + get() = this styledWith ChatFormatting.UNDERLINE +val MutableComponent.underline + get() = this styledWith ChatFormatting.UNDERLINE + +val String.italic + get() = this styledWith ChatFormatting.ITALIC +val MutableComponent.italic + get() = this styledWith ChatFormatting.ITALIC operator fun MutableComponent.plusAssign(component: Component) { - append(component) + append(component) } -val String.asTextComponent: MutableComponent get() = Component.literal(this) -val String.asTranslatedComponent: MutableComponent get() = Component.translatable(this) +val String.asTextComponent: MutableComponent + get() = Component.literal(this) +val String.asTranslatedComponent: MutableComponent + get() = Component.translatable(this) -fun String.asTranslatedComponent(vararg args: Any): MutableComponent = Component.translatable(this, *args) +fun String.asTranslatedComponent(vararg args: Any): MutableComponent = + Component.translatable(this, *args) /** - * Represents a value that the garbage collector is still allowed to collect. - * To create an instance of a [WeakValue], use [weakReference] or [weakMapped]. + * Represents a value that the garbage collector is still allowed to collect. To create an instance + * of a [WeakValue], use [weakReference] or [weakMapped]. */ interface WeakValue { - var value: T? + var value: T? } /** * A weakly referenced value that relies directly on a [WeakReference]. * - * This means that if there are no other places where the contained object is referenced, - * the reference will expire, and value contained within this reference will become null. + * This means that if there are no other places where the contained object is referenced, the + * reference will expire, and value contained within this reference will become null. */ private class WeakReferencedValue(var reference: WeakReference?) : WeakValue { - override var value: T? - get() = reference?.get() - set(value) { - reference = value?.let(::WeakReference) - } + override var value: T? + get() = reference?.get() + set(value) { + reference = value?.let(::WeakReference) + } } /** * A weakly referenced value that relies on a [WeakHashMap]. * - * Unlike [WeakReferencedValue], it relies on the continued existence of something else (obtained by [keyGen]). - * For example, this can be used to hold an entity, and have the reference expire when the world it's in is unloaded. + * Unlike [WeakReferencedValue], it relies on the continued existence of something else (obtained by + * [keyGen]). For example, this can be used to hold an entity, and have the reference expire when + * the world it's in is unloaded. */ private class WeakMappedValue(val keyGen: (T) -> K) : WeakValue { - val reference = WeakHashMap() - override var value: T? - get() = reference.values.firstOrNull() - set(value) { - reference.clear() - if (value != null) reference[keyGen(value)] = value - } + val reference = WeakHashMap() + override var value: T? + get() = reference.values.firstOrNull() + set(value) { + reference.clear() + if (value != null) reference[keyGen(value)] = value + } } /** - * Creates a [WeakReferencedValue], the contents of which will expire when nothing else is referencing them. + * Creates a [WeakReferencedValue], the contents of which will expire when nothing else is + * referencing them. */ -fun weakReference(value: T? = null): WeakValue = WeakReferencedValue(value?.let { WeakReference(it) }) +fun weakReference(value: T? = null): WeakValue = + WeakReferencedValue(value?.let { WeakReference(it) }) /** - * Creates a [WeakMappedValue], the contents of which will expire when nothing else is referencing the value returned by [keyGen]. + * Creates a [WeakMappedValue], the contents of which will expire when nothing else is referencing + * the value returned by [keyGen]. */ fun weakMapped(keyGen: (T) -> K): WeakValue = WeakMappedValue(keyGen) @@ -255,61 +308,57 @@ inline operator fun WeakValue.getValue(thisRef: Any?, property: KProperty @Suppress("NOTHING_TO_INLINE") inline operator fun WeakValue.setValue(thisRef: Any?, property: KProperty<*>, value: T?) { - this.value = value + this.value = value } -/** - * Returns an empty list if it's too complicated. - */ +/** Returns an empty list if it's too complicated. */ fun Iterable.serializeToNBT() = - if (IotaType.isTooLargeToSerialize(this)) - ListTag() - else - ListIota(this.toList()).serialize() + if (IotaType.isTooLargeToSerialize(this)) ListTag() else ListIota(this.toList()).serialize() fun Iterable.serializeToNBT(): ByteArrayTag { - val out = ByteArray(if (this is Collection<*>) this.size else 10) - for ((i, b) in this.withIndex()) { - out[i] = if (b) 1 else 0 - } - return ByteArrayTag(out) + val out = ByteArray(if (this is Collection<*>) this.size else 10) + for ((i, b) in this.withIndex()) { + out[i] = if (b) 1 else 0 + } + return ByteArrayTag(out) } fun List.zipWithDefault(array: ByteArray, default: (idx: Int) -> Byte): List> { - val list = ArrayList>(this.size) - var i = 0 - for (element in this) list.add(element to array.getOrElse(i++, default)) + val list = ArrayList>(this.size) + var i = 0 + for (element in this) list.add(element to array.getOrElse(i++, default)) - return list + return list } // Copy the impl from forge fun ItemStack.serializeToNBT(): CompoundTag { - val out = CompoundTag() - this.save(out) - return out + val out = CompoundTag() + this.save(out) + return out } @Suppress("UNCHECKED_CAST") @Throws(IllegalArgumentException::class) fun Tag.downcast(type: TagType): T { - if (this.type == type) { - return this as T - } else { - throw IllegalArgumentException( - "Expected this tag to be of type ${type.name}, but found ${this.type.name}." - ) - } + if (this.type == type) { + return this as T + } else { + throw IllegalArgumentException( + "Expected this tag to be of type ${type.name}, but found ${this.type.name}." + ) + } } const val ERROR_COLOR = 0xff_f800f8.toInt() + fun isOfTag(registry: Registry, key: ResourceKey, tag: TagKey): Boolean { - val maybeHolder = registry.getHolder(key) - val holder = if (maybeHolder.isPresent) maybeHolder.get() else return false - return holder.`is`(tag) + val maybeHolder = registry.getHolder(key) + val holder = if (maybeHolder.isPresent) maybeHolder.get() else return false + return holder.`is`(tag) } fun isOfTag(registry: Registry, loc: ResourceLocation, tag: TagKey): Boolean { - val key = ResourceKey.create(registry.key(), loc); - return isOfTag(registry, key, tag) + val key = ResourceKey.create(registry.key(), loc) + return isOfTag(registry, key, tag) } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/MathUtils.kt b/Common/src/main/java/at/petrak/hexcasting/api/utils/MathUtils.kt index 84448cc394..d664e7466e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/MathUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/MathUtils.kt @@ -1,38 +1,40 @@ package at.petrak.hexcasting.api.utils +import kotlin.math.* import org.joml.Quaternionf import org.joml.Vector3f -import kotlin.math.* object MathUtils { - @JvmStatic - fun clamp(long: Long, min: Long, max: Long): Long - = if (long <= min) min - else if (long >= max) max - else long + @JvmStatic + fun clamp(long: Long, min: Long, max: Long): Long = + if (long <= min) min else if (long >= max) max else long } object QuaternionfUtils { - @JvmStatic - val ONE: Quaternionf - get() = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f) + @JvmStatic + val ONE: Quaternionf + get() = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f) - @JvmStatic - fun fromXYZDegrees(vector3f: Vector3f): Quaternionf { - return fromXYZ(Math.toRadians(vector3f.x().toDouble()).toFloat(), Math.toRadians(vector3f.y().toDouble()).toFloat(), Math.toRadians(vector3f.z().toDouble()).toFloat()) - } + @JvmStatic + fun fromXYZDegrees(vector3f: Vector3f): Quaternionf { + return fromXYZ( + Math.toRadians(vector3f.x().toDouble()).toFloat(), + Math.toRadians(vector3f.y().toDouble()).toFloat(), + Math.toRadians(vector3f.z().toDouble()).toFloat() + ) + } - @JvmStatic - fun fromXYZ(vector3f: Vector3f): Quaternionf { - return fromXYZ(vector3f.x(), vector3f.y(), vector3f.z()) - } + @JvmStatic + fun fromXYZ(vector3f: Vector3f): Quaternionf { + return fromXYZ(vector3f.x(), vector3f.y(), vector3f.z()) + } - @JvmStatic - fun fromXYZ(f: Float, g: Float, h: Float): Quaternionf { - val quaternion = ONE - quaternion.mul(Quaternionf(sin((f / 2.0f)), 0.0f, 0.0f, cos((f / 2.0f)))) - quaternion.mul(Quaternionf(0.0f, sin((g / 2.0f)), 0.0f, cos((g / 2.0f)))) - quaternion.mul(Quaternionf(0.0f, 0.0f, sin((h / 2.0f)), cos((h / 2.0f)))) - return quaternion - } -} \ No newline at end of file + @JvmStatic + fun fromXYZ(f: Float, g: Float, h: Float): Quaternionf { + val quaternion = ONE + quaternion.mul(Quaternionf(sin((f / 2.0f)), 0.0f, 0.0f, cos((f / 2.0f)))) + quaternion.mul(Quaternionf(0.0f, sin((g / 2.0f)), 0.0f, cos((g / 2.0f)))) + quaternion.mul(Quaternionf(0.0f, 0.0f, sin((h / 2.0f)), cos((h / 2.0f)))) + return quaternion + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/MediaHelper.kt b/Common/src/main/java/at/petrak/hexcasting/api/utils/MediaHelper.kt index c237ac52be..6f01f11ef8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/MediaHelper.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/MediaHelper.kt @@ -5,107 +5,107 @@ package at.petrak.hexcasting.api.utils import at.petrak.hexcasting.api.HexAPI import at.petrak.hexcasting.api.addldata.ADMediaHolder import at.petrak.hexcasting.xplat.IXplatAbstractions +import kotlin.math.roundToInt import net.minecraft.server.level.ServerPlayer import net.minecraft.util.Mth import net.minecraft.world.item.ItemStack -import kotlin.math.roundToInt fun isMediaItem(stack: ItemStack): Boolean { - val mediaHolder = IXplatAbstractions.INSTANCE.findMediaHolder(stack) ?: return false - if (!mediaHolder.canProvide()) - return false - return mediaHolder.withdrawMedia(-1, true) > 0 + val mediaHolder = IXplatAbstractions.INSTANCE.findMediaHolder(stack) ?: return false + if (!mediaHolder.canProvide()) return false + return mediaHolder.withdrawMedia(-1, true) > 0 } /** - * Extract [cost] media from [stack]. If [cost] is less than zero, extract all media instead. - * This may mutate [stack] (and may consume it) unless [simulate] is set. + * Extract [cost] media from [stack]. If [cost] is less than zero, extract all media instead. This + * may mutate [stack] (and may consume it) unless [simulate] is set. * - * If [drainForBatteries] is true, this will only consider forms of media that can be used to make new batteries. + * If [drainForBatteries] is true, this will only consider forms of media that can be used to make + * new batteries. * * Return the amount of media extracted. This may be over [cost] if media is wasted. */ @JvmOverloads fun extractMedia( - stack: ItemStack, - cost: Long = -1, - drainForBatteries: Boolean = false, - simulate: Boolean = false + stack: ItemStack, + cost: Long = -1, + drainForBatteries: Boolean = false, + simulate: Boolean = false ): Long { - val mediaHolder = IXplatAbstractions.INSTANCE.findMediaHolder(stack) ?: return 0 + val mediaHolder = IXplatAbstractions.INSTANCE.findMediaHolder(stack) ?: return 0 - return extractMedia(mediaHolder, cost, drainForBatteries, simulate) + return extractMedia(mediaHolder, cost, drainForBatteries, simulate) } /** - * Extract [cost] media from [holder]. If [cost] is less than zero, extract all media instead. - * This may mutate the stack underlying [holder] (and may consume it) unless [simulate] is set. + * Extract [cost] media from [holder]. If [cost] is less than zero, extract all media instead. This + * may mutate the stack underlying [holder] (and may consume it) unless [simulate] is set. * - * If [drainForBatteries] is true, this will only consider forms of media that can be used to make new batteries. + * If [drainForBatteries] is true, this will only consider forms of media that can be used to make + * new batteries. * * Return the amount of media extracted. This may be over [cost] if media is wasted. */ fun extractMedia( - holder: ADMediaHolder, - cost: Long = -1, - drainForBatteries: Boolean = false, - simulate: Boolean = false + holder: ADMediaHolder, + cost: Long = -1, + drainForBatteries: Boolean = false, + simulate: Boolean = false ): Long { - if (drainForBatteries && !holder.canConstructBattery()) - return 0 + if (drainForBatteries && !holder.canConstructBattery()) return 0 - return holder.withdrawMedia(cost, simulate) + return holder.withdrawMedia(cost, simulate) } /** - * Convenience function to scan the player's inventory, curios, etc for media sources, - * and then sorts them + * Convenience function to scan the player's inventory, curios, etc for media sources, and then + * sorts them */ fun scanPlayerForMediaStuff(player: ServerPlayer): List { - val sources = mutableListOf() + val sources = mutableListOf() - (player.inventory.items + player.inventory.armor + player.inventory.offhand).forEach { - val holder = HexAPI.instance().findMediaHolder(it) - if (holder?.canProvide() == true) { - sources.add(holder) - } - } + (player.inventory.items + player.inventory.armor + player.inventory.offhand).forEach { + val holder = HexAPI.instance().findMediaHolder(it) + if (holder?.canProvide() == true) { + sources.add(holder) + } + } - sources.sortWith(::compareMediaItem) - sources.reverse() - return sources + sources.sortWith(::compareMediaItem) + sources.reverse() + return sources } -/** - * Sorted from least important to most important - */ +/** Sorted from least important to most important */ fun compareMediaItem(aMedia: ADMediaHolder, bMedia: ADMediaHolder): Int { - val priority = aMedia.consumptionPriority - bMedia.consumptionPriority - if (priority != 0) - return priority + val priority = aMedia.consumptionPriority - bMedia.consumptionPriority + if (priority != 0) return priority - return (aMedia.withdrawMedia(-1, true) - bMedia.withdrawMedia(-1, true)) - .coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt() + return (aMedia.withdrawMedia(-1, true) - bMedia.withdrawMedia(-1, true)) + .coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()) + .toInt() } fun mediaBarColor(media: Long, maxMedia: Long): Int { - val amt = if (maxMedia == 0L) { - 0f - } else { - media.toFloat() / maxMedia.toFloat() - } + val amt = + if (maxMedia == 0L) { + 0f + } else { + media.toFloat() / maxMedia.toFloat() + } - val r = Mth.lerp(amt, 84f, 254f) - val g = Mth.lerp(amt, 57f, 203f) - val b = Mth.lerp(amt, 138f, 230f) - return Mth.color(r / 255f, g / 255f, b / 255f) + val r = Mth.lerp(amt, 84f, 254f) + val g = Mth.lerp(amt, 57f, 203f) + val b = Mth.lerp(amt, 138f, 230f) + return Mth.color(r / 255f, g / 255f, b / 255f) } fun mediaBarWidth(media: Long, maxMedia: Long): Int { - val amt = if (maxMedia == 0L) { - 0f - } else { - media.toFloat() / maxMedia.toFloat() - } - return (13f * amt).roundToInt() + val amt = + if (maxMedia == 0L) { + 0f + } else { + media.toFloat() / maxMedia.toFloat() + } + return (13f * amt).roundToInt() } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/NBTDsl.kt b/Common/src/main/java/at/petrak/hexcasting/api/utils/NBTDsl.kt index 1c427b4c1e..b9b455d78a 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/NBTDsl.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/NBTDsl.kt @@ -6,210 +6,292 @@ import net.minecraft.nbt.* // https://github.com/TeamWizardry/LibrarianLib/blob/9cfb2cf3e35685568942ad41395265a2edc27d30/modules/core/src/main/kotlin/com/teamwizardry/librarianlib/core/util/kotlin/NbtBuilder.kt -@DslMarker -internal annotation class NBTDslMarker +@DslMarker internal annotation class NBTDslMarker @NBTDslMarker object NBTBuilder { - inline operator fun invoke(block: NbtCompoundBuilder.() -> Unit) = compound(block) - inline operator fun invoke(tag: CompoundTag, block: NbtCompoundBuilder.() -> Unit) = use(tag, block) - - inline fun use(tag: CompoundTag, block: NbtCompoundBuilder.() -> Unit): CompoundTag = - NbtCompoundBuilder(tag).apply(block).tag - - inline fun compound(block: NbtCompoundBuilder.() -> Unit): CompoundTag = - NbtCompoundBuilder(CompoundTag()).apply(block).tag - - inline fun list(block: NbtListBuilder.() -> Unit): ListTag = - NbtListBuilder(ListTag()).apply(block).tag - - inline fun list(vararg elements: Tag, block: NbtListBuilder.() -> Unit): ListTag = - NbtListBuilder(ListTag()).also { - it.addAll(elements.toList()) - it.block() - }.tag - - inline fun list(vararg elements: Tag): ListTag = ListTag().also { it.addAll(elements) } - inline fun list(elements: Collection): ListTag = ListTag().also { it.addAll(elements) } - inline fun list(elements: Collection, mapper: (T) -> Tag): ListTag = ListTag().also { it.addAll(elements.map(mapper)) } - - inline fun double(value: Number): DoubleTag = DoubleTag.valueOf(value.toDouble()) - inline fun float(value: Number): FloatTag = FloatTag.valueOf(value.toFloat()) - inline fun long(value: Number): LongTag = LongTag.valueOf(value.toLong()) - inline fun int(value: Number): IntTag = IntTag.valueOf(value.toInt()) - inline fun short(value: Number): ShortTag = ShortTag.valueOf(value.toShort()) - inline fun byte(value: Number): ByteTag = ByteTag.valueOf(value.toByte()) - - inline fun string(value: String): StringTag = StringTag.valueOf(value) - - inline fun byteArray(value: Collection): ByteArrayTag = ByteArrayTag(value.map { it.toByte() }) - inline fun byteArray(vararg value: Int): ByteArrayTag = ByteArrayTag(ByteArray(value.size) { value[it].toByte() }) - inline fun byteArray(vararg value: Byte): ByteArrayTag = ByteArrayTag(value) - inline fun byteArray(): ByteArrayTag = ByteArrayTag(byteArrayOf()) // avoiding overload ambiguity - inline fun longArray(value: Collection): LongArrayTag = LongArrayTag(value.map { it.toLong() }) - inline fun longArray(vararg value: Int): LongArrayTag = LongArrayTag(LongArray(value.size) { value[it].toLong() }) - inline fun longArray(vararg value: Long): LongArrayTag = LongArrayTag(value) - inline fun longArray(): LongArrayTag = LongArrayTag(longArrayOf()) // avoiding overload ambiguity - inline fun intArray(value: Collection): IntArrayTag = IntArrayTag(value.map { it.toInt() }) - inline fun intArray(vararg value: Int): IntArrayTag = IntArrayTag(value) + inline operator fun invoke(block: NbtCompoundBuilder.() -> Unit) = compound(block) + + inline operator fun invoke(tag: CompoundTag, block: NbtCompoundBuilder.() -> Unit) = + use(tag, block) + + inline fun use(tag: CompoundTag, block: NbtCompoundBuilder.() -> Unit): CompoundTag = + NbtCompoundBuilder(tag).apply(block).tag + + inline fun compound(block: NbtCompoundBuilder.() -> Unit): CompoundTag = + NbtCompoundBuilder(CompoundTag()).apply(block).tag + + inline fun list(block: NbtListBuilder.() -> Unit): ListTag = + NbtListBuilder(ListTag()).apply(block).tag + + inline fun list(vararg elements: Tag, block: NbtListBuilder.() -> Unit): ListTag = + NbtListBuilder(ListTag()) + .also { + it.addAll(elements.toList()) + it.block() + } + .tag + + inline fun list(vararg elements: Tag): ListTag = ListTag().also { it.addAll(elements) } + + inline fun list(elements: Collection): ListTag = ListTag().also { it.addAll(elements) } + + inline fun list(elements: Collection, mapper: (T) -> Tag): ListTag = + ListTag().also { it.addAll(elements.map(mapper)) } + + inline fun double(value: Number): DoubleTag = DoubleTag.valueOf(value.toDouble()) + + inline fun float(value: Number): FloatTag = FloatTag.valueOf(value.toFloat()) + + inline fun long(value: Number): LongTag = LongTag.valueOf(value.toLong()) + + inline fun int(value: Number): IntTag = IntTag.valueOf(value.toInt()) + + inline fun short(value: Number): ShortTag = ShortTag.valueOf(value.toShort()) + + inline fun byte(value: Number): ByteTag = ByteTag.valueOf(value.toByte()) + + inline fun string(value: String): StringTag = StringTag.valueOf(value) + + inline fun byteArray(value: Collection): ByteArrayTag = + ByteArrayTag(value.map { it.toByte() }) + + inline fun byteArray(vararg value: Int): ByteArrayTag = + ByteArrayTag(ByteArray(value.size) { value[it].toByte() }) + + inline fun byteArray(vararg value: Byte): ByteArrayTag = ByteArrayTag(value) + + inline fun byteArray(): ByteArrayTag = ByteArrayTag(byteArrayOf()) // avoiding overload ambiguity + + inline fun longArray(value: Collection): LongArrayTag = + LongArrayTag(value.map { it.toLong() }) + + inline fun longArray(vararg value: Int): LongArrayTag = + LongArrayTag(LongArray(value.size) { value[it].toLong() }) + + inline fun longArray(vararg value: Long): LongArrayTag = LongArrayTag(value) + + inline fun longArray(): LongArrayTag = LongArrayTag(longArrayOf()) // avoiding overload ambiguity + + inline fun intArray(value: Collection): IntArrayTag = + IntArrayTag(value.map { it.toInt() }) + + inline fun intArray(vararg value: Int): IntArrayTag = IntArrayTag(value) } @JvmInline @NBTDslMarker value class NbtCompoundBuilder(val tag: CompoundTag) { - // configuring this tag - - inline operator fun String.remAssign(nbt: Tag) { - tag.put(this, nbt) - } - - inline operator fun String.remAssign(str: String) { - tag.put(this, string(str)) - } - - inline operator fun String.remAssign(num: Int) { - tag.put(this, int(num)) - } - - inline operator fun String.remAssign(num: Long) { - tag.put(this, long(num)) - } - - inline operator fun String.remAssign(num: Double) { - tag.put(this, double(num)) - } - - inline operator fun String.remAssign(num: Float) { - tag.put(this, float(num)) - } - - inline operator fun String.remAssign(bool: Boolean) { - tag.put(this, byte(if (bool) 1 else 0)) - } - - // creating new tags - - inline fun compound(block: NbtCompoundBuilder.() -> Unit): CompoundTag = - NbtCompoundBuilder(CompoundTag()).apply(block).tag - - inline fun list(block: NbtListBuilder.() -> Unit): ListTag = - NbtListBuilder(ListTag()).apply(block).tag - - inline fun list(vararg elements: Tag, block: NbtListBuilder.() -> Unit): ListTag = - NbtListBuilder(ListTag()).also { - it.addAll(elements.toList()) - it.block() - }.tag - - inline fun list(vararg elements: Tag): ListTag = ListTag().also { it.addAll(elements) } - inline fun list(elements: Collection): ListTag = ListTag().also { it.addAll(elements) } - inline fun list(elements: Collection, mapper: (T) -> Tag): ListTag = ListTag().also { it.addAll(elements.map(mapper)) } - - inline fun double(value: Number): DoubleTag = DoubleTag.valueOf(value.toDouble()) - inline fun float(value: Number): FloatTag = FloatTag.valueOf(value.toFloat()) - inline fun long(value: Number): LongTag = LongTag.valueOf(value.toLong()) - inline fun int(value: Number): IntTag = IntTag.valueOf(value.toInt()) - inline fun short(value: Number): ShortTag = ShortTag.valueOf(value.toShort()) - inline fun byte(value: Number): ByteTag = ByteTag.valueOf(value.toByte()) - - inline fun string(value: String): StringTag = StringTag.valueOf(value) - - inline fun byteArray(value: Collection): ByteArrayTag = ByteArrayTag(value.map { it.toByte() }) - inline fun byteArray(vararg value: Int): ByteArrayTag = ByteArrayTag(ByteArray(value.size) { value[it].toByte() }) - inline fun byteArray(vararg value: Byte): ByteArrayTag = ByteArrayTag(value) - inline fun byteArray(): ByteArrayTag = ByteArrayTag(byteArrayOf()) // avoiding overload ambiguity - inline fun longArray(value: Collection): LongArrayTag = LongArrayTag(value.map { it.toLong() }) - inline fun longArray(vararg value: Int): LongArrayTag = LongArrayTag(LongArray(value.size) { value[it].toLong() }) - inline fun longArray(vararg value: Long): LongArrayTag = LongArrayTag(value) - inline fun longArray(): LongArrayTag = LongArrayTag(longArrayOf()) // avoiding overload ambiguity - inline fun intArray(value: Collection): IntArrayTag = IntArrayTag(value.map { it.toInt() }) - inline fun intArray(vararg value: Int): IntArrayTag = IntArrayTag(value) + // configuring this tag + + inline operator fun String.remAssign(nbt: Tag) { + tag.put(this, nbt) + } + + inline operator fun String.remAssign(str: String) { + tag.put(this, string(str)) + } + + inline operator fun String.remAssign(num: Int) { + tag.put(this, int(num)) + } + + inline operator fun String.remAssign(num: Long) { + tag.put(this, long(num)) + } + + inline operator fun String.remAssign(num: Double) { + tag.put(this, double(num)) + } + + inline operator fun String.remAssign(num: Float) { + tag.put(this, float(num)) + } + + inline operator fun String.remAssign(bool: Boolean) { + tag.put(this, byte(if (bool) 1 else 0)) + } + + // creating new tags + + inline fun compound(block: NbtCompoundBuilder.() -> Unit): CompoundTag = + NbtCompoundBuilder(CompoundTag()).apply(block).tag + + inline fun list(block: NbtListBuilder.() -> Unit): ListTag = + NbtListBuilder(ListTag()).apply(block).tag + + inline fun list(vararg elements: Tag, block: NbtListBuilder.() -> Unit): ListTag = + NbtListBuilder(ListTag()) + .also { + it.addAll(elements.toList()) + it.block() + } + .tag + + inline fun list(vararg elements: Tag): ListTag = ListTag().also { it.addAll(elements) } + + inline fun list(elements: Collection): ListTag = ListTag().also { it.addAll(elements) } + + inline fun list(elements: Collection, mapper: (T) -> Tag): ListTag = + ListTag().also { it.addAll(elements.map(mapper)) } + + inline fun double(value: Number): DoubleTag = DoubleTag.valueOf(value.toDouble()) + + inline fun float(value: Number): FloatTag = FloatTag.valueOf(value.toFloat()) + + inline fun long(value: Number): LongTag = LongTag.valueOf(value.toLong()) + + inline fun int(value: Number): IntTag = IntTag.valueOf(value.toInt()) + + inline fun short(value: Number): ShortTag = ShortTag.valueOf(value.toShort()) + + inline fun byte(value: Number): ByteTag = ByteTag.valueOf(value.toByte()) + + inline fun string(value: String): StringTag = StringTag.valueOf(value) + + inline fun byteArray(value: Collection): ByteArrayTag = + ByteArrayTag(value.map { it.toByte() }) + + inline fun byteArray(vararg value: Int): ByteArrayTag = + ByteArrayTag(ByteArray(value.size) { value[it].toByte() }) + + inline fun byteArray(vararg value: Byte): ByteArrayTag = ByteArrayTag(value) + + inline fun byteArray(): ByteArrayTag = ByteArrayTag(byteArrayOf()) // avoiding overload ambiguity + + inline fun longArray(value: Collection): LongArrayTag = + LongArrayTag(value.map { it.toLong() }) + + inline fun longArray(vararg value: Int): LongArrayTag = + LongArrayTag(LongArray(value.size) { value[it].toLong() }) + + inline fun longArray(vararg value: Long): LongArrayTag = LongArrayTag(value) + + inline fun longArray(): LongArrayTag = LongArrayTag(longArrayOf()) // avoiding overload ambiguity + + inline fun intArray(value: Collection): IntArrayTag = + IntArrayTag(value.map { it.toInt() }) + + inline fun intArray(vararg value: Int): IntArrayTag = IntArrayTag(value) } @JvmInline @NBTDslMarker value class NbtListBuilder(val tag: ListTag) { - // configuring this tag - - /** - * Add the given tag to this list - */ - inline operator fun Tag.unaryPlus() { - tag.add(this) - } - - /** - * Add the given tags to this list - */ - inline operator fun Collection.unaryPlus() { - tag.addAll(this) - } - - /** - * Add the given tag to this list. This is explicitly defined for [ListTag] because otherwise there is overload - * ambiguity between the [Tag] and [Collection] methods. - */ - inline operator fun ListTag.unaryPlus() { - tag.add(this) - } - - inline fun addAll(nbt: Collection) { - this.tag.addAll(nbt) - } - - inline fun add(nbt: Tag) { - this.tag.add(nbt) - } - - // creating new tags - - inline fun compound(block: NbtCompoundBuilder.() -> Unit): CompoundTag = - NbtCompoundBuilder(CompoundTag()).apply(block).tag - - inline fun list(block: NbtListBuilder.() -> Unit): ListTag = - NbtListBuilder(ListTag()).apply(block).tag - - inline fun list(vararg elements: Tag, block: NbtListBuilder.() -> Unit): ListTag = - NbtListBuilder(ListTag()).also { - it.addAll(elements.toList()) - it.block() - }.tag - - inline fun list(vararg elements: Tag): ListTag = ListTag().also { it.addAll(elements) } - inline fun list(elements: Collection): ListTag = ListTag().also { it.addAll(elements) } - inline fun list(elements: Collection, mapper: (T) -> Tag): ListTag = ListTag().also { it.addAll(elements.map(mapper)) } - - inline fun double(value: Number): DoubleTag = DoubleTag.valueOf(value.toDouble()) - inline fun float(value: Number): FloatTag = FloatTag.valueOf(value.toFloat()) - inline fun long(value: Number): LongTag = LongTag.valueOf(value.toLong()) - inline fun int(value: Number): IntTag = IntTag.valueOf(value.toInt()) - inline fun short(value: Number): ShortTag = ShortTag.valueOf(value.toShort()) - inline fun byte(value: Number): ByteTag = ByteTag.valueOf(value.toByte()) - - inline fun string(value: String): StringTag = StringTag.valueOf(value) - - inline fun byteArray(value: Collection): ByteArrayTag = ByteArrayTag(value.map { it.toByte() }) - inline fun byteArray(vararg value: Int): ByteArrayTag = ByteArrayTag(ByteArray(value.size) { value[it].toByte() }) - inline fun byteArray(vararg value: Byte): ByteArrayTag = ByteArrayTag(value) - inline fun byteArray(): ByteArrayTag = ByteArrayTag(byteArrayOf()) // avoiding overload ambiguity - inline fun longArray(value: Collection): LongArrayTag = LongArrayTag(value.map { it.toLong() }) - inline fun longArray(vararg value: Int): LongArrayTag = LongArrayTag(LongArray(value.size) { value[it].toLong() }) - inline fun longArray(vararg value: Long): LongArrayTag = LongArrayTag(value) - inline fun longArray(): LongArrayTag = LongArrayTag(longArrayOf()) // avoiding overload ambiguity - inline fun intArray(value: Collection): IntArrayTag = IntArrayTag(value.map { it.toInt() }) - inline fun intArray(vararg value: Int): IntArrayTag = IntArrayTag(value) - - inline fun doubles(vararg value: Int): List = value.map { DoubleTag.valueOf(it.toDouble()) } - inline fun doubles(vararg value: Double): List = value.map { DoubleTag.valueOf(it) } - inline fun floats(vararg value: Int): List = value.map { FloatTag.valueOf(it.toFloat()) } - inline fun floats(vararg value: Float): List = value.map { FloatTag.valueOf(it) } - inline fun longs(vararg value: Int): List = value.map { LongTag.valueOf(it.toLong()) } - inline fun longs(vararg value: Long): List = value.map { LongTag.valueOf(it) } - inline fun ints(vararg value: Int): List = value.map { IntTag.valueOf(it) } - inline fun shorts(vararg value: Int): List = value.map { ShortTag.valueOf(it.toShort()) } - inline fun shorts(vararg value: Short): List = value.map { ShortTag.valueOf(it) } - inline fun bytes(vararg value: Int): List = value.map { ByteTag.valueOf(it.toByte()) } - inline fun bytes(vararg value: Byte): List = value.map { ByteTag.valueOf(it) } - - fun strings(vararg value: String): List = value.map { StringTag.valueOf(it) } + // configuring this tag + + /** Add the given tag to this list */ + inline operator fun Tag.unaryPlus() { + tag.add(this) + } + + /** Add the given tags to this list */ + inline operator fun Collection.unaryPlus() { + tag.addAll(this) + } + + /** + * Add the given tag to this list. This is explicitly defined for [ListTag] because otherwise + * there is overload ambiguity between the [Tag] and [Collection] methods. + */ + inline operator fun ListTag.unaryPlus() { + tag.add(this) + } + + inline fun addAll(nbt: Collection) { + this.tag.addAll(nbt) + } + + inline fun add(nbt: Tag) { + this.tag.add(nbt) + } + + // creating new tags + + inline fun compound(block: NbtCompoundBuilder.() -> Unit): CompoundTag = + NbtCompoundBuilder(CompoundTag()).apply(block).tag + + inline fun list(block: NbtListBuilder.() -> Unit): ListTag = + NbtListBuilder(ListTag()).apply(block).tag + + inline fun list(vararg elements: Tag, block: NbtListBuilder.() -> Unit): ListTag = + NbtListBuilder(ListTag()) + .also { + it.addAll(elements.toList()) + it.block() + } + .tag + + inline fun list(vararg elements: Tag): ListTag = ListTag().also { it.addAll(elements) } + + inline fun list(elements: Collection): ListTag = ListTag().also { it.addAll(elements) } + + inline fun list(elements: Collection, mapper: (T) -> Tag): ListTag = + ListTag().also { it.addAll(elements.map(mapper)) } + + inline fun double(value: Number): DoubleTag = DoubleTag.valueOf(value.toDouble()) + + inline fun float(value: Number): FloatTag = FloatTag.valueOf(value.toFloat()) + + inline fun long(value: Number): LongTag = LongTag.valueOf(value.toLong()) + + inline fun int(value: Number): IntTag = IntTag.valueOf(value.toInt()) + + inline fun short(value: Number): ShortTag = ShortTag.valueOf(value.toShort()) + + inline fun byte(value: Number): ByteTag = ByteTag.valueOf(value.toByte()) + + inline fun string(value: String): StringTag = StringTag.valueOf(value) + + inline fun byteArray(value: Collection): ByteArrayTag = + ByteArrayTag(value.map { it.toByte() }) + + inline fun byteArray(vararg value: Int): ByteArrayTag = + ByteArrayTag(ByteArray(value.size) { value[it].toByte() }) + + inline fun byteArray(vararg value: Byte): ByteArrayTag = ByteArrayTag(value) + + inline fun byteArray(): ByteArrayTag = ByteArrayTag(byteArrayOf()) // avoiding overload ambiguity + + inline fun longArray(value: Collection): LongArrayTag = + LongArrayTag(value.map { it.toLong() }) + + inline fun longArray(vararg value: Int): LongArrayTag = + LongArrayTag(LongArray(value.size) { value[it].toLong() }) + + inline fun longArray(vararg value: Long): LongArrayTag = LongArrayTag(value) + + inline fun longArray(): LongArrayTag = LongArrayTag(longArrayOf()) // avoiding overload ambiguity + + inline fun intArray(value: Collection): IntArrayTag = + IntArrayTag(value.map { it.toInt() }) + + inline fun intArray(vararg value: Int): IntArrayTag = IntArrayTag(value) + + inline fun doubles(vararg value: Int): List = + value.map { DoubleTag.valueOf(it.toDouble()) } + + inline fun doubles(vararg value: Double): List = value.map { DoubleTag.valueOf(it) } + + inline fun floats(vararg value: Int): List = + value.map { FloatTag.valueOf(it.toFloat()) } + + inline fun floats(vararg value: Float): List = value.map { FloatTag.valueOf(it) } + + inline fun longs(vararg value: Int): List = value.map { LongTag.valueOf(it.toLong()) } + + inline fun longs(vararg value: Long): List = value.map { LongTag.valueOf(it) } + + inline fun ints(vararg value: Int): List = value.map { IntTag.valueOf(it) } + + inline fun shorts(vararg value: Int): List = + value.map { ShortTag.valueOf(it.toShort()) } + + inline fun shorts(vararg value: Short): List = value.map { ShortTag.valueOf(it) } + + inline fun bytes(vararg value: Int): List = value.map { ByteTag.valueOf(it.toByte()) } + + inline fun bytes(vararg value: Byte): List = value.map { ByteTag.valueOf(it) } + + fun strings(vararg value: String): List = value.map { StringTag.valueOf(it) } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/NBTHelper.kt b/Common/src/main/java/at/petrak/hexcasting/api/utils/NBTHelper.kt index ef9a757c01..26268ec254 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/NBTHelper.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/NBTHelper.kt @@ -2,17 +2,24 @@ package at.petrak.hexcasting.api.utils +import java.util.* import net.minecraft.nbt.* import net.minecraft.world.item.ItemStack -import java.util.* - -private inline fun T?.getIf(key: K, predicate: T?.(K) -> Boolean, get: T.(K) -> E): E? = - getIf(key, predicate, get, null) -private inline fun T?.getIf(key: K, predicate: T?.(K) -> Boolean, get: T.(K) -> E, default: E): E { - if (this != null && predicate(key)) - return get(key) - return default +private inline fun T?.getIf( + key: K, + predicate: T?.(K) -> Boolean, + get: T.(K) -> E +): E? = getIf(key, predicate, get, null) + +private inline fun T?.getIf( + key: K, + predicate: T?.(K) -> Boolean, + get: T.(K) -> E, + default: E +): E { + if (this != null && predicate(key)) return get(key) + return default } // ======================================================================================================== CompoundTag @@ -20,47 +27,77 @@ private inline fun T?.getIf(key: K, predicate: T?.(K) -> Boolean // Checks for containment fun CompoundTag?.hasNumber(key: String) = contains(key, Tag.TAG_ANY_NUMERIC) + fun CompoundTag?.hasByte(key: String) = contains(key, Tag.TAG_BYTE) + fun CompoundTag?.hasShort(key: String) = contains(key, Tag.TAG_SHORT) + fun CompoundTag?.hasInt(key: String) = contains(key, Tag.TAG_INT) + fun CompoundTag?.hasLong(key: String) = contains(key, Tag.TAG_LONG) + fun CompoundTag?.hasFloat(key: String) = contains(key, Tag.TAG_FLOAT) + fun CompoundTag?.hasDouble(key: String) = contains(key, Tag.TAG_DOUBLE) + fun CompoundTag?.hasLongArray(key: String) = contains(key, Tag.TAG_LONG_ARRAY) + fun CompoundTag?.hasIntArray(key: String) = contains(key, Tag.TAG_INT_ARRAY) + fun CompoundTag?.hasByteArray(key: String) = contains(key, Tag.TAG_BYTE_ARRAY) + fun CompoundTag?.hasCompound(key: String) = contains(key, Tag.TAG_COMPOUND) + fun CompoundTag?.hasString(key: String) = contains(key, Tag.TAG_STRING) + fun CompoundTag?.hasList(key: String) = contains(key, Tag.TAG_LIST) + fun CompoundTag?.hasList(key: String, objType: Int) = hasList(key, objType.toByte()) + fun CompoundTag?.hasList(key: String, objType: Byte): Boolean { - if (!hasList(key)) return false - val lt = get(key) as ListTag - return lt.elementType == objType || lt.elementType == 0.toByte() + if (!hasList(key)) return false + val lt = get(key) as ListTag + return lt.elementType == objType || lt.elementType == 0.toByte() } fun CompoundTag?.hasUUID(key: String) = this != null && hasUUID(key) fun CompoundTag?.contains(key: String, id: Byte) = contains(key, id.toInt()) + fun CompoundTag?.contains(key: String, id: Int) = this != null && contains(key, id) + fun CompoundTag?.contains(key: String) = this != null && contains(key) // Puts fun CompoundTag?.putBoolean(key: String, value: Boolean) = this?.putBoolean(key, value) + fun CompoundTag?.putByte(key: String, value: Byte) = this?.putByte(key, value) + fun CompoundTag?.putShort(key: String, value: Short) = this?.putShort(key, value) + fun CompoundTag?.putInt(key: String, value: Int) = this?.putInt(key, value) + fun CompoundTag?.putLong(key: String, value: Long) = this?.putLong(key, value) + fun CompoundTag?.putFloat(key: String, value: Float) = this?.putFloat(key, value) + fun CompoundTag?.putDouble(key: String, value: Double) = this?.putDouble(key, value) + fun CompoundTag?.putLongArray(key: String, value: LongArray) = this?.putLongArray(key, value) + fun CompoundTag?.putIntArray(key: String, value: IntArray) = this?.putIntArray(key, value) + fun CompoundTag?.putByteArray(key: String, value: ByteArray) = this?.putByteArray(key, value) + fun CompoundTag?.putCompound(key: String, value: CompoundTag) = put(key, value) + fun CompoundTag?.putString(key: String, value: String) = this?.putString(key, value) + fun CompoundTag?.putList(key: String, value: ListTag) = put(key, value) + fun CompoundTag?.putUUID(key: String, value: UUID) = this?.putUUID(key, value) + fun CompoundTag?.put(key: String, value: Tag) = this?.put(key, value) // Remove @@ -71,42 +108,54 @@ fun CompoundTag?.remove(key: String) = this?.remove(key) @JvmOverloads fun CompoundTag?.getBoolean(key: String, defaultExpected: Boolean = false) = - getIf(key, CompoundTag?::hasNumber, CompoundTag::getBoolean, defaultExpected) + getIf(key, CompoundTag?::hasNumber, CompoundTag::getBoolean, defaultExpected) @JvmOverloads fun CompoundTag?.getByte(key: String, defaultExpected: Byte = 0) = - getIf(key, CompoundTag?::hasNumber, CompoundTag::getByte, defaultExpected) + getIf(key, CompoundTag?::hasNumber, CompoundTag::getByte, defaultExpected) @JvmOverloads fun CompoundTag?.getShort(key: String, defaultExpected: Short = 0) = - getIf(key, CompoundTag?::hasNumber, CompoundTag::getShort, defaultExpected) + getIf(key, CompoundTag?::hasNumber, CompoundTag::getShort, defaultExpected) @JvmOverloads fun CompoundTag?.getInt(key: String, defaultExpected: Int = 0) = - getIf(key, CompoundTag?::hasNumber, CompoundTag::getInt, defaultExpected) + getIf(key, CompoundTag?::hasNumber, CompoundTag::getInt, defaultExpected) @JvmOverloads fun CompoundTag?.getLong(key: String, defaultExpected: Long = 0) = - getIf(key, CompoundTag?::hasNumber, CompoundTag::getLong, defaultExpected) + getIf(key, CompoundTag?::hasNumber, CompoundTag::getLong, defaultExpected) @JvmOverloads fun CompoundTag?.getFloat(key: String, defaultExpected: Float = 0f) = - getIf(key, CompoundTag?::hasNumber, CompoundTag::getFloat, defaultExpected) + getIf(key, CompoundTag?::hasNumber, CompoundTag::getFloat, defaultExpected) @JvmOverloads fun CompoundTag?.getDouble(key: String, defaultExpected: Double = 0.0) = - getIf(key, CompoundTag?::hasNumber, CompoundTag::getDouble, defaultExpected) + getIf(key, CompoundTag?::hasNumber, CompoundTag::getDouble, defaultExpected) + +fun CompoundTag?.getLongArray(key: String) = + getIf(key, CompoundTag?::hasLongArray, CompoundTag::getLongArray) + +fun CompoundTag?.getIntArray(key: String) = + getIf(key, CompoundTag?::hasIntArray, CompoundTag::getIntArray) + +fun CompoundTag?.getByteArray(key: String) = + getIf(key, CompoundTag?::hasByteArray, CompoundTag::getByteArray) -fun CompoundTag?.getLongArray(key: String) = getIf(key, CompoundTag?::hasLongArray, CompoundTag::getLongArray) -fun CompoundTag?.getIntArray(key: String) = getIf(key, CompoundTag?::hasIntArray, CompoundTag::getIntArray) -fun CompoundTag?.getByteArray(key: String) = getIf(key, CompoundTag?::hasByteArray, CompoundTag::getByteArray) fun CompoundTag?.getCompound(key: String): CompoundTag? = - getIf(key, CompoundTag?::hasCompound, CompoundTag::getCompound) + getIf(key, CompoundTag?::hasCompound, CompoundTag::getCompound) + +fun CompoundTag?.getString(key: String) = + getIf(key, CompoundTag?::hasString, CompoundTag::getString) -fun CompoundTag?.getString(key: String) = getIf(key, CompoundTag?::hasString, CompoundTag::getString) fun CompoundTag?.getList(key: String, objType: Byte) = getList(key, objType.toInt()) -fun CompoundTag?.getList(key: String, objType: Int) = getIf(key, { hasList(key, objType) }) { getList(it, objType) } + +fun CompoundTag?.getList(key: String, objType: Int) = + getIf(key, { hasList(key, objType) }) { getList(it, objType) } + fun CompoundTag?.getUUID(key: String) = getIf(key, CompoundTag?::hasUUID, CompoundTag::getUUID) + fun CompoundTag?.get(key: String) = getIf(key, CompoundTag?::contains, CompoundTag::get) @JvmSynthetic @@ -115,118 +164,158 @@ fun CompoundTag.getList(key: String, objType: Byte): ListTag = getList(key, objT // Get-or-create -fun CompoundTag.getOrCreateCompound(key: String): CompoundTag = getCompound(key) ?: CompoundTag().also { putCompound(key, it) } +fun CompoundTag.getOrCreateCompound(key: String): CompoundTag = + getCompound(key) ?: CompoundTag().also { putCompound(key, it) } + fun CompoundTag.getOrCreateList(key: String, objType: Byte) = getOrCreateList(key, objType.toInt()) -fun CompoundTag.getOrCreateList(key: String, objType: Int): ListTag = if (hasList(key, objType)) getList(key, objType) else ListTag().also { putList(key, it) } + +fun CompoundTag.getOrCreateList(key: String, objType: Int): ListTag = + if (hasList(key, objType)) getList(key, objType) else ListTag().also { putList(key, it) } // ================================================================================================================ Tag -val Tag.asBoolean get() = asByte == 0.toByte() -val Tag.asByte get() = (this as? NumericTag)?.asByte ?: 0.toByte() -val Tag.asShort get() = (this as? NumericTag)?.asShort ?: 0.toShort() -val Tag.asInt get() = (this as? NumericTag)?.asInt ?: 0 -val Tag.asLong get() = (this as? NumericTag)?.asLong ?: 0L -val Tag.asFloat get() = (this as? NumericTag)?.asFloat ?: 0F -val Tag.asDouble get() = (this as? NumericTag)?.asDouble ?: 0.0 +val Tag.asBoolean + get() = asByte == 0.toByte() +val Tag.asByte + get() = (this as? NumericTag)?.asByte ?: 0.toByte() +val Tag.asShort + get() = (this as? NumericTag)?.asShort ?: 0.toShort() +val Tag.asInt + get() = (this as? NumericTag)?.asInt ?: 0 +val Tag.asLong + get() = (this as? NumericTag)?.asLong ?: 0L +val Tag.asFloat + get() = (this as? NumericTag)?.asFloat ?: 0F +val Tag.asDouble + get() = (this as? NumericTag)?.asDouble ?: 0.0 val Tag.asLongArray: LongArray - get() = when (this) { - is LongArrayTag -> this.asLongArray - is IntArrayTag -> { - val array = this.asIntArray - LongArray(array.size) { array[it].toLong() } - } - is ByteArrayTag -> { - val array = this.asByteArray - LongArray(array.size) { array[it].toLong() } - } - else -> LongArray(0) - } + get() = + when (this) { + is LongArrayTag -> this.asLongArray + is IntArrayTag -> { + val array = this.asIntArray + LongArray(array.size) { array[it].toLong() } + } + is ByteArrayTag -> { + val array = this.asByteArray + LongArray(array.size) { array[it].toLong() } + } + else -> LongArray(0) + } val Tag.asIntArray: IntArray - get() = when (this) { - is IntArrayTag -> this.asIntArray - is LongArrayTag -> { - val array = this.asLongArray - IntArray(array.size) { array[it].toInt() } - } - is ByteArrayTag -> { - val array = this.asByteArray - IntArray(array.size) { array[it].toInt() } - } - else -> IntArray(0) - } + get() = + when (this) { + is IntArrayTag -> this.asIntArray + is LongArrayTag -> { + val array = this.asLongArray + IntArray(array.size) { array[it].toInt() } + } + is ByteArrayTag -> { + val array = this.asByteArray + IntArray(array.size) { array[it].toInt() } + } + else -> IntArray(0) + } val Tag.asByteArray: ByteArray - get() = when (this) { - is ByteArrayTag -> this.asByteArray - is LongArrayTag -> { - val array = this.asLongArray - ByteArray(array.size) { array[it].toByte() } - } - is IntArrayTag -> { - val array = this.asIntArray - ByteArray(array.size) { array[it].toByte() } - } - else -> ByteArray(0) - } - -val Tag.asCompound get() = this as? CompoundTag ?: CompoundTag() + get() = + when (this) { + is ByteArrayTag -> this.asByteArray + is LongArrayTag -> { + val array = this.asLongArray + ByteArray(array.size) { array[it].toByte() } + } + is IntArrayTag -> { + val array = this.asIntArray + ByteArray(array.size) { array[it].toByte() } + } + else -> ByteArray(0) + } + +val Tag.asCompound + get() = this as? CompoundTag ?: CompoundTag() // asString is defined in Tag -val Tag.asList get() = this as? ListTag ?: ListTag() -val Tag.asUUID: UUID get() = if (this is IntArrayTag && this.size == 4) NbtUtils.loadUUID(this) else UUID(0, 0) +val Tag.asList + get() = this as? ListTag ?: ListTag() +val Tag.asUUID: UUID + get() = if (this is IntArrayTag && this.size == 4) NbtUtils.loadUUID(this) else UUID(0, 0) // ========================================================================================================== ItemStack // Checks for containment fun ItemStack.hasNumber(key: String) = tag.hasNumber(key) + fun ItemStack.hasByte(key: String) = tag.hasByte(key) + fun ItemStack.hasShort(key: String) = tag.hasShort(key) + fun ItemStack.hasInt(key: String) = tag.hasInt(key) + fun ItemStack.hasLong(key: String) = tag.hasLong(key) + fun ItemStack.hasFloat(key: String) = tag.hasFloat(key) + fun ItemStack.hasDouble(key: String) = tag.hasDouble(key) + fun ItemStack.hasLongArray(key: String) = tag.hasLongArray(key) + fun ItemStack.hasIntArray(key: String) = tag.hasIntArray(key) + fun ItemStack.hasByteArray(key: String) = tag.hasByteArray(key) + fun ItemStack.hasCompound(key: String) = tag.hasCompound(key) + fun ItemStack.hasString(key: String) = tag.hasString(key) + fun ItemStack.hasList(key: String) = tag.hasList(key) + fun ItemStack.hasList(key: String, objType: Int) = tag.hasList(key, objType) + fun ItemStack.hasList(key: String, objType: Byte) = tag.hasList(key, objType) + fun ItemStack.hasUUID(key: String) = tag.hasUUID(key) -@JvmName("contains") -fun ItemStack.containsTag(key: String) = tag.contains(key) +@JvmName("contains") fun ItemStack.containsTag(key: String) = tag.contains(key) -@JvmName("contains") -fun ItemStack.containsTag(key: String, id: Byte) = tag.contains(key, id) +@JvmName("contains") fun ItemStack.containsTag(key: String, id: Byte) = tag.contains(key, id) -@JvmName("contains") -fun ItemStack.containsTag(key: String, id: Int) = tag.contains(key, id) +@JvmName("contains") fun ItemStack.containsTag(key: String, id: Int) = tag.contains(key, id) // Puts fun ItemStack.putBoolean(key: String, value: Boolean) = orCreateTag.putBoolean(key, value) + fun ItemStack.putByte(key: String, value: Byte) = orCreateTag.putByte(key, value) + fun ItemStack.putShort(key: String, value: Short) = orCreateTag.putShort(key, value) + fun ItemStack.putInt(key: String, value: Int) = orCreateTag.putInt(key, value) + fun ItemStack.putLong(key: String, value: Long) = orCreateTag.putLong(key, value) + fun ItemStack.putFloat(key: String, value: Float) = orCreateTag.putFloat(key, value) + fun ItemStack.putDouble(key: String, value: Double) = orCreateTag.putDouble(key, value) fun ItemStack.putLongArray(key: String, value: LongArray) = orCreateTag.putLongArray(key, value) + fun ItemStack.putIntArray(key: String, value: IntArray) = orCreateTag.putIntArray(key, value) + fun ItemStack.putByteArray(key: String, value: ByteArray) = orCreateTag.putByteArray(key, value) + fun ItemStack.putCompound(key: String, value: CompoundTag) = putTag(key, value) + fun ItemStack.putString(key: String, value: String) = orCreateTag.putString(key, value) + fun ItemStack.putList(key: String, value: ListTag) = putTag(key, value) + fun ItemStack.putUUID(key: String, value: UUID) = orCreateTag.putUUID(key, value) -@JvmName("put") -fun ItemStack.putTag(key: String, value: Tag) = orCreateTag.put(key, value) +@JvmName("put") fun ItemStack.putTag(key: String, value: Tag) = orCreateTag.put(key, value) // Remove @@ -235,7 +324,8 @@ fun ItemStack.remove(key: String) = removeTagKey(key) // Gets @JvmOverloads -fun ItemStack.getBoolean(key: String, defaultExpected: Boolean = false) = tag.getBoolean(key, defaultExpected) +fun ItemStack.getBoolean(key: String, defaultExpected: Boolean = false) = + tag.getBoolean(key, defaultExpected) @JvmOverloads fun ItemStack.getByte(key: String, defaultExpected: Byte = 0) = tag.getByte(key, defaultExpected) @@ -250,24 +340,34 @@ fun ItemStack.getInt(key: String, defaultExpected: Int = 0) = tag.getInt(key, de fun ItemStack.getLong(key: String, defaultExpected: Long = 0) = tag.getLong(key, defaultExpected) @JvmOverloads -fun ItemStack.getFloat(key: String, defaultExpected: Float = 0f) = tag.getFloat(key, defaultExpected) +fun ItemStack.getFloat(key: String, defaultExpected: Float = 0f) = + tag.getFloat(key, defaultExpected) @JvmOverloads -fun ItemStack.getDouble(key: String, defaultExpected: Double = 0.0) = tag.getDouble(key, defaultExpected) +fun ItemStack.getDouble(key: String, defaultExpected: Double = 0.0) = + tag.getDouble(key, defaultExpected) fun ItemStack.getLongArray(key: String) = tag.getLongArray(key) + fun ItemStack.getIntArray(key: String) = tag.getIntArray(key) + fun ItemStack.getByteArray(key: String) = tag.getByteArray(key) + fun ItemStack.getCompound(key: String) = tag.getCompound(key) + fun ItemStack.getString(key: String) = tag.getString(key) + fun ItemStack.getList(key: String, objType: Int) = tag.getList(key, objType) + fun ItemStack.getUUID(key: String) = tag.getUUID(key) -@JvmName("get") -fun ItemStack.getTag(key: String) = tag.get(key) +@JvmName("get") fun ItemStack.getTag(key: String) = tag.get(key) // Get-or-create fun ItemStack.getOrCreateCompound(key: String): CompoundTag = getOrCreateTagElement(key) -fun ItemStack.getOrCreateList(key: String, objType: Byte) = orCreateTag.getOrCreateList(key, objType) + +fun ItemStack.getOrCreateList(key: String, objType: Byte) = + orCreateTag.getOrCreateList(key, objType) + fun ItemStack.getOrCreateList(key: String, objType: Int) = orCreateTag.getOrCreateList(key, objType) diff --git a/Common/src/main/java/at/petrak/hexcasting/client/ClientTickCounter.java b/Common/src/main/java/at/petrak/hexcasting/client/ClientTickCounter.java index e45ba75643..45c67f4786 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/ClientTickCounter.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/ClientTickCounter.java @@ -4,22 +4,22 @@ import net.minecraft.client.Minecraft; public class ClientTickCounter { - public static long ticksInGame = 0L; - public static float partialTicks = 0.0F; + public static long ticksInGame = 0L; + public static float partialTicks = 0.0F; - public static float getTotal() { - return (float) ticksInGame + partialTicks; - } + public static float getTotal() { + return (float) ticksInGame + partialTicks; + } - public static void renderTickStart(float renderTickTime) { - partialTicks = renderTickTime; - } + public static void renderTickStart(float renderTickTime) { + partialTicks = renderTickTime; + } - public static void clientTickEnd() { - if (!Minecraft.getInstance().isPaused()) { - ++ticksInGame; - partialTicks = 0.0F; - GaslightingTracker.postFrameCheckRendered(); - } - } + public static void clientTickEnd() { + if (!Minecraft.getInstance().isPaused()) { + ++ticksInGame; + partialTicks = 0.0F; + GaslightingTracker.postFrameCheckRendered(); + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/PatternShapeMatcher.java b/Common/src/main/java/at/petrak/hexcasting/client/PatternShapeMatcher.java index dbe366ceb9..b8867c5aa2 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/PatternShapeMatcher.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/PatternShapeMatcher.java @@ -1,4 +1,3 @@ package at.petrak.hexcasting.client; -public class PatternShapeMatcher { -} +public class PatternShapeMatcher {} diff --git a/Common/src/main/java/at/petrak/hexcasting/client/RegisterClientStuff.java b/Common/src/main/java/at/petrak/hexcasting/client/RegisterClientStuff.java index 75658fa63a..cd387da70e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/RegisterClientStuff.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/RegisterClientStuff.java @@ -1,5 +1,7 @@ package at.petrak.hexcasting.client; +import static at.petrak.hexcasting.api.HexAPI.modLoc; + import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.item.IotaHolderItem; import at.petrak.hexcasting.api.item.MediaHolderItem; @@ -24,13 +26,14 @@ import at.petrak.hexcasting.common.lib.HexBlocks; import at.petrak.hexcasting.common.lib.HexItems; import at.petrak.hexcasting.xplat.IClientXplatAbstractions; +import java.util.*; +import java.util.function.*; import net.minecraft.client.color.block.BlockColor; import net.minecraft.client.color.item.ItemColor; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.ModelBakery; -import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; @@ -41,246 +44,270 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import org.jetbrains.annotations.NotNull; -import java.util.*; -import java.util.function.*; - -import static at.petrak.hexcasting.api.HexAPI.modLoc; - public class RegisterClientStuff { - public static Map> QUENCHED_ALLAY_VARIANTS = new HashMap<>(); - private static final Map QUENCHED_ALLAY_TYPES = Map.of( - HexBlocks.QUENCHED_ALLAY, false, - HexBlocks.QUENCHED_ALLAY_TILES, true, - HexBlocks.QUENCHED_ALLAY_BRICKS, true, - HexBlocks.QUENCHED_ALLAY_BRICKS_SMALL, true); - - public static void init() { - registerSealableDataHolderOverrides(HexItems.FOCUS, - stack -> HexItems.FOCUS.readIotaTag(stack) != null, - ItemFocus::isSealed); - registerSealableDataHolderOverrides(HexItems.SPELLBOOK, - stack -> HexItems.SPELLBOOK.readIotaTag(stack) != null, - ItemSpellbook::isSealed); - registerVariantOverrides(HexItems.FOCUS, HexItems.FOCUS::getVariant); - registerVariantOverrides(HexItems.SPELLBOOK, HexItems.SPELLBOOK::getVariant); - registerVariantOverrides(HexItems.CYPHER, HexItems.CYPHER::getVariant); - registerVariantOverrides(HexItems.TRINKET, HexItems.TRINKET::getVariant); - registerVariantOverrides(HexItems.ARTIFACT, HexItems.ARTIFACT::getVariant); - IClientXplatAbstractions.INSTANCE.registerItemProperty(HexItems.THOUGHT_KNOT, ItemThoughtKnot.WRITTEN_PRED, - (stack, level, holder, holderID) -> { - if (NBTHelper.contains(stack, ItemThoughtKnot.TAG_DATA)) { - return 1; - } else { - return 0; - } - }); - - registerPackagedSpellOverrides(HexItems.CYPHER); - registerPackagedSpellOverrides(HexItems.TRINKET); - registerPackagedSpellOverrides(HexItems.ARTIFACT); - - var x = IClientXplatAbstractions.INSTANCE; - x.registerItemProperty(HexItems.BATTERY, ItemMediaBattery.MEDIA_PREDICATE, - (stack, level, holder, holderID) -> { - var item = (MediaHolderItem) stack.getItem(); - return item.getMediaFullness(stack); - }); - x.registerItemProperty(HexItems.BATTERY, ItemMediaBattery.MAX_MEDIA_PREDICATE, - (stack, level, holder, holderID) -> { - var item = (ItemMediaBattery) stack.getItem(); - var max = item.getMaxMedia(stack); - return 1.049658f * (float) Math.log((float) max / MediaConstants.CRYSTAL_UNIT + 9.06152f) - 2.1436f; - }); - - registerScrollOverrides(HexItems.SCROLL_SMOL); - registerScrollOverrides(HexItems.SCROLL_MEDIUM); - registerScrollOverrides(HexItems.SCROLL_LARGE); - - x.registerItemProperty(HexItems.SLATE, ItemSlate.WRITTEN_PRED, - (stack, level, holder, holderID) -> ItemSlate.hasPattern(stack) ? 1f : 0f); - - registerWandOverrides(HexItems.STAFF_OAK); - registerWandOverrides(HexItems.STAFF_BIRCH); - registerWandOverrides(HexItems.STAFF_SPRUCE); - registerWandOverrides(HexItems.STAFF_JUNGLE); - registerWandOverrides(HexItems.STAFF_DARK_OAK); - registerWandOverrides(HexItems.STAFF_ACACIA); - registerWandOverrides(HexItems.STAFF_EDIFIED); - // purposely skip quenched - registerWandOverrides(HexItems.STAFF_MINDSPLICE); - - registerGaslight4(HexItems.STAFF_QUENCHED); - registerGaslight4(HexBlocks.QUENCHED_ALLAY.asItem()); - registerGaslight4(HexBlocks.QUENCHED_ALLAY_TILES.asItem()); - registerGaslight4(HexBlocks.QUENCHED_ALLAY_BRICKS.asItem()); - registerGaslight4(HexBlocks.QUENCHED_ALLAY_BRICKS_SMALL.asItem()); - registerGaslight4(HexItems.QUENCHED_SHARD); - - x.setRenderLayer(HexBlocks.CONJURED_LIGHT, RenderType.cutout()); - x.setRenderLayer(HexBlocks.CONJURED_BLOCK, RenderType.cutout()); - x.setRenderLayer(HexBlocks.EDIFIED_DOOR, RenderType.cutout()); - x.setRenderLayer(HexBlocks.EDIFIED_TRAPDOOR, RenderType.cutout()); - x.setRenderLayer(HexBlocks.AKASHIC_BOOKSHELF, RenderType.cutout()); - x.setRenderLayer(HexBlocks.SCONCE, RenderType.cutout()); - - x.setRenderLayer(HexBlocks.AMETHYST_EDIFIED_LEAVES, RenderType.cutoutMipped()); - x.setRenderLayer(HexBlocks.AVENTURINE_EDIFIED_LEAVES, RenderType.cutoutMipped()); - x.setRenderLayer(HexBlocks.CITRINE_EDIFIED_LEAVES, RenderType.cutoutMipped()); - - x.setRenderLayer(HexBlocks.AKASHIC_RECORD, RenderType.translucent()); - x.setRenderLayer(HexBlocks.QUENCHED_ALLAY, RenderType.translucent()); - - x.registerEntityRenderer(HexEntities.WALL_SCROLL, WallScrollRenderer::new); - -// for (var tex : new ResourceLocation[]{ -// PatternTooltipComponent.PRISTINE_BG, -// PatternTooltipComponent.ANCIENT_BG, -// PatternTooltipComponent.SLATE_BG -// }) { -// Minecraft.getInstance().getTextureManager().bindForSetup(tex); -// } - - ScryingLensOverlays.addScryingLensStuff(); - } - - private static void registerGaslight4(Item item) { - IClientXplatAbstractions.INSTANCE.registerItemProperty(item, - GaslightingTracker.GASLIGHTING_PRED, (stack, level, holder, holderID) -> - Math.abs(GaslightingTracker.getGaslightingAmount() % 4)); - } - - public static void registerColorProviders(BiConsumer itemColorRegistry, - BiConsumer blockColorRegistry) { - itemColorRegistry.accept(makeIotaStorageColorizer(HexItems.FOCUS::getColor), HexItems.FOCUS); - itemColorRegistry.accept(makeIotaStorageColorizer(HexItems.SPELLBOOK::getColor), HexItems.SPELLBOOK); - itemColorRegistry.accept(makeIotaStorageColorizer(HexItems.THOUGHT_KNOT::getColor), HexItems.THOUGHT_KNOT); - - blockColorRegistry.accept((bs, level, pos, idx) -> { - if (!bs.getValue(BlockAkashicBookshelf.HAS_BOOKS) || level == null || pos == null) { - return 0xff_ffffff; - } - var tile = level.getBlockEntity(pos); - if (!(tile instanceof BlockEntityAkashicBookshelf beas)) { - // this gets called for particles for some irritating reason - return 0xff_ffffff; - } - var iotaTag = beas.getIotaTag(); - if (iotaTag == null) { - return 0xff_ffffff; - } - return IotaType.getColor(iotaTag); - }, HexBlocks.AKASHIC_BOOKSHELF); - } - - /** - * Helper function to colorize the layers of an item that stores an iota, in the manner of foci and spellbooks. - *
- * 0 = base; 1 = overlay - */ - public static ItemColor makeIotaStorageColorizer(ToIntFunction getColor) { - return (stack, idx) -> { - if (idx == 1) { - return getColor.applyAsInt(stack); - } - return 0xff_ffffff; - }; - } - - private static void registerSealableDataHolderOverrides(IotaHolderItem item, Predicate hasIota, - Predicate isSealed) { - IClientXplatAbstractions.INSTANCE.registerItemProperty((Item) item, ItemFocus.OVERLAY_PRED, - (stack, level, holder, holderID) -> { - if (!hasIota.test(stack) && !NBTHelper.hasString(stack, IotaHolderItem.TAG_OVERRIDE_VISUALLY)) { - return 0; - } - if (!isSealed.test(stack)) { - return 1; - } - return 2; - }); - } - - private static void registerVariantOverrides(VariantItem item, Function variant) { - IClientXplatAbstractions.INSTANCE.registerItemProperty((Item) item, ItemFocus.VARIANT_PRED, - (stack, level, holder, holderID) -> variant.apply(stack)); - } - - private static void registerScrollOverrides(ItemScroll scroll) { - IClientXplatAbstractions.INSTANCE.registerItemProperty(scroll, ItemScroll.ANCIENT_PREDICATE, - (stack, level, holder, holderID) -> NBTHelper.hasString(stack, ItemScroll.TAG_OP_ID) ? 1f : 0f); - } - - private static void registerPackagedSpellOverrides(ItemPackagedHex item) { - IClientXplatAbstractions.INSTANCE.registerItemProperty(item, ItemPackagedHex.HAS_PATTERNS_PRED, - (stack, level, holder, holderID) -> - item.hasHex(stack) ? 1f : 0f - ); - } - - private static void registerWandOverrides(ItemStaff item) { - IClientXplatAbstractions.INSTANCE.registerItemProperty(item, ItemStaff.FUNNY_LEVEL_PREDICATE, - (stack, level, holder, holderID) -> { - if (!stack.hasCustomHoverName()) { - return 0; - } - var name = stack.getHoverName().getString().toLowerCase(Locale.ROOT); - if (name.contains("old")) { - return 1f; - } else if (name.contains("cherry")) { - return 2f; - } else { - return 0f; - } - }); - } - - public static void registerBlockEntityRenderers(@NotNull BlockEntityRendererRegisterererer registerer) { - registerer.registerBlockEntityRenderer(HexBlockEntities.SLATE_TILE, BlockEntitySlateRenderer::new); - registerer.registerBlockEntityRenderer(HexBlockEntities.AKASHIC_BOOKSHELF_TILE, - BlockEntityAkashicBookshelfRenderer::new); - registerer.registerBlockEntityRenderer(HexBlockEntities.QUENCHED_ALLAY_TILE, - BlockEntityQuenchedAllayRenderer::new); - registerer.registerBlockEntityRenderer(HexBlockEntities.QUENCHED_ALLAY_TILES_TILE, - BlockEntityQuenchedAllayRenderer::new); - registerer.registerBlockEntityRenderer(HexBlockEntities.QUENCHED_ALLAY_BRICKS_TILE, - BlockEntityQuenchedAllayRenderer::new); - registerer.registerBlockEntityRenderer(HexBlockEntities.QUENCHED_ALLAY_BRICKS_SMALL_TILE, - BlockEntityQuenchedAllayRenderer::new); - } - - @FunctionalInterface - public interface BlockEntityRendererRegisterererer { - void registerBlockEntityRenderer(BlockEntityType type, - BlockEntityRendererProvider berp); - } - - public static void onModelRegister(ResourceManager recMan, Consumer extraModels) { - for (var type : QUENCHED_ALLAY_TYPES.entrySet()) { - var blockLoc = BuiltInRegistries.BLOCK.getKey(type.getKey()); - var locStart = "block/"; - if (type.getValue()) - locStart += "deco/"; - - for (int i = 0; i < BlockQuenchedAllay.VARIANTS; i++) { - extraModels.accept(modLoc( locStart + blockLoc.getPath() + "_" + i)); - } - } - } - - public static void onModelBake(ModelBakery loader, Map map) { - for (var type : QUENCHED_ALLAY_TYPES.entrySet()) { - var blockLoc = BuiltInRegistries.BLOCK.getKey(type.getKey()); - var locStart = "block/"; - if (type.getValue()) - locStart += "deco/"; - - var list = new ArrayList(); - for (int i = 0; i < BlockQuenchedAllay.VARIANTS; i++) { - var variantLoc = modLoc(locStart + blockLoc.getPath() + "_" + i); - var model = map.get(variantLoc); - list.add(model); - } - QUENCHED_ALLAY_VARIANTS.put(blockLoc, list); - } - } + public static Map> QUENCHED_ALLAY_VARIANTS = new HashMap<>(); + private static final Map QUENCHED_ALLAY_TYPES = + Map.of( + HexBlocks.QUENCHED_ALLAY, false, + HexBlocks.QUENCHED_ALLAY_TILES, true, + HexBlocks.QUENCHED_ALLAY_BRICKS, true, + HexBlocks.QUENCHED_ALLAY_BRICKS_SMALL, true); + + public static void init() { + registerSealableDataHolderOverrides( + HexItems.FOCUS, stack -> HexItems.FOCUS.readIotaTag(stack) != null, ItemFocus::isSealed); + registerSealableDataHolderOverrides( + HexItems.SPELLBOOK, + stack -> HexItems.SPELLBOOK.readIotaTag(stack) != null, + ItemSpellbook::isSealed); + registerVariantOverrides(HexItems.FOCUS, HexItems.FOCUS::getVariant); + registerVariantOverrides(HexItems.SPELLBOOK, HexItems.SPELLBOOK::getVariant); + registerVariantOverrides(HexItems.CYPHER, HexItems.CYPHER::getVariant); + registerVariantOverrides(HexItems.TRINKET, HexItems.TRINKET::getVariant); + registerVariantOverrides(HexItems.ARTIFACT, HexItems.ARTIFACT::getVariant); + IClientXplatAbstractions.INSTANCE.registerItemProperty( + HexItems.THOUGHT_KNOT, + ItemThoughtKnot.WRITTEN_PRED, + (stack, level, holder, holderID) -> { + if (NBTHelper.contains(stack, ItemThoughtKnot.TAG_DATA)) { + return 1; + } else { + return 0; + } + }); + + registerPackagedSpellOverrides(HexItems.CYPHER); + registerPackagedSpellOverrides(HexItems.TRINKET); + registerPackagedSpellOverrides(HexItems.ARTIFACT); + + var x = IClientXplatAbstractions.INSTANCE; + x.registerItemProperty( + HexItems.BATTERY, + ItemMediaBattery.MEDIA_PREDICATE, + (stack, level, holder, holderID) -> { + var item = (MediaHolderItem) stack.getItem(); + return item.getMediaFullness(stack); + }); + x.registerItemProperty( + HexItems.BATTERY, + ItemMediaBattery.MAX_MEDIA_PREDICATE, + (stack, level, holder, holderID) -> { + var item = (ItemMediaBattery) stack.getItem(); + var max = item.getMaxMedia(stack); + return 1.049658f * (float) Math.log((float) max / MediaConstants.CRYSTAL_UNIT + 9.06152f) + - 2.1436f; + }); + + registerScrollOverrides(HexItems.SCROLL_SMOL); + registerScrollOverrides(HexItems.SCROLL_MEDIUM); + registerScrollOverrides(HexItems.SCROLL_LARGE); + + x.registerItemProperty( + HexItems.SLATE, + ItemSlate.WRITTEN_PRED, + (stack, level, holder, holderID) -> ItemSlate.hasPattern(stack) ? 1f : 0f); + + registerWandOverrides(HexItems.STAFF_OAK); + registerWandOverrides(HexItems.STAFF_BIRCH); + registerWandOverrides(HexItems.STAFF_SPRUCE); + registerWandOverrides(HexItems.STAFF_JUNGLE); + registerWandOverrides(HexItems.STAFF_DARK_OAK); + registerWandOverrides(HexItems.STAFF_ACACIA); + registerWandOverrides(HexItems.STAFF_EDIFIED); + // purposely skip quenched + registerWandOverrides(HexItems.STAFF_MINDSPLICE); + + registerGaslight4(HexItems.STAFF_QUENCHED); + registerGaslight4(HexBlocks.QUENCHED_ALLAY.asItem()); + registerGaslight4(HexBlocks.QUENCHED_ALLAY_TILES.asItem()); + registerGaslight4(HexBlocks.QUENCHED_ALLAY_BRICKS.asItem()); + registerGaslight4(HexBlocks.QUENCHED_ALLAY_BRICKS_SMALL.asItem()); + registerGaslight4(HexItems.QUENCHED_SHARD); + + x.setRenderLayer(HexBlocks.CONJURED_LIGHT, RenderType.cutout()); + x.setRenderLayer(HexBlocks.CONJURED_BLOCK, RenderType.cutout()); + x.setRenderLayer(HexBlocks.EDIFIED_DOOR, RenderType.cutout()); + x.setRenderLayer(HexBlocks.EDIFIED_TRAPDOOR, RenderType.cutout()); + x.setRenderLayer(HexBlocks.AKASHIC_BOOKSHELF, RenderType.cutout()); + x.setRenderLayer(HexBlocks.SCONCE, RenderType.cutout()); + + x.setRenderLayer(HexBlocks.AMETHYST_EDIFIED_LEAVES, RenderType.cutoutMipped()); + x.setRenderLayer(HexBlocks.AVENTURINE_EDIFIED_LEAVES, RenderType.cutoutMipped()); + x.setRenderLayer(HexBlocks.CITRINE_EDIFIED_LEAVES, RenderType.cutoutMipped()); + + x.setRenderLayer(HexBlocks.AKASHIC_RECORD, RenderType.translucent()); + x.setRenderLayer(HexBlocks.QUENCHED_ALLAY, RenderType.translucent()); + + x.registerEntityRenderer(HexEntities.WALL_SCROLL, WallScrollRenderer::new); + + // for (var tex : new ResourceLocation[]{ + // PatternTooltipComponent.PRISTINE_BG, + // PatternTooltipComponent.ANCIENT_BG, + // PatternTooltipComponent.SLATE_BG + // }) { + // Minecraft.getInstance().getTextureManager().bindForSetup(tex); + // } + + ScryingLensOverlays.addScryingLensStuff(); + } + + private static void registerGaslight4(Item item) { + IClientXplatAbstractions.INSTANCE.registerItemProperty( + item, + GaslightingTracker.GASLIGHTING_PRED, + (stack, level, holder, holderID) -> + Math.abs(GaslightingTracker.getGaslightingAmount() % 4)); + } + + public static void registerColorProviders( + BiConsumer itemColorRegistry, + BiConsumer blockColorRegistry) { + itemColorRegistry.accept(makeIotaStorageColorizer(HexItems.FOCUS::getColor), HexItems.FOCUS); + itemColorRegistry.accept( + makeIotaStorageColorizer(HexItems.SPELLBOOK::getColor), HexItems.SPELLBOOK); + itemColorRegistry.accept( + makeIotaStorageColorizer(HexItems.THOUGHT_KNOT::getColor), HexItems.THOUGHT_KNOT); + + blockColorRegistry.accept( + (bs, level, pos, idx) -> { + if (!bs.getValue(BlockAkashicBookshelf.HAS_BOOKS) || level == null || pos == null) { + return 0xff_ffffff; + } + var tile = level.getBlockEntity(pos); + if (!(tile instanceof BlockEntityAkashicBookshelf beas)) { + // this gets called for particles for some irritating reason + return 0xff_ffffff; + } + var iotaTag = beas.getIotaTag(); + if (iotaTag == null) { + return 0xff_ffffff; + } + return IotaType.getColor(iotaTag); + }, + HexBlocks.AKASHIC_BOOKSHELF); + } + + /** + * Helper function to colorize the layers of an item that stores an iota, in the manner of foci + * and spellbooks.
+ * 0 = base; 1 = overlay + */ + public static ItemColor makeIotaStorageColorizer(ToIntFunction getColor) { + return (stack, idx) -> { + if (idx == 1) { + return getColor.applyAsInt(stack); + } + return 0xff_ffffff; + }; + } + + private static void registerSealableDataHolderOverrides( + IotaHolderItem item, Predicate hasIota, Predicate isSealed) { + IClientXplatAbstractions.INSTANCE.registerItemProperty( + (Item) item, + ItemFocus.OVERLAY_PRED, + (stack, level, holder, holderID) -> { + if (!hasIota.test(stack) + && !NBTHelper.hasString(stack, IotaHolderItem.TAG_OVERRIDE_VISUALLY)) { + return 0; + } + if (!isSealed.test(stack)) { + return 1; + } + return 2; + }); + } + + private static void registerVariantOverrides( + VariantItem item, Function variant) { + IClientXplatAbstractions.INSTANCE.registerItemProperty( + (Item) item, + ItemFocus.VARIANT_PRED, + (stack, level, holder, holderID) -> variant.apply(stack)); + } + + private static void registerScrollOverrides(ItemScroll scroll) { + IClientXplatAbstractions.INSTANCE.registerItemProperty( + scroll, + ItemScroll.ANCIENT_PREDICATE, + (stack, level, holder, holderID) -> + NBTHelper.hasString(stack, ItemScroll.TAG_OP_ID) ? 1f : 0f); + } + + private static void registerPackagedSpellOverrides(ItemPackagedHex item) { + IClientXplatAbstractions.INSTANCE.registerItemProperty( + item, + ItemPackagedHex.HAS_PATTERNS_PRED, + (stack, level, holder, holderID) -> item.hasHex(stack) ? 1f : 0f); + } + + private static void registerWandOverrides(ItemStaff item) { + IClientXplatAbstractions.INSTANCE.registerItemProperty( + item, + ItemStaff.FUNNY_LEVEL_PREDICATE, + (stack, level, holder, holderID) -> { + if (!stack.hasCustomHoverName()) { + return 0; + } + var name = stack.getHoverName().getString().toLowerCase(Locale.ROOT); + if (name.contains("old")) { + return 1f; + } else if (name.contains("cherry")) { + return 2f; + } else { + return 0f; + } + }); + } + + public static void registerBlockEntityRenderers( + @NotNull BlockEntityRendererRegisterererer registerer) { + registerer.registerBlockEntityRenderer( + HexBlockEntities.SLATE_TILE, BlockEntitySlateRenderer::new); + registerer.registerBlockEntityRenderer( + HexBlockEntities.AKASHIC_BOOKSHELF_TILE, BlockEntityAkashicBookshelfRenderer::new); + registerer.registerBlockEntityRenderer( + HexBlockEntities.QUENCHED_ALLAY_TILE, BlockEntityQuenchedAllayRenderer::new); + registerer.registerBlockEntityRenderer( + HexBlockEntities.QUENCHED_ALLAY_TILES_TILE, BlockEntityQuenchedAllayRenderer::new); + registerer.registerBlockEntityRenderer( + HexBlockEntities.QUENCHED_ALLAY_BRICKS_TILE, BlockEntityQuenchedAllayRenderer::new); + registerer.registerBlockEntityRenderer( + HexBlockEntities.QUENCHED_ALLAY_BRICKS_SMALL_TILE, BlockEntityQuenchedAllayRenderer::new); + } + + @FunctionalInterface + public interface BlockEntityRendererRegisterererer { + void registerBlockEntityRenderer( + BlockEntityType type, BlockEntityRendererProvider berp); + } + + public static void onModelRegister( + ResourceManager recMan, Consumer extraModels) { + for (var type : QUENCHED_ALLAY_TYPES.entrySet()) { + var blockLoc = BuiltInRegistries.BLOCK.getKey(type.getKey()); + var locStart = "block/"; + if (type.getValue()) locStart += "deco/"; + + for (int i = 0; i < BlockQuenchedAllay.VARIANTS; i++) { + extraModels.accept(modLoc(locStart + blockLoc.getPath() + "_" + i)); + } + } + } + + public static void onModelBake(ModelBakery loader, Map map) { + for (var type : QUENCHED_ALLAY_TYPES.entrySet()) { + var blockLoc = BuiltInRegistries.BLOCK.getKey(type.getKey()); + var locStart = "block/"; + if (type.getValue()) locStart += "deco/"; + + var list = new ArrayList(); + for (int i = 0; i < BlockQuenchedAllay.VARIANTS; i++) { + var variantLoc = modLoc(locStart + blockLoc.getPath() + "_" + i); + var model = map.get(variantLoc); + list.add(model); + } + QUENCHED_ALLAY_VARIANTS.put(blockLoc, list); + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java b/Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java index c6073517cc..5c22ffeefb 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java @@ -9,51 +9,54 @@ import net.minecraft.world.item.Item; public class ShiftScrollListener { - private static double mainHandDelta = 0; - private static double offHandDelta = 0; - - public static boolean onScrollInGameplay(double delta) { - if (Minecraft.getInstance().screen != null) { - return false; - } - - return onScroll(delta, true); - } - - public static boolean onScroll(double delta, boolean needsSneaking) { - LocalPlayer player = Minecraft.getInstance().player; - // not .isCrouching! that fails for players who are not on the ground - // yes, this does work if you remap your sneak key - if (player != null && (player.isShiftKeyDown() || !needsSneaking)) { - // Spectators shouldn't interact with items! - if (player.isSpectator()) { - return false; - } - - if (IsScrollableItem(player.getMainHandItem().getItem())) { - mainHandDelta += delta; - return true; - } else if (IsScrollableItem(player.getOffhandItem().getItem())) { - offHandDelta += delta; - return true; - } - } - - return false; - } - - public static void clientTickEnd() { - if (mainHandDelta != 0 || offHandDelta != 0) { - IClientXplatAbstractions.INSTANCE.sendPacketToServer( - new MsgShiftScrollC2S(mainHandDelta, offHandDelta, Minecraft.getInstance().options.keySprint.isDown(), - HexConfig.client().invertSpellbookScrollDirection(), - HexConfig.client().invertAbacusScrollDirection())); - mainHandDelta = 0; - offHandDelta = 0; - } - } - - private static boolean IsScrollableItem(Item item) { - return item == HexItems.SPELLBOOK || item == HexItems.ABACUS; - } + private static double mainHandDelta = 0; + private static double offHandDelta = 0; + + public static boolean onScrollInGameplay(double delta) { + if (Minecraft.getInstance().screen != null) { + return false; + } + + return onScroll(delta, true); + } + + public static boolean onScroll(double delta, boolean needsSneaking) { + LocalPlayer player = Minecraft.getInstance().player; + // not .isCrouching! that fails for players who are not on the ground + // yes, this does work if you remap your sneak key + if (player != null && (player.isShiftKeyDown() || !needsSneaking)) { + // Spectators shouldn't interact with items! + if (player.isSpectator()) { + return false; + } + + if (IsScrollableItem(player.getMainHandItem().getItem())) { + mainHandDelta += delta; + return true; + } else if (IsScrollableItem(player.getOffhandItem().getItem())) { + offHandDelta += delta; + return true; + } + } + + return false; + } + + public static void clientTickEnd() { + if (mainHandDelta != 0 || offHandDelta != 0) { + IClientXplatAbstractions.INSTANCE.sendPacketToServer( + new MsgShiftScrollC2S( + mainHandDelta, + offHandDelta, + Minecraft.getInstance().options.keySprint.isDown(), + HexConfig.client().invertSpellbookScrollDirection(), + HexConfig.client().invertAbacusScrollDirection())); + mainHandDelta = 0; + offHandDelta = 0; + } + } + + private static boolean IsScrollableItem(Item item) { + return item == HexItems.SPELLBOOK || item == HexItems.ABACUS; + } } 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 d17e617cd9..fdb7bfc863 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,5 +1,7 @@ package at.petrak.hexcasting.client.entity; +import static at.petrak.hexcasting.api.HexAPI.modLoc; + import at.petrak.hexcasting.client.render.WorldlyPatternRenderHelpers; import at.petrak.hexcasting.common.entities.EntityWallScroll; import com.mojang.blaze3d.systems.RenderSystem; @@ -17,120 +19,149 @@ import org.joml.Matrix3f; import org.joml.Matrix4f; -import static at.petrak.hexcasting.api.HexAPI.modLoc; - public class WallScrollRenderer extends EntityRenderer { - private static final ResourceLocation PRISTINE_BG_LARGE = modLoc("textures/entity/scroll_large.png"); - private static final ResourceLocation PRISTINE_BG_MEDIUM = modLoc("textures/entity/scroll_medium.png"); - private static final ResourceLocation PRISTINE_BG_SMOL = modLoc("textures/block/scroll_paper.png"); - 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"); - - public WallScrollRenderer(EntityRendererProvider.Context p_174008_) { - super(p_174008_); - } - - // I do as the PaintingRenderer guides - @Override - public void render(EntityWallScroll wallScroll, float yaw, float partialTicks, PoseStack ps, - MultiBufferSource bufSource, int packedLight) { - - RenderSystem.setShader(GameRenderer::getPositionTexShader); - - ps.pushPose(); - - ps.mulPose(Axis.YP.rotationDegrees(180f - yaw)); - ps.mulPose(Axis.ZP.rotationDegrees(180f)); - - int light = LevelRenderer.getLightColor(wallScroll.level(), wallScroll.getPos()); - - { - ps.pushPose(); - // X is right, Y is down, Z is *in* - // Our origin will be the lower-left corner of the scroll touching the wall - // (so it has "negative" thickness) - ps.translate(-wallScroll.blockSize / 2f, -wallScroll.blockSize / 2f, 1f / 32f); - - float dx = wallScroll.blockSize, dy = wallScroll.blockSize, dz = -1f / 16f; - float margin = 1f / 48f; - var last = ps.last(); - var mat = last.pose(); - var norm = last.normal(); - - 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); - vertex(mat, norm, light, verts, 0, dy, dz, 0, 1, 0, 0, -1); - vertex(mat, norm, light, verts, dx, dy, dz, 1, 1, 0, 0, -1); - vertex(mat, norm, light, verts, dx, 0, dz, 1, 0, 0, 0, -1); - // Back face - vertex(mat, norm, light, verts, 0, 0, 0, 0, 0, 0, 0, 1); - vertex(mat, norm, light, verts, dx, 0, 0, 1, 0, 0, 0, 1); - vertex(mat, norm, light, verts, dx, dy, 0, 1, 1, 0, 0, 1); - vertex(mat, norm, light, verts, 0, dy, 0, 0, 1, 0, 0, 1); - // Top face - vertex(mat, norm, light, verts, 0, 0, 0, 0, 0, 0, -1, 0); - vertex(mat, norm, light, verts, 0, 0, dz, 0, margin, 0, -1, 0); - vertex(mat, norm, light, verts, dx, 0, dz, 1, margin, 0, -1, 0); - vertex(mat, norm, light, verts, dx, 0, 0, 1, 0, 0, -1, 0); - // Left face - vertex(mat, norm, light, verts, 0, 0, 0, 0, 0, -1, 0, 0); - vertex(mat, norm, light, verts, 0, dy, 0, 0, 1, -1, 0, 0); - vertex(mat, norm, light, verts, 0, dy, dz, margin, 1, -1, 0, 0); - vertex(mat, norm, light, verts, 0, 0, dz, margin, 0, -1, 0, 0); - // Right face - vertex(mat, norm, light, verts, dx, 0, dz, 1 - margin, 0, 1, 0, 0); - vertex(mat, norm, light, verts, dx, dy, dz, 1 - margin, 1, 1, 0, 0); - vertex(mat, norm, light, verts, dx, dy, 0, 1, 1, 1, 0, 0); - vertex(mat, norm, light, verts, dx, 0, 0, 1, 0, 1, 0, 0); - // Bottom face - vertex(mat, norm, light, verts, 0, dy, dz, 0, 1 - margin, 0, 1, 0); - vertex(mat, norm, light, verts, 0, dy, 0, 0, 1, 0, 1, 0); - vertex(mat, norm, light, verts, dx, dy, 0, 1, 1, 0, 1, 0); - vertex(mat, norm, light, verts, dx, dy, dz, 1, 1 - margin, 0, 1, 0); - - ps.popPose(); - - if(wallScroll.pattern != null) - WorldlyPatternRenderHelpers.renderPatternForScroll(wallScroll.pattern, wallScroll, ps, bufSource, light, wallScroll.blockSize, wallScroll.getShowsStrokeOrder()); - } - - ps.popPose(); - super.render(wallScroll, yaw, partialTicks, ps, bufSource, packedLight); - } - - @Override - public ResourceLocation getTextureLocation(EntityWallScroll wallScroll) { - if (wallScroll.isAncient) { - if (wallScroll.blockSize <= 1) { - return ANCIENT_BG_SMOL; - } else if (wallScroll.blockSize == 2) { - return ANCIENT_BG_MEDIUM; - } else { - return ANCIENT_BG_LARGE; - } - } else { - if (wallScroll.blockSize <= 1) { - return PRISTINE_BG_SMOL; - } else if (wallScroll.blockSize == 2) { - return PRISTINE_BG_MEDIUM; - } else { - return PRISTINE_BG_LARGE; - } - } - } - - 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 final ResourceLocation PRISTINE_BG_LARGE = + modLoc("textures/entity/scroll_large.png"); + private static final ResourceLocation PRISTINE_BG_MEDIUM = + modLoc("textures/entity/scroll_medium.png"); + private static final ResourceLocation PRISTINE_BG_SMOL = + modLoc("textures/block/scroll_paper.png"); + 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"); + + public WallScrollRenderer(EntityRendererProvider.Context p_174008_) { + super(p_174008_); + } + + // I do as the PaintingRenderer guides + @Override + public void render( + EntityWallScroll wallScroll, + float yaw, + float partialTicks, + PoseStack ps, + MultiBufferSource bufSource, + int packedLight) { + + RenderSystem.setShader(GameRenderer::getPositionTexShader); + + ps.pushPose(); + + ps.mulPose(Axis.YP.rotationDegrees(180f - yaw)); + ps.mulPose(Axis.ZP.rotationDegrees(180f)); + + int light = LevelRenderer.getLightColor(wallScroll.level(), wallScroll.getPos()); + + { + ps.pushPose(); + // X is right, Y is down, Z is *in* + // Our origin will be the lower-left corner of the scroll touching the wall + // (so it has "negative" thickness) + ps.translate(-wallScroll.blockSize / 2f, -wallScroll.blockSize / 2f, 1f / 32f); + + float dx = wallScroll.blockSize, dy = wallScroll.blockSize, dz = -1f / 16f; + float margin = 1f / 48f; + var last = ps.last(); + var mat = last.pose(); + var norm = last.normal(); + + 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); + vertex(mat, norm, light, verts, 0, dy, dz, 0, 1, 0, 0, -1); + vertex(mat, norm, light, verts, dx, dy, dz, 1, 1, 0, 0, -1); + vertex(mat, norm, light, verts, dx, 0, dz, 1, 0, 0, 0, -1); + // Back face + vertex(mat, norm, light, verts, 0, 0, 0, 0, 0, 0, 0, 1); + vertex(mat, norm, light, verts, dx, 0, 0, 1, 0, 0, 0, 1); + vertex(mat, norm, light, verts, dx, dy, 0, 1, 1, 0, 0, 1); + vertex(mat, norm, light, verts, 0, dy, 0, 0, 1, 0, 0, 1); + // Top face + vertex(mat, norm, light, verts, 0, 0, 0, 0, 0, 0, -1, 0); + vertex(mat, norm, light, verts, 0, 0, dz, 0, margin, 0, -1, 0); + vertex(mat, norm, light, verts, dx, 0, dz, 1, margin, 0, -1, 0); + vertex(mat, norm, light, verts, dx, 0, 0, 1, 0, 0, -1, 0); + // Left face + vertex(mat, norm, light, verts, 0, 0, 0, 0, 0, -1, 0, 0); + vertex(mat, norm, light, verts, 0, dy, 0, 0, 1, -1, 0, 0); + vertex(mat, norm, light, verts, 0, dy, dz, margin, 1, -1, 0, 0); + vertex(mat, norm, light, verts, 0, 0, dz, margin, 0, -1, 0, 0); + // Right face + vertex(mat, norm, light, verts, dx, 0, dz, 1 - margin, 0, 1, 0, 0); + vertex(mat, norm, light, verts, dx, dy, dz, 1 - margin, 1, 1, 0, 0); + vertex(mat, norm, light, verts, dx, dy, 0, 1, 1, 1, 0, 0); + vertex(mat, norm, light, verts, dx, 0, 0, 1, 0, 1, 0, 0); + // Bottom face + vertex(mat, norm, light, verts, 0, dy, dz, 0, 1 - margin, 0, 1, 0); + vertex(mat, norm, light, verts, 0, dy, 0, 0, 1, 0, 1, 0); + vertex(mat, norm, light, verts, dx, dy, 0, 1, 1, 0, 1, 0); + vertex(mat, norm, light, verts, dx, dy, dz, 1, 1 - margin, 0, 1, 0); + + ps.popPose(); + + if (wallScroll.pattern != null) + WorldlyPatternRenderHelpers.renderPatternForScroll( + wallScroll.pattern, + wallScroll, + ps, + bufSource, + light, + wallScroll.blockSize, + wallScroll.getShowsStrokeOrder()); + } + + ps.popPose(); + super.render(wallScroll, yaw, partialTicks, ps, bufSource, packedLight); + } + + @Override + public ResourceLocation getTextureLocation(EntityWallScroll wallScroll) { + if (wallScroll.isAncient) { + if (wallScroll.blockSize <= 1) { + return ANCIENT_BG_SMOL; + } else if (wallScroll.blockSize == 2) { + return ANCIENT_BG_MEDIUM; + } else { + return ANCIENT_BG_LARGE; + } + } else { + if (wallScroll.blockSize <= 1) { + return PRISTINE_BG_SMOL; + } else if (wallScroll.blockSize == 2) { + return PRISTINE_BG_MEDIUM; + } else { + return PRISTINE_BG_LARGE; + } + } + } + + 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(); + } } 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 0a9f66cd2b..4f10867779 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 @@ -22,6 +22,7 @@ import at.petrak.hexcasting.common.msgs.MsgNewSpellPatternC2S import at.petrak.hexcasting.xplat.IClientXplatAbstractions import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack +import kotlin.math.* import net.minecraft.client.Minecraft import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.screens.Screen @@ -34,500 +35,508 @@ import net.minecraft.util.FormattedCharSequence import net.minecraft.util.Mth import net.minecraft.world.InteractionHand import net.minecraft.world.phys.Vec2 -import kotlin.math.* // TODO winfy: fix this class to use ExecutionClientView -class GuiSpellcasting constructor( - private val handOpenedWith: InteractionHand, - private var patterns: MutableList, - private var cachedStack: List, - private var cachedRavenmind: CompoundTag?, - private var parenCount: Int, +class GuiSpellcasting +constructor( + private val handOpenedWith: InteractionHand, + private var patterns: MutableList, + private var cachedStack: List, + private var cachedRavenmind: CompoundTag?, + private var parenCount: Int, ) : Screen("gui.hexcasting.spellcasting".asTranslatedComponent) { - private var stackDescs: List = listOf() - private var parenDescs: List = listOf() - private var ravenmind: FormattedCharSequence? = null - - private var drawState: PatternDrawState = PatternDrawState.BetweenPatterns - private val usedSpots: MutableSet = HashSet() - - private var ambianceSoundInstance: GridSoundInstance? = null - - private val randSrc = SoundInstance.createUnseededRandom() - - init { - for ((pattern, origin) in patterns) { - this.usedSpots.addAll(pattern.positions(origin)) - } - this.calculateIotaDisplays() - } - - fun recvServerUpdate(info: ExecutionClientView, index: Int) { - if (info.isStackClear) { - this.minecraft?.setScreen(null) - return - } - - // TODO this is the kinda hacky bit - if (info.resolutionType == ResolvedPatternType.UNDONE) { - this.patterns.reversed().drop(1).firstOrNull { it.type == ResolvedPatternType.ESCAPED }?.let { it.type = ResolvedPatternType.UNDONE } - this.patterns.getOrNull(index)?.let { it.type = ResolvedPatternType.EVALUATED } - } else this.patterns.getOrNull(index)?.let { - it.type = info.resolutionType - } - - this.cachedStack = info.stackDescs - this.cachedRavenmind = info.ravenmind - this.calculateIotaDisplays() - } - - fun calculateIotaDisplays() { - val mc = Minecraft.getInstance() - val width = (this.width * LHS_IOTAS_ALLOCATION).toInt() - this.stackDescs = - this.cachedStack.map { IotaType.getDisplayWithMaxWidth(it, width, mc.font) } - .asReversed() -// this.parenDescs = if (this.cachedParens.isNotEmpty()) -// this.cachedParens.flatMap { HexIotaTypes.getDisplayWithMaxWidth(it, width, mc.font) } -// else if (this.parenCount > 0) -// listOf("...".gold.visualOrderText) -// else -// emptyList() - this.parenDescs = emptyList() - this.ravenmind = - this.cachedRavenmind?.let { - IotaType.getDisplayWithMaxWidth( - it, - (this.width * RHS_IOTAS_ALLOCATION).toInt(), - mc.font - ) - } - } - - override fun init() { - val minecraft = Minecraft.getInstance() - val soundManager = minecraft.soundManager - soundManager.stop(HexSounds.CASTING_AMBIANCE.location, null) - val player = minecraft.player - if (player != null) { - this.ambianceSoundInstance = GridSoundInstance(player) - soundManager.play(this.ambianceSoundInstance!!) - } - - this.calculateIotaDisplays() - } - - override fun tick() { - val minecraft = Minecraft.getInstance() - val player = minecraft.player - if (player != null) { - val heldItem = player.getItemInHand(handOpenedWith) - if (heldItem.isEmpty || !heldItem.`is`(HexTags.Items.STAVES)) - closeForReal() - } - } - - override fun mouseClicked(mxOut: Double, myOut: Double, pButton: Int): Boolean { - 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) { - val coord = this.pxToCoord(Vec2(mx.toFloat(), my.toFloat())) - if (!this.usedSpots.contains(coord)) { - this.drawState = PatternDrawState.JustStarted(coord) - Minecraft.getInstance().soundManager.play( - SimpleSoundInstance( - HexSounds.START_PATTERN, - SoundSource.PLAYERS, - 0.25f, - 1f, - randSrc, - this.ambianceSoundInstance!!.x, - this.ambianceSoundInstance!!.y, - this.ambianceSoundInstance!!.z, - ) - ) - } - } - - 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()) - - val anchorCoord = when (this.drawState) { - PatternDrawState.BetweenPatterns -> null - is PatternDrawState.JustStarted -> (this.drawState as PatternDrawState.JustStarted).start - is PatternDrawState.Drawing -> (this.drawState as PatternDrawState.Drawing).current - } - if (anchorCoord != null) { - val anchor = this.coordToPx(anchorCoord) - val mouse = Vec2(mx.toFloat(), my.toFloat()) - val snapDist = - this.hexSize() * this.hexSize() * 2.0 * Mth.clamp(HexConfig.client().gridSnapThreshold(), 0.5, 1.0) - if (anchor.distanceToSqr(mouse) >= snapDist) { - val delta = mouse.add(anchor.negated()) - val angle = atan2(delta.y, delta.x) - // 0 is right, increases clockwise(?) - val snappedAngle = angle.div(Mth.TWO_PI).mod(6.0f) - val newdir = HexDir.values()[(snappedAngle.times(6).roundToInt() + 1).mod(6)] - // The player might have a lousy aim, so set the new anchor point to the "ideal" - // location as if they had hit it exactly on the nose. - val idealNextLoc = anchorCoord + newdir - var playSound = false - if (!this.usedSpots.contains(idealNextLoc)) { - if (this.drawState is PatternDrawState.JustStarted) { - val pat = HexPattern(newdir) - - this.drawState = PatternDrawState.Drawing(anchorCoord, idealNextLoc, pat) - playSound = true - } else if (this.drawState is PatternDrawState.Drawing) { - // how anyone gets around without a borrowck is beyond me - val ds = (this.drawState as PatternDrawState.Drawing) - val lastDir = ds.wipPattern.finalDir() - if (newdir == lastDir.rotatedBy(HexAngle.BACK)) { - // We're diametrically opposite! Do a backtrack - if (ds.wipPattern.angles.isEmpty()) { - this.drawState = PatternDrawState.JustStarted(ds.current + newdir) - } else { - ds.current += newdir - ds.wipPattern.angles.removeLast() - } - playSound = true - } else { - val success = ds.wipPattern.tryAppendDir(newdir) - if (success) { - ds.current = idealNextLoc - } - playSound = success - } - } - } - - if (playSound) { - Minecraft.getInstance().soundManager.play( - SimpleSoundInstance( - HexSounds.ADD_TO_PATTERN, - SoundSource.PLAYERS, - 0.25f, - 1f + (Math.random().toFloat() - 0.5f) * 0.1f, - randSrc, - this.ambianceSoundInstance!!.x, - this.ambianceSoundInstance!!.y, - this.ambianceSoundInstance!!.z, - ) - ) - } - } - } - - return false - } - - override fun mouseReleased(mx: Double, my: Double, pButton: Int): Boolean { - 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 -> { - // Well, we never managed to get anything on the stack this go-around. - this.drawState = PatternDrawState.BetweenPatterns - } - - is PatternDrawState.Drawing -> { - val (start, _, pat) = this.drawState as PatternDrawState.Drawing - this.drawState = PatternDrawState.BetweenPatterns - this.patterns.add(ResolvedPattern(pat, start, ResolvedPatternType.UNRESOLVED)) - - this.usedSpots.addAll(pat.positions(start)) - - IClientXplatAbstractions.INSTANCE.sendPacketToServer( - MsgNewSpellPatternC2S( - this.handOpenedWith, - pat, - this.patterns - ) - ) - } - } - - return false - } - - override fun mouseScrolled(pMouseX: Double, pMouseY: Double, pDelta: Double): Boolean { - super.mouseScrolled(pMouseX, pMouseY, pDelta) - - val mouseHandler = Minecraft.getInstance().mouseHandler - - if (mouseHandler.accumulatedScroll != 0.0 && sign(pDelta) != sign(mouseHandler.accumulatedScroll)) { - mouseHandler.accumulatedScroll = 0.0 - } - - mouseHandler.accumulatedScroll += pDelta - val accumulation: Int = mouseHandler.accumulatedScroll.toInt() - if (accumulation == 0) { - return true - } - - mouseHandler.accumulatedScroll -= accumulation.toDouble() - - ShiftScrollListener.onScroll(pDelta, false) - - return true - } - - override fun onClose() { - if (drawState == PatternDrawState.BetweenPatterns) - closeForReal() - else - drawState = PatternDrawState.BetweenPatterns - } - - fun closeForReal() { - Minecraft.getInstance().soundManager.stop(HexSounds.CASTING_AMBIANCE.location, null) - - super.onClose() - } - - - override fun render(graphics: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTick: Float) { - super.render(graphics, pMouseX, pMouseY, pPartialTick) - - this.ambianceSoundInstance?.mousePosX = pMouseX / this.width.toDouble() - this.ambianceSoundInstance?.mousePosY = pMouseX / this.width.toDouble() - - val ps = graphics.pose() // TODO: Determine if this is still necessary. - - val mat = ps.last().pose() - val prevShader = RenderSystem.getShader() - RenderSystem.setShader(GameRenderer::getPositionColorShader) - RenderSystem.disableDepthTest() - RenderSystem.disableCull() - - // Draw guide dots around the cursor - val mousePos = Vec2(pMouseX.toFloat(), pMouseY.toFloat()) - // snap it to the center - val mouseCoord = this.pxToCoord(mousePos) - val radius = 3 - for (dotCoord in mouseCoord.rangeAround(radius)) { - if (!this.usedSpots.contains(dotCoord)) { - val dotPx = this.coordToPx(dotCoord) - val delta = dotPx.add(mousePos.negated()).length() - // when right on top of the cursor, 1.0 - // when at the full radius, 0! this is so we don't have dots suddenly appear/disappear. - // we subtract size from delta so there's a little "island" of 100% bright points by the mouse - val scaledDist = Mth.clamp( - 1.0f - ((delta - this.hexSize()) / (radius.toFloat() * this.hexSize())), - 0f, - 1f - ) - drawSpot( - mat, - dotPx, - scaledDist * 2f, - Mth.lerp(scaledDist, 0.4f, 0.5f), - Mth.lerp(scaledDist, 0.8f, 1.0f), - Mth.lerp(scaledDist, 0.7f, 0.9f), - scaledDist - ) - } - } - RenderSystem.defaultBlendFunc() - - for ((idx, elts) in this.patterns.withIndex()) { - val (pat, origin, valid) = elts - drawPatternFromPoints( - mat, - pat.toLines( - this.hexSize(), - this.coordToPx(origin) - ), - findDupIndices(pat.positions()), - true, - valid.color or (0xC8 shl 24), - valid.fadeColor or (0xC8 shl 24), - if (valid.success) 0.2f else 0.9f, - DEFAULT_READABILITY_OFFSET, - 1f, - idx.toDouble() - ) - } - - // Now draw the currently WIP pattern - if (this.drawState !is PatternDrawState.BetweenPatterns) { - val points = mutableListOf() - var dupIndices: Set? = null - - if (this.drawState is PatternDrawState.JustStarted) { - val ds = this.drawState as PatternDrawState.JustStarted - points.add(this.coordToPx(ds.start)) - } else if (this.drawState is PatternDrawState.Drawing) { - val ds = this.drawState as PatternDrawState.Drawing - dupIndices = findDupIndices(ds.wipPattern.positions()) - for (pos in ds.wipPattern.positions()) { - val pix = this.coordToPx(pos + ds.start) - points.add(pix) - } - } - - points.add(mousePos) - // Use the size of the patterns as the seed so that way when this one is added the zappies don't jump - drawPatternFromPoints(mat, - points, - dupIndices, - false, - 0xff_64c8ff_u.toInt(), - 0xff_fecbe6_u.toInt(), - 0.1f, - DEFAULT_READABILITY_OFFSET, - 1f, - this.patterns.size.toDouble()) - } - - RenderSystem.enableDepthTest() - - val mc = Minecraft.getInstance() - val font = mc.font - ps.pushPose() - ps.translate(10.0, 10.0, 0.0) - -// if (this.parenCount > 0) { -// val boxHeight = (this.parenDescs.size + 1f) * 10f -// RenderSystem.setShader(GameRenderer::getPositionColorShader) -// RenderSystem.defaultBlendFunc() -// drawBox(ps, 0f, 0f, (this.width * LHS_IOTAS_ALLOCATION + 5).toFloat(), boxHeight, 7.5f) -// ps.translate(0.0, 0.0, 1.0) -// -// val time = ClientTickCounter.getTotal() * 0.16f -// val opacity = (Mth.map(cos(time), -1f, 1f, 200f, 255f)).toInt() -// val color = 0x00_ffffff or (opacity shl 24) -// RenderSystem.setShader { prevShader } -// for (desc in this.parenDescs) { -// font.draw(ps, desc, 10f, 7f, color) -// ps.translate(0.0, 10.0, 0.0) -// } -// ps.translate(0.0, 15.0, 0.0) -// } - - if (this.stackDescs.isNotEmpty()) { - val boxHeight = (this.stackDescs.size + 1f) * 10f - RenderSystem.setShader(GameRenderer::getPositionColorShader) - RenderSystem.enableBlend() - drawBox(ps, 0f, 0f, (this.width * LHS_IOTAS_ALLOCATION + 5).toFloat(), boxHeight) - ps.translate(0.0, 0.0, 1.0) - RenderSystem.setShader { prevShader } - for (desc in this.stackDescs) { - graphics.drawString(font, desc, 5, 7, -1) // TODO: Confirm this works - ps.translate(0.0, 10.0, 0.0) - } - } - - ps.popPose() - if (this.ravenmind != null) { - val kotlinBad = this.ravenmind!! - ps.pushPose() - val boxHeight = 15f - val addlScale = 1.5f - ps.translate(this.width * (1.0 - RHS_IOTAS_ALLOCATION * addlScale) - 10, 10.0, 0.0) - RenderSystem.setShader(GameRenderer::getPositionColorShader) - RenderSystem.enableBlend() - drawBox( - ps, 0f, 0f, - (this.width * RHS_IOTAS_ALLOCATION * addlScale).toFloat(), boxHeight * addlScale, - ) - ps.translate(5.0, 5.0, 1.0) - ps.scale(addlScale, addlScale, 1f) - - val time = ClientTickCounter.getTotal() * 0.2f - val opacity = (Mth.map(sin(time), -1f, 1f, 150f, 255f)).toInt() - val color = 0x00_ffffff or (opacity shl 24) - - RenderSystem.setShader { prevShader } - graphics.drawString(font, kotlinBad, 0, 0, color) // TODO: Confirm this works - ps.popPose() - } - - RenderSystem.setShader { prevShader } - } - - // why the hell is this default true - override fun isPauseScreen(): Boolean = false - - /** Distance between adjacent hex centers */ - fun hexSize(): Float { - val scaleModifier = Minecraft.getInstance().player!!.getAttributeValue(HexAttributes.GRID_ZOOM) - - // Originally, we allowed 32 dots across. Assuming a 1920x1080 screen this allowed like 500-odd area. - // Let's be generous and give them 512. - val baseScale = sqrt(this.width.toDouble() * this.height / 512.0) - return (baseScale / scaleModifier).toFloat() - } - - fun coordsOffset(): Vec2 = Vec2(this.width.toFloat() * 0.5f, this.height.toFloat() * 0.5f) - - fun coordToPx(coord: HexCoord) = - at.petrak.hexcasting.api.utils.coordToPx(coord, this.hexSize(), this.coordsOffset()) - - fun pxToCoord(px: Vec2) = at.petrak.hexcasting.api.utils.pxToCoord(px, this.hexSize(), this.coordsOffset()) - - - private sealed class PatternDrawState { - /** We're waiting on the player to right-click again */ - object BetweenPatterns : PatternDrawState() - - /** We just started drawing and haven't drawn the first line yet. */ - data class JustStarted(val start: HexCoord) : PatternDrawState() - - /** We've started drawing a pattern for real. */ - data class Drawing(val start: HexCoord, var current: HexCoord, val wipPattern: HexPattern) : PatternDrawState() - } - - companion object { - const val LHS_IOTAS_ALLOCATION = 0.7 - const val RHS_IOTAS_ALLOCATION = 0.15 - - fun drawBox(ps: PoseStack, x: Float, y: Float, w: Float, h: Float, leftMargin: Float = 2.5f) { - RenderSystem.setShader(GameRenderer::getPositionColorShader) - RenderSystem.enableBlend() - renderQuad(ps, x, y, w, h, 0x50_303030) - renderQuad(ps, x + leftMargin, y + 2.5f, w - leftMargin - 2.5f, h - 5f, 0x50_303030) - } - } + private var stackDescs: List = listOf() + private var parenDescs: List = listOf() + private var ravenmind: FormattedCharSequence? = null + + private var drawState: PatternDrawState = PatternDrawState.BetweenPatterns + private val usedSpots: MutableSet = HashSet() + + private var ambianceSoundInstance: GridSoundInstance? = null + + private val randSrc = SoundInstance.createUnseededRandom() + + init { + for ((pattern, origin) in patterns) { + this.usedSpots.addAll(pattern.positions(origin)) + } + this.calculateIotaDisplays() + } + + fun recvServerUpdate(info: ExecutionClientView, index: Int) { + if (info.isStackClear) { + this.minecraft?.setScreen(null) + return + } + + // TODO this is the kinda hacky bit + if (info.resolutionType == ResolvedPatternType.UNDONE) { + this.patterns + .reversed() + .drop(1) + .firstOrNull { it.type == ResolvedPatternType.ESCAPED } + ?.let { it.type = ResolvedPatternType.UNDONE } + this.patterns.getOrNull(index)?.let { it.type = ResolvedPatternType.EVALUATED } + } else this.patterns.getOrNull(index)?.let { it.type = info.resolutionType } + + this.cachedStack = info.stackDescs + this.cachedRavenmind = info.ravenmind + this.calculateIotaDisplays() + } + + fun calculateIotaDisplays() { + val mc = Minecraft.getInstance() + val width = (this.width * LHS_IOTAS_ALLOCATION).toInt() + this.stackDescs = + this.cachedStack.map { IotaType.getDisplayWithMaxWidth(it, width, mc.font) }.asReversed() + // this.parenDescs = if (this.cachedParens.isNotEmpty()) + // this.cachedParens.flatMap { HexIotaTypes.getDisplayWithMaxWidth(it, width, + // mc.font) } + // else if (this.parenCount > 0) + // listOf("...".gold.visualOrderText) + // else + // emptyList() + this.parenDescs = emptyList() + this.ravenmind = + this.cachedRavenmind?.let { + IotaType.getDisplayWithMaxWidth(it, (this.width * RHS_IOTAS_ALLOCATION).toInt(), mc.font) + } + } + + override fun init() { + val minecraft = Minecraft.getInstance() + val soundManager = minecraft.soundManager + soundManager.stop(HexSounds.CASTING_AMBIANCE.location, null) + val player = minecraft.player + if (player != null) { + this.ambianceSoundInstance = GridSoundInstance(player) + soundManager.play(this.ambianceSoundInstance!!) + } + + this.calculateIotaDisplays() + } + + override fun tick() { + val minecraft = Minecraft.getInstance() + val player = minecraft.player + if (player != null) { + val heldItem = player.getItemInHand(handOpenedWith) + if (heldItem.isEmpty || !heldItem.`is`(HexTags.Items.STAVES)) closeForReal() + } + } + + override fun mouseClicked(mxOut: Double, myOut: Double, pButton: Int): Boolean { + 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) { + val coord = this.pxToCoord(Vec2(mx.toFloat(), my.toFloat())) + if (!this.usedSpots.contains(coord)) { + this.drawState = PatternDrawState.JustStarted(coord) + Minecraft.getInstance() + .soundManager + .play( + SimpleSoundInstance( + HexSounds.START_PATTERN, + SoundSource.PLAYERS, + 0.25f, + 1f, + randSrc, + this.ambianceSoundInstance!!.x, + this.ambianceSoundInstance!!.y, + this.ambianceSoundInstance!!.z, + ) + ) + } + } + + 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()) + + val anchorCoord = + when (this.drawState) { + PatternDrawState.BetweenPatterns -> null + is PatternDrawState.JustStarted -> (this.drawState as PatternDrawState.JustStarted).start + is PatternDrawState.Drawing -> (this.drawState as PatternDrawState.Drawing).current + } + if (anchorCoord != null) { + val anchor = this.coordToPx(anchorCoord) + val mouse = Vec2(mx.toFloat(), my.toFloat()) + val snapDist = + this.hexSize() * + this.hexSize() * + 2.0 * + Mth.clamp(HexConfig.client().gridSnapThreshold(), 0.5, 1.0) + if (anchor.distanceToSqr(mouse) >= snapDist) { + val delta = mouse.add(anchor.negated()) + val angle = atan2(delta.y, delta.x) + // 0 is right, increases clockwise(?) + val snappedAngle = angle.div(Mth.TWO_PI).mod(6.0f) + val newdir = HexDir.values()[(snappedAngle.times(6).roundToInt() + 1).mod(6)] + // The player might have a lousy aim, so set the new anchor point to the "ideal" + // location as if they had hit it exactly on the nose. + val idealNextLoc = anchorCoord + newdir + var playSound = false + if (!this.usedSpots.contains(idealNextLoc)) { + if (this.drawState is PatternDrawState.JustStarted) { + val pat = HexPattern(newdir) + + this.drawState = PatternDrawState.Drawing(anchorCoord, idealNextLoc, pat) + playSound = true + } else if (this.drawState is PatternDrawState.Drawing) { + // how anyone gets around without a borrowck is beyond me + val ds = (this.drawState as PatternDrawState.Drawing) + val lastDir = ds.wipPattern.finalDir() + if (newdir == lastDir.rotatedBy(HexAngle.BACK)) { + // We're diametrically opposite! Do a backtrack + if (ds.wipPattern.angles.isEmpty()) { + this.drawState = PatternDrawState.JustStarted(ds.current + newdir) + } else { + ds.current += newdir + ds.wipPattern.angles.removeLast() + } + playSound = true + } else { + val success = ds.wipPattern.tryAppendDir(newdir) + if (success) { + ds.current = idealNextLoc + } + playSound = success + } + } + } + + if (playSound) { + Minecraft.getInstance() + .soundManager + .play( + SimpleSoundInstance( + HexSounds.ADD_TO_PATTERN, + SoundSource.PLAYERS, + 0.25f, + 1f + (Math.random().toFloat() - 0.5f) * 0.1f, + randSrc, + this.ambianceSoundInstance!!.x, + this.ambianceSoundInstance!!.y, + this.ambianceSoundInstance!!.z, + ) + ) + } + } + } + + return false + } + + override fun mouseReleased(mx: Double, my: Double, pButton: Int): Boolean { + 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 -> { + // Well, we never managed to get anything on the stack this go-around. + this.drawState = PatternDrawState.BetweenPatterns + } + is PatternDrawState.Drawing -> { + val (start, _, pat) = this.drawState as PatternDrawState.Drawing + this.drawState = PatternDrawState.BetweenPatterns + this.patterns.add(ResolvedPattern(pat, start, ResolvedPatternType.UNRESOLVED)) + + this.usedSpots.addAll(pat.positions(start)) + + IClientXplatAbstractions.INSTANCE.sendPacketToServer( + MsgNewSpellPatternC2S(this.handOpenedWith, pat, this.patterns) + ) + } + } + + return false + } + + override fun mouseScrolled(pMouseX: Double, pMouseY: Double, pDelta: Double): Boolean { + super.mouseScrolled(pMouseX, pMouseY, pDelta) + + val mouseHandler = Minecraft.getInstance().mouseHandler + + if ( + mouseHandler.accumulatedScroll != 0.0 && sign(pDelta) != sign(mouseHandler.accumulatedScroll) + ) { + mouseHandler.accumulatedScroll = 0.0 + } + + mouseHandler.accumulatedScroll += pDelta + val accumulation: Int = mouseHandler.accumulatedScroll.toInt() + if (accumulation == 0) { + return true + } + + mouseHandler.accumulatedScroll -= accumulation.toDouble() + + ShiftScrollListener.onScroll(pDelta, false) + + return true + } + + override fun onClose() { + if (drawState == PatternDrawState.BetweenPatterns) closeForReal() + else drawState = PatternDrawState.BetweenPatterns + } + + fun closeForReal() { + Minecraft.getInstance().soundManager.stop(HexSounds.CASTING_AMBIANCE.location, null) + + super.onClose() + } + + override fun render(graphics: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTick: Float) { + super.render(graphics, pMouseX, pMouseY, pPartialTick) + + this.ambianceSoundInstance?.mousePosX = pMouseX / this.width.toDouble() + this.ambianceSoundInstance?.mousePosY = pMouseX / this.width.toDouble() + + val ps = graphics.pose() // TODO: Determine if this is still necessary. + + val mat = ps.last().pose() + val prevShader = RenderSystem.getShader() + RenderSystem.setShader(GameRenderer::getPositionColorShader) + RenderSystem.disableDepthTest() + RenderSystem.disableCull() + + // Draw guide dots around the cursor + val mousePos = Vec2(pMouseX.toFloat(), pMouseY.toFloat()) + // snap it to the center + val mouseCoord = this.pxToCoord(mousePos) + val radius = 3 + for (dotCoord in mouseCoord.rangeAround(radius)) { + if (!this.usedSpots.contains(dotCoord)) { + val dotPx = this.coordToPx(dotCoord) + val delta = dotPx.add(mousePos.negated()).length() + // when right on top of the cursor, 1.0 + // when at the full radius, 0! this is so we don't have dots suddenly appear/disappear. + // we subtract size from delta so there's a little "island" of 100% bright points by the + // mouse + val scaledDist = + Mth.clamp(1.0f - ((delta - this.hexSize()) / (radius.toFloat() * this.hexSize())), 0f, 1f) + drawSpot( + mat, + dotPx, + scaledDist * 2f, + Mth.lerp(scaledDist, 0.4f, 0.5f), + Mth.lerp(scaledDist, 0.8f, 1.0f), + Mth.lerp(scaledDist, 0.7f, 0.9f), + scaledDist + ) + } + } + RenderSystem.defaultBlendFunc() + + for ((idx, elts) in this.patterns.withIndex()) { + val (pat, origin, valid) = elts + drawPatternFromPoints( + mat, + pat.toLines(this.hexSize(), this.coordToPx(origin)), + findDupIndices(pat.positions()), + true, + valid.color or (0xC8 shl 24), + valid.fadeColor or (0xC8 shl 24), + if (valid.success) 0.2f else 0.9f, + DEFAULT_READABILITY_OFFSET, + 1f, + idx.toDouble() + ) + } + + // Now draw the currently WIP pattern + if (this.drawState !is PatternDrawState.BetweenPatterns) { + val points = mutableListOf() + var dupIndices: Set? = null + + if (this.drawState is PatternDrawState.JustStarted) { + val ds = this.drawState as PatternDrawState.JustStarted + points.add(this.coordToPx(ds.start)) + } else if (this.drawState is PatternDrawState.Drawing) { + val ds = this.drawState as PatternDrawState.Drawing + dupIndices = findDupIndices(ds.wipPattern.positions()) + for (pos in ds.wipPattern.positions()) { + val pix = this.coordToPx(pos + ds.start) + points.add(pix) + } + } + + points.add(mousePos) + // Use the size of the patterns as the seed so that way when this one is added the zappies + // don't jump + drawPatternFromPoints( + mat, + points, + dupIndices, + false, + 0xff_64c8ff_u.toInt(), + 0xff_fecbe6_u.toInt(), + 0.1f, + DEFAULT_READABILITY_OFFSET, + 1f, + this.patterns.size.toDouble() + ) + } + + RenderSystem.enableDepthTest() + + val mc = Minecraft.getInstance() + val font = mc.font + ps.pushPose() + ps.translate(10.0, 10.0, 0.0) + + // if (this.parenCount > 0) { + // val boxHeight = (this.parenDescs.size + 1f) * 10f + // RenderSystem.setShader(GameRenderer::getPositionColorShader) + // RenderSystem.defaultBlendFunc() + // drawBox(ps, 0f, 0f, (this.width * LHS_IOTAS_ALLOCATION + 5).toFloat(), boxHeight, + // 7.5f) + // ps.translate(0.0, 0.0, 1.0) + // + // val time = ClientTickCounter.getTotal() * 0.16f + // val opacity = (Mth.map(cos(time), -1f, 1f, 200f, 255f)).toInt() + // val color = 0x00_ffffff or (opacity shl 24) + // RenderSystem.setShader { prevShader } + // for (desc in this.parenDescs) { + // font.draw(ps, desc, 10f, 7f, color) + // ps.translate(0.0, 10.0, 0.0) + // } + // ps.translate(0.0, 15.0, 0.0) + // } + + if (this.stackDescs.isNotEmpty()) { + val boxHeight = (this.stackDescs.size + 1f) * 10f + RenderSystem.setShader(GameRenderer::getPositionColorShader) + RenderSystem.enableBlend() + drawBox(ps, 0f, 0f, (this.width * LHS_IOTAS_ALLOCATION + 5).toFloat(), boxHeight) + ps.translate(0.0, 0.0, 1.0) + RenderSystem.setShader { prevShader } + for (desc in this.stackDescs) { + graphics.drawString(font, desc, 5, 7, -1) // TODO: Confirm this works + ps.translate(0.0, 10.0, 0.0) + } + } + + ps.popPose() + if (this.ravenmind != null) { + val kotlinBad = this.ravenmind!! + ps.pushPose() + val boxHeight = 15f + val addlScale = 1.5f + ps.translate(this.width * (1.0 - RHS_IOTAS_ALLOCATION * addlScale) - 10, 10.0, 0.0) + RenderSystem.setShader(GameRenderer::getPositionColorShader) + RenderSystem.enableBlend() + drawBox( + ps, + 0f, + 0f, + (this.width * RHS_IOTAS_ALLOCATION * addlScale).toFloat(), + boxHeight * addlScale, + ) + ps.translate(5.0, 5.0, 1.0) + ps.scale(addlScale, addlScale, 1f) + + val time = ClientTickCounter.getTotal() * 0.2f + val opacity = (Mth.map(sin(time), -1f, 1f, 150f, 255f)).toInt() + val color = 0x00_ffffff or (opacity shl 24) + + RenderSystem.setShader { prevShader } + graphics.drawString(font, kotlinBad, 0, 0, color) // TODO: Confirm this works + ps.popPose() + } + + RenderSystem.setShader { prevShader } + } + + // why the hell is this default true + override fun isPauseScreen(): Boolean = false + + /** Distance between adjacent hex centers */ + fun hexSize(): Float { + val scaleModifier = Minecraft.getInstance().player!!.getAttributeValue(HexAttributes.GRID_ZOOM) + + // Originally, we allowed 32 dots across. Assuming a 1920x1080 screen this allowed like 500-odd + // area. + // Let's be generous and give them 512. + val baseScale = sqrt(this.width.toDouble() * this.height / 512.0) + return (baseScale / scaleModifier).toFloat() + } + + fun coordsOffset(): Vec2 = Vec2(this.width.toFloat() * 0.5f, this.height.toFloat() * 0.5f) + + fun coordToPx(coord: HexCoord) = + at.petrak.hexcasting.api.utils.coordToPx(coord, this.hexSize(), this.coordsOffset()) + + fun pxToCoord(px: Vec2) = + at.petrak.hexcasting.api.utils.pxToCoord(px, this.hexSize(), this.coordsOffset()) + + private sealed class PatternDrawState { + /** We're waiting on the player to right-click again */ + object BetweenPatterns : PatternDrawState() + + /** We just started drawing and haven't drawn the first line yet. */ + data class JustStarted(val start: HexCoord) : PatternDrawState() + + /** We've started drawing a pattern for real. */ + data class Drawing(val start: HexCoord, var current: HexCoord, val wipPattern: HexPattern) : + PatternDrawState() + } + + companion object { + const val LHS_IOTAS_ALLOCATION = 0.7 + const val RHS_IOTAS_ALLOCATION = 0.15 + + fun drawBox(ps: PoseStack, x: Float, y: Float, w: Float, h: Float, leftMargin: Float = 2.5f) { + RenderSystem.setShader(GameRenderer::getPositionColorShader) + RenderSystem.enableBlend() + renderQuad(ps, x, y, w, h, 0x50_303030) + renderQuad(ps, x + leftMargin, y + 2.5f, w - leftMargin - 2.5f, h - 5f, 0x50_303030) + } + } } 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 c6f7da11d8..1d0f34d03a 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,5 +1,7 @@ package at.petrak.hexcasting.client.gui; +import static at.petrak.hexcasting.api.HexAPI.modLoc; + import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.client.render.PatternColors; import at.petrak.hexcasting.client.render.PatternRenderer; @@ -13,8 +15,6 @@ import net.minecraft.world.inventory.tooltip.TooltipComponent; import org.jetbrains.annotations.Nullable; -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 // yoink @@ -22,68 +22,79 @@ * @see PatternTooltip the associated data for this */ public class PatternTooltipComponent implements ClientTooltipComponent { - public static final ResourceLocation PRISTINE_BG = modLoc("textures/gui/scroll.png"); - public static final ResourceLocation ANCIENT_BG = modLoc("textures/gui/scroll_ancient.png"); - public static final ResourceLocation SLATE_BG = modLoc("textures/gui/slate.png"); - - private static final float RENDER_SIZE = 128f; - private static final int TEXTURE_SIZE = 48; - - private final HexPattern pattern; - private final ResourceLocation background; - - public PatternTooltipComponent(PatternTooltip tt) { - this.pattern = tt.pattern(); - this.background = tt.background(); - } - - @Nullable - public static ClientTooltipComponent tryConvert(TooltipComponent cmp) { - if (cmp instanceof PatternTooltip ptt) { - return new PatternTooltipComponent(ptt); - } - return null; - } - - @Override - public void renderImage(Font font, int mouseX, int mouseY, GuiGraphics graphics) { - var ps = graphics.pose(); - - // far as i can tell "mouseX" and "mouseY" are actually the positions of the corner of the tooltip - ps.pushPose(); - ps.translate(mouseX, mouseY, 500); - RenderSystem.enableBlend(); - renderBG(graphics, this.background); - - // renderText happens *before* renderImage for some asinine reason - ps.translate(0, 0, 100); - ps.scale(RENDER_SIZE, RENDER_SIZE, 1); - - 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(); - } - - private static void renderBG(GuiGraphics graphics, ResourceLocation background) { - graphics.blit( - background, // texture - 0, 0, // x, y - (int) RENDER_SIZE, (int) RENDER_SIZE, // renderWidth, renderHeight - 0f, 0f, // u, v (textureCoords) - TEXTURE_SIZE, TEXTURE_SIZE, // regionWidth, regionHeight (texture sample dimensions) - TEXTURE_SIZE, TEXTURE_SIZE); // textureWidth, textureHeight (total dimensions of texture) - } - - @Override - public int getWidth(Font pFont) { - return (int) RENDER_SIZE; - } - - @Override - public int getHeight() { - return (int) RENDER_SIZE; - } + public static final ResourceLocation PRISTINE_BG = modLoc("textures/gui/scroll.png"); + public static final ResourceLocation ANCIENT_BG = modLoc("textures/gui/scroll_ancient.png"); + public static final ResourceLocation SLATE_BG = modLoc("textures/gui/slate.png"); + + private static final float RENDER_SIZE = 128f; + private static final int TEXTURE_SIZE = 48; + + private final HexPattern pattern; + private final ResourceLocation background; + + public PatternTooltipComponent(PatternTooltip tt) { + this.pattern = tt.pattern(); + this.background = tt.background(); + } + + @Nullable public static ClientTooltipComponent tryConvert(TooltipComponent cmp) { + if (cmp instanceof PatternTooltip ptt) { + return new PatternTooltipComponent(ptt); + } + return null; + } + + @Override + public void renderImage(Font font, int mouseX, int mouseY, GuiGraphics graphics) { + var ps = graphics.pose(); + + // far as i can tell "mouseX" and "mouseY" are actually the positions of the corner of the + // tooltip + ps.pushPose(); + ps.translate(mouseX, mouseY, 500); + RenderSystem.enableBlend(); + renderBG(graphics, this.background); + + // renderText happens *before* renderImage for some asinine reason + ps.translate(0, 0, 100); + ps.scale(RENDER_SIZE, RENDER_SIZE, 1); + + 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(); + } + + private static void renderBG(GuiGraphics graphics, ResourceLocation background) { + graphics.blit( + background, // texture + 0, + 0, // x, y + (int) RENDER_SIZE, + (int) RENDER_SIZE, // renderWidth, renderHeight + 0f, + 0f, // u, v (textureCoords) + TEXTURE_SIZE, + TEXTURE_SIZE, // regionWidth, regionHeight (texture sample dimensions) + TEXTURE_SIZE, + TEXTURE_SIZE); // textureWidth, textureHeight (total dimensions of texture) + } + + @Override + public int getWidth(Font pFont) { + return (int) RENDER_SIZE; + } + + @Override + public int getHeight() { + return (int) RENDER_SIZE; + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/ktxt/ClientAccessorWrappers.kt b/Common/src/main/java/at/petrak/hexcasting/client/ktxt/ClientAccessorWrappers.kt index 5863d7f050..255a80b361 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/ktxt/ClientAccessorWrappers.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/ktxt/ClientAccessorWrappers.kt @@ -1,9 +1,10 @@ @file:JvmName("ClientAccessorWrappers") + package at.petrak.hexcasting.client.ktxt import at.petrak.hexcasting.mixin.accessor.client.AccessorMouseHandler import net.minecraft.client.MouseHandler var MouseHandler.accumulatedScroll: Double - get() = (this as AccessorMouseHandler).`hex$getAccumulatedScroll`() - set(value) = (this as AccessorMouseHandler).`hex$setAccumulatedScroll`(value) + get() = (this as AccessorMouseHandler).`hex$getAccumulatedScroll`() + set(value) = (this as AccessorMouseHandler).`hex$setAccumulatedScroll`(value) diff --git a/Common/src/main/java/at/petrak/hexcasting/client/model/AltioraLayer.java b/Common/src/main/java/at/petrak/hexcasting/client/model/AltioraLayer.java index a354d6eba3..dca5cf3183 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/model/AltioraLayer.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/model/AltioraLayer.java @@ -1,5 +1,7 @@ package at.petrak.hexcasting.client.model; +import static at.petrak.hexcasting.api.HexAPI.modLoc; + import at.petrak.hexcasting.xplat.IXplatAbstractions; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; @@ -17,36 +19,46 @@ import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.item.Items; -import static at.petrak.hexcasting.api.HexAPI.modLoc; +public class AltioraLayer> + extends RenderLayer { + private static final ResourceLocation TEX_LOC = modLoc("textures/misc/altiora.png"); + + private final ElytraModel elytraModel; + + public AltioraLayer(RenderLayerParent renderer, EntityModelSet ems) { + super(renderer); + this.elytraModel = new ElytraModel<>(ems.bakeLayer(HexModelLayers.ALTIORA)); + } + + @Override + public void render( + PoseStack ps, + MultiBufferSource buffer, + int packedLight, + AbstractClientPlayer player, + float limbSwing, + float limbSwingAmount, + float partialTick, + float ageInTicks, + float netHeadYaw, + float headPitch) { + var altiora = IXplatAbstractions.INSTANCE.getAltiora(player); + // do a best effort to not render over other elytra, although we can never patch up everything + var chestSlot = player.getItemBySlot(EquipmentSlot.CHEST); + if (altiora != null && !chestSlot.is(Items.ELYTRA)) { + ps.pushPose(); + ps.translate(0.0, 0.0, 0.125); + + this.getParentModel().copyPropertiesTo(this.elytraModel); + this.elytraModel.setupAnim( + player, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch); + VertexConsumer verts = + ItemRenderer.getArmorFoilBuffer( + buffer, RenderType.armorCutoutNoCull(TEX_LOC), false, true); + this.elytraModel.renderToBuffer( + ps, verts, packedLight, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F); -public class AltioraLayer> extends RenderLayer { - private static final ResourceLocation TEX_LOC = modLoc("textures/misc/altiora.png"); - - private final ElytraModel elytraModel; - - public AltioraLayer(RenderLayerParent renderer, EntityModelSet ems) { - super(renderer); - this.elytraModel = new ElytraModel<>(ems.bakeLayer(HexModelLayers.ALTIORA)); - } - - @Override - public void render(PoseStack ps, MultiBufferSource buffer, int packedLight, AbstractClientPlayer player, - float limbSwing, float limbSwingAmount, float partialTick, float ageInTicks, float netHeadYaw, - float headPitch) { - var altiora = IXplatAbstractions.INSTANCE.getAltiora(player); - // do a best effort to not render over other elytra, although we can never patch up everything - var chestSlot = player.getItemBySlot(EquipmentSlot.CHEST); - if (altiora != null && !chestSlot.is(Items.ELYTRA)) { - ps.pushPose(); - ps.translate(0.0, 0.0, 0.125); - - this.getParentModel().copyPropertiesTo(this.elytraModel); - this.elytraModel.setupAnim(player, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch); - VertexConsumer verts = ItemRenderer.getArmorFoilBuffer( - buffer, RenderType.armorCutoutNoCull(TEX_LOC), false, true); - this.elytraModel.renderToBuffer(ps, verts, packedLight, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F); - - ps.popPose(); - } - } + ps.popPose(); + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/model/HexModelLayers.java b/Common/src/main/java/at/petrak/hexcasting/client/model/HexModelLayers.java index 11dc412327..7f14ff97ca 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/model/HexModelLayers.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/model/HexModelLayers.java @@ -1,34 +1,33 @@ package at.petrak.hexcasting.client.model; -import net.minecraft.client.model.ElytraModel; -import net.minecraft.client.model.geom.ModelLayerLocation; -import net.minecraft.client.model.geom.builders.LayerDefinition; +import static at.petrak.hexcasting.api.HexAPI.modLoc; import java.util.function.BiConsumer; import java.util.function.Supplier; - -import static at.petrak.hexcasting.api.HexAPI.modLoc; +import net.minecraft.client.model.ElytraModel; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.builders.LayerDefinition; // https://github.com/VazkiiMods/Botania/blob/1.19.x/Xplat/src/main/java/vazkii/botania/client/model/BotaniaModelLayers.java public class HexModelLayers { - public static final ModelLayerLocation ALTIORA = make("altiora"); - - public static final ModelLayerLocation ROBES = make("robes"); - - private static ModelLayerLocation make(String name) { - return make(name, "main"); - } - - private static ModelLayerLocation make(String name, String layer) { - // Don't add to vanilla's ModelLayers. It seems to only be used for error checking - // And would be annoying to do under Forge's parallel mod loading - return new ModelLayerLocation(modLoc(name), layer); - } - - // moving this stuff into the same file: - // https://github.com/VazkiiMods/Botania/blob/1.19.x/Xplat/src/main/java/vazkii/botania/client/model/BotaniaLayerDefinitions.java - public static void init(BiConsumer> consumer) { - consumer.accept(ALTIORA, ElytraModel::createLayer); - consumer.accept(ROBES, HexRobesModels::variant1); - } + public static final ModelLayerLocation ALTIORA = make("altiora"); + + public static final ModelLayerLocation ROBES = make("robes"); + + private static ModelLayerLocation make(String name) { + return make(name, "main"); + } + + private static ModelLayerLocation make(String name, String layer) { + // Don't add to vanilla's ModelLayers. It seems to only be used for error checking + // And would be annoying to do under Forge's parallel mod loading + return new ModelLayerLocation(modLoc(name), layer); + } + + // moving this stuff into the same file: + // https://github.com/VazkiiMods/Botania/blob/1.19.x/Xplat/src/main/java/vazkii/botania/client/model/BotaniaLayerDefinitions.java + public static void init(BiConsumer> consumer) { + consumer.accept(ALTIORA, ElytraModel::createLayer); + consumer.accept(ROBES, HexRobesModels::variant1); + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/model/HexRobesModels.java b/Common/src/main/java/at/petrak/hexcasting/client/model/HexRobesModels.java index 7f35494cab..859ee1e366 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/model/HexRobesModels.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/model/HexRobesModels.java @@ -4,67 +4,99 @@ // Exported for Minecraft version 1.17 or later with Mojang mappings // Paste this class into your mod and generate all required imports +import static at.petrak.hexcasting.api.HexAPI.modLoc; + import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.PartPose; import net.minecraft.client.model.geom.builders.*; -import static at.petrak.hexcasting.api.HexAPI.modLoc; - public class HexRobesModels { - // This layer location should be baked with EntityRendererProvider.Context in the entity renderer and passed into - // this model's constructor - public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(modLoc("robes"), "main"); + // This layer location should be baked with EntityRendererProvider.Context in the entity renderer + // and passed into + // this model's constructor + public static final ModelLayerLocation LAYER_LOCATION = + new ModelLayerLocation(modLoc("robes"), "main"); - public static LayerDefinition variant1() { - MeshDefinition meshdefinition = new MeshDefinition(); - PartDefinition partdefinition = meshdefinition.getRoot(); + public static LayerDefinition variant1() { + MeshDefinition meshdefinition = new MeshDefinition(); + PartDefinition partdefinition = meshdefinition.getRoot(); - PartDefinition Hood = partdefinition.addOrReplaceChild("Hood", - CubeListBuilder.create() - .texOffs(0, 0).addBox(-8.0F, 0.0F, 0.0F, 8.0F, 8.0F, 8.0F, new CubeDeformation(0.0F)) - .texOffs(0, 16).addBox(-8.0F, 0.0F, 0.0F, 8.0F, 8.0F, 8.0F, new CubeDeformation(0.0F)), - PartPose.offset(4.0F, -8.0F, -4.0F)); + PartDefinition Hood = + partdefinition.addOrReplaceChild( + "Hood", + CubeListBuilder.create() + .texOffs(0, 0) + .addBox(-8.0F, 0.0F, 0.0F, 8.0F, 8.0F, 8.0F, new CubeDeformation(0.0F)) + .texOffs(0, 16) + .addBox(-8.0F, 0.0F, 0.0F, 8.0F, 8.0F, 8.0F, new CubeDeformation(0.0F)), + PartPose.offset(4.0F, -8.0F, -4.0F)); - PartDefinition Horns = partdefinition.addOrReplaceChild("Horns", - CubeListBuilder.create() - .texOffs(24, 0).addBox(-8.0F, 0.0F, 0.0F, 8.0F, 4.0F, 0.0F, new CubeDeformation(0.0F)) - .texOffs(24, 0).mirror().addBox(9.5F, 0.0F, 0.0F, 8.0F, 4.0F, 0.0F, new CubeDeformation(0.0F)) - .mirror(false), - PartPose.offset(-4.8F, -8.2F, 0.0F)); + PartDefinition Horns = + partdefinition.addOrReplaceChild( + "Horns", + CubeListBuilder.create() + .texOffs(24, 0) + .addBox(-8.0F, 0.0F, 0.0F, 8.0F, 4.0F, 0.0F, new CubeDeformation(0.0F)) + .texOffs(24, 0) + .mirror() + .addBox(9.5F, 0.0F, 0.0F, 8.0F, 4.0F, 0.0F, new CubeDeformation(0.0F)) + .mirror(false), + PartPose.offset(-4.8F, -8.2F, 0.0F)); - PartDefinition Torso = partdefinition.addOrReplaceChild("Torso", - CubeListBuilder.create() - .texOffs(40, 0).addBox(-8.0F, 0.0F, 0.0F, 8.0F, 12.0F, 4.0F, new CubeDeformation(0.0F)) - .texOffs(40, 16).addBox(-8.0F, 0.0F, 0.0F, 8.0F, 12.0F, 4.0F, new CubeDeformation(0.0F)), - PartPose.offset(4.0F, 0.0F, -2.0F)); + PartDefinition Torso = + partdefinition.addOrReplaceChild( + "Torso", + CubeListBuilder.create() + .texOffs(40, 0) + .addBox(-8.0F, 0.0F, 0.0F, 8.0F, 12.0F, 4.0F, new CubeDeformation(0.0F)) + .texOffs(40, 16) + .addBox(-8.0F, 0.0F, 0.0F, 8.0F, 12.0F, 4.0F, new CubeDeformation(0.0F)), + PartPose.offset(4.0F, 0.0F, -2.0F)); - PartDefinition Arms = partdefinition.addOrReplaceChild("Arms", - CubeListBuilder.create() - .texOffs(0, 32).addBox(-4.0F, 0.0F, 0.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.0F)) - .texOffs(0, 32).mirror().addBox(8.0F, 0.0F, 0.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.0F)) - .mirror(false), - PartPose.offset(-4.0F, 0.0F, -2.0F)); + PartDefinition Arms = + partdefinition.addOrReplaceChild( + "Arms", + CubeListBuilder.create() + .texOffs(0, 32) + .addBox(-4.0F, 0.0F, 0.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.0F)) + .texOffs(0, 32) + .mirror() + .addBox(8.0F, 0.0F, 0.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.0F)) + .mirror(false), + PartPose.offset(-4.0F, 0.0F, -2.0F)); - PartDefinition Skirt = partdefinition.addOrReplaceChild("Skirt", CubeListBuilder.create(), - PartPose.offset(0.0F, 12.0F, -2.0F)); + PartDefinition Skirt = + partdefinition.addOrReplaceChild( + "Skirt", CubeListBuilder.create(), PartPose.offset(0.0F, 12.0F, -2.0F)); - PartDefinition Left_r1 = Skirt.addOrReplaceChild("Left_r1", - CubeListBuilder.create() - .texOffs(48, 32).mirror().addBox(0.1F, 0.0F, -4.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.0F)) - .mirror(false), - PartPose.offsetAndRotation(0.0F, 0.0F, 4.0F, 0.0F, 0.0F, -0.1309F)); + PartDefinition Left_r1 = + Skirt.addOrReplaceChild( + "Left_r1", + CubeListBuilder.create() + .texOffs(48, 32) + .mirror() + .addBox(0.1F, 0.0F, -4.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.0F)) + .mirror(false), + PartPose.offsetAndRotation(0.0F, 0.0F, 4.0F, 0.0F, 0.0F, -0.1309F)); - PartDefinition Right_r1 = Skirt.addOrReplaceChild("Right_r1", - CubeListBuilder.create() - .texOffs(48, 32).addBox(-4.0F, 0.0F, -4.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.0F)), - PartPose.offsetAndRotation(0.0F, 0.0F, 4.0F, 0.0F, 0.0F, 0.1309F)); + PartDefinition Right_r1 = + Skirt.addOrReplaceChild( + "Right_r1", + CubeListBuilder.create() + .texOffs(48, 32) + .addBox(-4.0F, 0.0F, -4.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.0F)), + PartPose.offsetAndRotation(0.0F, 0.0F, 4.0F, 0.0F, 0.0F, 0.1309F)); - PartDefinition Legs = partdefinition.addOrReplaceChild("Legs", - CubeListBuilder.create().texOffs(16, 41) - .addBox(-4.0F, 0.0F, 0.0F, 4.0F, 3.0F, 4.0F, new CubeDeformation(0.0F)) - .texOffs(16, 41).addBox(0.0F, 0.0F, 0.0F, 4.0F, 3.0F, 4.0F, new CubeDeformation(0.0F)), - PartPose.offset(0.0F, 21.0F, -2.0F)); + PartDefinition Legs = + partdefinition.addOrReplaceChild( + "Legs", + CubeListBuilder.create() + .texOffs(16, 41) + .addBox(-4.0F, 0.0F, 0.0F, 4.0F, 3.0F, 4.0F, new CubeDeformation(0.0F)) + .texOffs(16, 41) + .addBox(0.0F, 0.0F, 0.0F, 4.0F, 3.0F, 4.0F, new CubeDeformation(0.0F)), + PartPose.offset(0.0F, 21.0F, -2.0F)); - return LayerDefinition.create(meshdefinition, 64, 64); - } -} \ No newline at end of file + return LayerDefinition.create(meshdefinition, 64, 64); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/client/model/MyOwnArmorModelWithBlackjackAndHookers.java b/Common/src/main/java/at/petrak/hexcasting/client/model/MyOwnArmorModelWithBlackjackAndHookers.java index fc63fcdf72..5c69725b76 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/model/MyOwnArmorModelWithBlackjackAndHookers.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/model/MyOwnArmorModelWithBlackjackAndHookers.java @@ -10,76 +10,88 @@ // https://github.com/VazkiiMods/Botania/blob/1.19.x/Xplat/src/main/java/vazkii/botania/client/model/armor/ArmorModel.java public class MyOwnArmorModelWithBlackjackAndHookers extends HumanoidModel { - protected final EquipmentSlot slot; + protected final EquipmentSlot slot; - public MyOwnArmorModelWithBlackjackAndHookers(ModelPart root, EquipmentSlot slot) { - super(root); - this.slot = slot; - } + public MyOwnArmorModelWithBlackjackAndHookers(ModelPart root, EquipmentSlot slot) { + super(root); + this.slot = slot; + } - // [VanillaCopy] ArmorStandArmorModel.setupAnim because armor stands are dumb - // This fixes the armor "breathing" and helmets always facing south on armor stands - @Override - public void setupAnim(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, - float netHeadYaw, float headPitch) { - if (!(entity instanceof ArmorStand entityIn)) { - super.setupAnim(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch); - return; - } + // [VanillaCopy] ArmorStandArmorModel.setupAnim because armor stands are dumb + // This fixes the armor "breathing" and helmets always facing south on armor stands + @Override + public void setupAnim( + LivingEntity entity, + float limbSwing, + float limbSwingAmount, + float ageInTicks, + float netHeadYaw, + float headPitch) { + if (!(entity instanceof ArmorStand entityIn)) { + super.setupAnim(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch); + return; + } - this.head.xRot = ((float) Math.PI / 180F) * entityIn.getHeadPose().getX(); - this.head.yRot = ((float) Math.PI / 180F) * entityIn.getHeadPose().getY(); - this.head.zRot = ((float) Math.PI / 180F) * entityIn.getHeadPose().getZ(); - this.head.setPos(0.0F, 1.0F, 0.0F); - this.body.xRot = ((float) Math.PI / 180F) * entityIn.getBodyPose().getX(); - this.body.yRot = ((float) Math.PI / 180F) * entityIn.getBodyPose().getY(); - this.body.zRot = ((float) Math.PI / 180F) * entityIn.getBodyPose().getZ(); - this.leftArm.xRot = ((float) Math.PI / 180F) * entityIn.getLeftArmPose().getX(); - this.leftArm.yRot = ((float) Math.PI / 180F) * entityIn.getLeftArmPose().getY(); - this.leftArm.zRot = ((float) Math.PI / 180F) * entityIn.getLeftArmPose().getZ(); - this.rightArm.xRot = ((float) Math.PI / 180F) * entityIn.getRightArmPose().getX(); - this.rightArm.yRot = ((float) Math.PI / 180F) * entityIn.getRightArmPose().getY(); - this.rightArm.zRot = ((float) Math.PI / 180F) * entityIn.getRightArmPose().getZ(); - this.leftLeg.xRot = ((float) Math.PI / 180F) * entityIn.getLeftLegPose().getX(); - this.leftLeg.yRot = ((float) Math.PI / 180F) * entityIn.getLeftLegPose().getY(); - this.leftLeg.zRot = ((float) Math.PI / 180F) * entityIn.getLeftLegPose().getZ(); - this.leftLeg.setPos(1.9F, 11.0F, 0.0F); - this.rightLeg.xRot = ((float) Math.PI / 180F) * entityIn.getRightLegPose().getX(); - this.rightLeg.yRot = ((float) Math.PI / 180F) * entityIn.getRightLegPose().getY(); - this.rightLeg.zRot = ((float) Math.PI / 180F) * entityIn.getRightLegPose().getZ(); - this.rightLeg.setPos(-1.9F, 11.0F, 0.0F); - this.hat.copyFrom(this.head); - } + this.head.xRot = ((float) Math.PI / 180F) * entityIn.getHeadPose().getX(); + this.head.yRot = ((float) Math.PI / 180F) * entityIn.getHeadPose().getY(); + this.head.zRot = ((float) Math.PI / 180F) * entityIn.getHeadPose().getZ(); + this.head.setPos(0.0F, 1.0F, 0.0F); + this.body.xRot = ((float) Math.PI / 180F) * entityIn.getBodyPose().getX(); + this.body.yRot = ((float) Math.PI / 180F) * entityIn.getBodyPose().getY(); + this.body.zRot = ((float) Math.PI / 180F) * entityIn.getBodyPose().getZ(); + this.leftArm.xRot = ((float) Math.PI / 180F) * entityIn.getLeftArmPose().getX(); + this.leftArm.yRot = ((float) Math.PI / 180F) * entityIn.getLeftArmPose().getY(); + this.leftArm.zRot = ((float) Math.PI / 180F) * entityIn.getLeftArmPose().getZ(); + this.rightArm.xRot = ((float) Math.PI / 180F) * entityIn.getRightArmPose().getX(); + this.rightArm.yRot = ((float) Math.PI / 180F) * entityIn.getRightArmPose().getY(); + this.rightArm.zRot = ((float) Math.PI / 180F) * entityIn.getRightArmPose().getZ(); + this.leftLeg.xRot = ((float) Math.PI / 180F) * entityIn.getLeftLegPose().getX(); + this.leftLeg.yRot = ((float) Math.PI / 180F) * entityIn.getLeftLegPose().getY(); + this.leftLeg.zRot = ((float) Math.PI / 180F) * entityIn.getLeftLegPose().getZ(); + this.leftLeg.setPos(1.9F, 11.0F, 0.0F); + this.rightLeg.xRot = ((float) Math.PI / 180F) * entityIn.getRightLegPose().getX(); + this.rightLeg.yRot = ((float) Math.PI / 180F) * entityIn.getRightLegPose().getY(); + this.rightLeg.zRot = ((float) Math.PI / 180F) * entityIn.getRightLegPose().getZ(); + this.rightLeg.setPos(-1.9F, 11.0F, 0.0F); + this.hat.copyFrom(this.head); + } - @Override - public void renderToBuffer(PoseStack ms, VertexConsumer buffer, int light, int overlay, float r, float g, float b - , float a) { - setPartVisibility(slot); - super.renderToBuffer(ms, buffer, light, overlay, r, g, b, a); - } + @Override + public void renderToBuffer( + PoseStack ms, + VertexConsumer buffer, + int light, + int overlay, + float r, + float g, + float b, + float a) { + setPartVisibility(slot); + super.renderToBuffer(ms, buffer, light, overlay, r, g, b, a); + } - // [VanillaCopy] HumanoidArmorLayer - private void setPartVisibility(EquipmentSlot slot) { - setAllVisible(false); - switch (slot) { - case HEAD -> { - head.visible = true; - hat.visible = true; - } - case CHEST -> { - body.visible = true; - rightArm.visible = true; - leftArm.visible = true; - } - case LEGS -> { - body.visible = true; - rightLeg.visible = true; - leftLeg.visible = true; - } - case FEET -> { - rightLeg.visible = true; - leftLeg.visible = true; - } - } - } + // [VanillaCopy] HumanoidArmorLayer + private void setPartVisibility(EquipmentSlot slot) { + setAllVisible(false); + switch (slot) { + case HEAD -> { + head.visible = true; + hat.visible = true; + } + case CHEST -> { + body.visible = true; + rightArm.visible = true; + leftArm.visible = true; + } + case LEGS -> { + body.visible = true; + rightLeg.visible = true; + leftLeg.visible = true; + } + case FEET -> { + rightLeg.visible = true; + leftLeg.visible = true; + } + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/particles/ConjureParticle.java b/Common/src/main/java/at/petrak/hexcasting/client/particles/ConjureParticle.java index 122e536e75..289fd18f9f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/particles/ConjureParticle.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/particles/ConjureParticle.java @@ -9,6 +9,7 @@ import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.Tesselator; import com.mojang.blaze3d.vertex.VertexFormat; +import java.util.Random; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.particle.*; @@ -18,107 +19,118 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Random; - public class ConjureParticle extends TextureSheetParticle { - private static final Random RANDOM = new Random(); - - private final SpriteSet sprites; - - ConjureParticle(ClientLevel pLevel, double x, double y, double z, double dx, double dy, double dz, - SpriteSet pSprites, int color) { - super(pLevel, x, y, z, dx, dy, dz); - this.quadSize *= 0.9f; - this.setParticleSpeed(dx, dy, dz); - - var r = FastColor.ARGB32.red(color); - var g = FastColor.ARGB32.green(color); - var b = FastColor.ARGB32.blue(color); - this.setColor(r / 255f, g / 255f, b / 255f); - this.setAlpha(0.3f); - - this.friction = 0.96F; - this.gravity = dy != 0 && dx != 0 && dz != 0 ? -0.01F : 0F; - this.speedUpWhenYMotionIsBlocked = true; - this.sprites = pSprites; - - this.roll = RANDOM.nextFloat(360); - this.oRoll = this.roll; - - this.lifetime = (int) (64.0 / ((Math.random() + 3f) * 0.25f)); - this.hasPhysics = false; - this.setSpriteFromAge(pSprites); - } - - public @NotNull ParticleRenderType getRenderType() { - return CONJURE_RENDER_TYPE; - } - - public void tick() { - super.tick(); - this.setSpriteFromAge(this.sprites); - this.alpha = 1.0f - ((float) this.age / (float) this.lifetime); - this.alpha *= 0.3f; - this.quadSize *= 0.96f; - } - - public void setSpriteFromAge(@NotNull SpriteSet pSprite) { - if (!this.removed) { - int age = this.age * 4; - if (age > this.lifetime) { - age /= 4; - } - this.setSprite(pSprite.get(age, this.lifetime)); - } - } - - public static class Provider implements ParticleProvider { - private final SpriteSet sprite; - - public Provider(SpriteSet pSprites) { - this.sprite = pSprites; - } - - @Nullable - @Override - public Particle createParticle(ConjureParticleOptions type, ClientLevel level, - double pX, double pY, double pZ, - double pXSpeed, double pYSpeed, double pZSpeed) { - return new ConjureParticle(level, pX, pY, pZ, pXSpeed, pYSpeed, pZSpeed, this.sprite, type.color()); - } - } - - // https://github.com/VazkiiMods/Botania/blob/db85d778ab23f44c11181209319066d1f04a9e3d/Xplat/src/main/java/vazkii/botania/client/fx/FXWisp.java - private record ConjureRenderType() implements ParticleRenderType { - @Override - public void begin(BufferBuilder buf, TextureManager texMan) { - Minecraft.getInstance().gameRenderer.lightTexture().turnOnLightLayer(); - RenderSystem.depthMask(false); - RenderSystem.enableBlend(); - RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE); - - RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_PARTICLES); - var tex = texMan.getTexture(TextureAtlas.LOCATION_PARTICLES); - IClientXplatAbstractions.INSTANCE.setFilterSave(tex, true, false); - buf.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.PARTICLE); - RenderSystem.enableDepthTest(); - } - - @Override - public void end(Tesselator tess) { - tess.end(); - IClientXplatAbstractions.INSTANCE.restoreLastFilter( - Minecraft.getInstance().getTextureManager().getTexture(TextureAtlas.LOCATION_PARTICLES) - ); - RenderSystem.disableBlend(); - RenderSystem.depthMask(true); - } - - @Override - public String toString() { - return HexAPI.MOD_ID + ":conjure"; - } - } - - public static final ConjureRenderType CONJURE_RENDER_TYPE = new ConjureRenderType(); + private static final Random RANDOM = new Random(); + + private final SpriteSet sprites; + + ConjureParticle( + ClientLevel pLevel, + double x, + double y, + double z, + double dx, + double dy, + double dz, + SpriteSet pSprites, + int color) { + super(pLevel, x, y, z, dx, dy, dz); + this.quadSize *= 0.9f; + this.setParticleSpeed(dx, dy, dz); + + var r = FastColor.ARGB32.red(color); + var g = FastColor.ARGB32.green(color); + var b = FastColor.ARGB32.blue(color); + this.setColor(r / 255f, g / 255f, b / 255f); + this.setAlpha(0.3f); + + this.friction = 0.96F; + this.gravity = dy != 0 && dx != 0 && dz != 0 ? -0.01F : 0F; + this.speedUpWhenYMotionIsBlocked = true; + this.sprites = pSprites; + + this.roll = RANDOM.nextFloat(360); + this.oRoll = this.roll; + + this.lifetime = (int) (64.0 / ((Math.random() + 3f) * 0.25f)); + this.hasPhysics = false; + this.setSpriteFromAge(pSprites); + } + + public @NotNull ParticleRenderType getRenderType() { + return CONJURE_RENDER_TYPE; + } + + public void tick() { + super.tick(); + this.setSpriteFromAge(this.sprites); + this.alpha = 1.0f - ((float) this.age / (float) this.lifetime); + this.alpha *= 0.3f; + this.quadSize *= 0.96f; + } + + public void setSpriteFromAge(@NotNull SpriteSet pSprite) { + if (!this.removed) { + int age = this.age * 4; + if (age > this.lifetime) { + age /= 4; + } + this.setSprite(pSprite.get(age, this.lifetime)); + } + } + + public static class Provider implements ParticleProvider { + private final SpriteSet sprite; + + public Provider(SpriteSet pSprites) { + this.sprite = pSprites; + } + + @Nullable @Override + public Particle createParticle( + ConjureParticleOptions type, + ClientLevel level, + double pX, + double pY, + double pZ, + double pXSpeed, + double pYSpeed, + double pZSpeed) { + return new ConjureParticle( + level, pX, pY, pZ, pXSpeed, pYSpeed, pZSpeed, this.sprite, type.color()); + } + } + + // https://github.com/VazkiiMods/Botania/blob/db85d778ab23f44c11181209319066d1f04a9e3d/Xplat/src/main/java/vazkii/botania/client/fx/FXWisp.java + private record ConjureRenderType() implements ParticleRenderType { + @Override + public void begin(BufferBuilder buf, TextureManager texMan) { + Minecraft.getInstance().gameRenderer.lightTexture().turnOnLightLayer(); + RenderSystem.depthMask(false); + RenderSystem.enableBlend(); + RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE); + + RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_PARTICLES); + var tex = texMan.getTexture(TextureAtlas.LOCATION_PARTICLES); + IClientXplatAbstractions.INSTANCE.setFilterSave(tex, true, false); + buf.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.PARTICLE); + RenderSystem.enableDepthTest(); + } + + @Override + public void end(Tesselator tess) { + tess.end(); + IClientXplatAbstractions.INSTANCE.restoreLastFilter( + Minecraft.getInstance().getTextureManager().getTexture(TextureAtlas.LOCATION_PARTICLES)); + RenderSystem.disableBlend(); + RenderSystem.depthMask(true); + } + + @Override + public String toString() { + return HexAPI.MOD_ID + ":conjure"; + } + } + + public static final ConjureRenderType CONJURE_RENDER_TYPE = new ConjureRenderType(); } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/GaslightingTracker.java b/Common/src/main/java/at/petrak/hexcasting/client/render/GaslightingTracker.java index c57a5abd49..8d05682f84 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/GaslightingTracker.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/GaslightingTracker.java @@ -1,27 +1,27 @@ package at.petrak.hexcasting.client.render; -import net.minecraft.resources.ResourceLocation; - import static at.petrak.hexcasting.api.HexAPI.modLoc; +import net.minecraft.resources.ResourceLocation; + // *nothing* that changes can be looked at for changes public class GaslightingTracker { - private static int GASLIGHTING_AMOUNT = 0; - private static int LOOKING_COOLDOWN_MAX = 40; - private static int LOOKING_COOLDOWN = LOOKING_COOLDOWN_MAX; + private static int GASLIGHTING_AMOUNT = 0; + private static int LOOKING_COOLDOWN_MAX = 40; + private static int LOOKING_COOLDOWN = LOOKING_COOLDOWN_MAX; - public static ResourceLocation GASLIGHTING_PRED = modLoc("variant"); + public static ResourceLocation GASLIGHTING_PRED = modLoc("variant"); - public static int getGaslightingAmount() { - LOOKING_COOLDOWN = LOOKING_COOLDOWN_MAX; - return GASLIGHTING_AMOUNT; - } + public static int getGaslightingAmount() { + LOOKING_COOLDOWN = LOOKING_COOLDOWN_MAX; + return GASLIGHTING_AMOUNT; + } - public static void postFrameCheckRendered() { - if (LOOKING_COOLDOWN > 0) { - LOOKING_COOLDOWN -= 1; - } else { - GASLIGHTING_AMOUNT += 1; - } - } + public static void postFrameCheckRendered() { + if (LOOKING_COOLDOWN > 0) { + LOOKING_COOLDOWN -= 1; + } else { + GASLIGHTING_AMOUNT += 1; + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/HexAdditionalRenderers.java b/Common/src/main/java/at/petrak/hexcasting/client/render/HexAdditionalRenderers.java index 1861a9b8fb..29ceb53d81 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/HexAdditionalRenderers.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/HexAdditionalRenderers.java @@ -14,6 +14,8 @@ import com.mojang.blaze3d.vertex.Tesselator; import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.datafixers.util.Pair; +import java.util.List; +import java.util.function.BiConsumer; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.multiplayer.ClientLevel; @@ -29,195 +31,192 @@ import net.minecraft.world.phys.Vec3; import org.joml.Vector3f; -import java.util.List; -import java.util.function.BiConsumer; - public class HexAdditionalRenderers { - public static void overlayLevel(PoseStack ps, float partialTick) { - var player = Minecraft.getInstance().player; - if (player != null) { - var sentinel = IXplatAbstractions.INSTANCE.getSentinel(player); - if (sentinel != null && player.level().dimension().equals(sentinel.dimension())) { - renderSentinel(sentinel, player, ps, partialTick); - } - } - } - - public static void overlayGui(GuiGraphics graphics, float partialTicks) { - tryRenderScryingLensOverlay(graphics, partialTicks); - } - - private static void renderSentinel(Sentinel sentinel, LocalPlayer owner, - PoseStack ps, float partialTicks) { - ps.pushPose(); - - // zero vector is the player - var mc = Minecraft.getInstance(); - var camera = mc.gameRenderer.getMainCamera(); - var playerPos = camera.getPosition(); - ps.translate( - sentinel.position().x - playerPos.x, - sentinel.position().y - playerPos.y, - sentinel.position().z - playerPos.z); - - var time = ClientTickCounter.getTotal() / 2; - var bobSpeed = 1f / 20; - var magnitude = 0.1f; - ps.translate(0, Mth.sin(bobSpeed * time) * magnitude, 0); - var spinSpeed = 1f / 30; - ps.mulPose(QuaternionfUtils.fromXYZ(new Vector3f(0, spinSpeed * time, 0))); - if (sentinel.extendsRange()) { - ps.mulPose(QuaternionfUtils.fromXYZ(new Vector3f(spinSpeed * time / 8f, 0, 0))); - } - - float scale = 0.5f; - ps.scale(scale, scale, scale); - - - var tess = Tesselator.getInstance(); - var buf = tess.getBuilder(); - var neo = ps.last().pose(); - RenderSystem.enableBlend(); - RenderSystem.setShader(GameRenderer::getRendertypeLinesShader); - RenderSystem.disableDepthTest(); - RenderSystem.disableCull(); - RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); - RenderSystem.lineWidth(5f); - - var pigment = IXplatAbstractions.INSTANCE.getPigment(owner); - var colProvider = pigment.getColorProvider(); - BiConsumer v = (l, r) -> { - int lcolor = colProvider.getColor(time, new Vec3(l[0], l[1], l[2])), - rcolor = colProvider.getColor(time, new Vec3(r[0], r[1], r[2])); - var normal = new Vector3f(r[0] - l[0], r[1] - l[1], r[2] - l[2]); - normal.normalize(); - buf.vertex(neo, l[0], l[1], l[2]) - .color(lcolor) - .normal(ps.last().normal(), normal.x(), normal.y(), normal.z()) - .endVertex(); - buf.vertex(neo, r[0], r[1], r[2]) - .color(rcolor) - .normal(ps.last().normal(), -normal.x(), -normal.y(), -normal.z()) - .endVertex(); - }; - - // Icosahedron inscribed inside the unit sphere - buf.begin(VertexFormat.Mode.LINES, DefaultVertexFormat.POSITION_COLOR_NORMAL); - for (int side = 0; side <= 1; side++) { - var ring = (side == 0) ? Icos.BOTTOM_RING : Icos.TOP_RING; - var apex = (side == 0) ? Icos.BOTTOM : Icos.TOP; - - // top & bottom spider - for (int i = 0; i < 5; i++) { - v.accept(apex, ring[i]); - } - - // ring around - for (int i = 0; i < 5; i++) { - v.accept(ring[i % 5], ring[(i + 1) % 5]); - } - } - // center band - for (int i = 0; i < 5; i++) { - var bottom = Icos.BOTTOM_RING[i]; - v.accept(Icos.TOP_RING[(i + 2) % 5], bottom); - v.accept(bottom, Icos.TOP_RING[(i + 3) % 5]); - } - tess.end(); - - RenderSystem.enableDepthTest(); - RenderSystem.enableCull(); - ps.popPose(); - } - - private static class Icos { - public static float[] TOP = {0, 1, 0}; - public static float[] BOTTOM = {0, -1, 0}; - public static float[][] TOP_RING = new float[5][]; - public static float[][] BOTTOM_RING = new float[5][]; - - static { - var theta = (float) Mth.atan2(0.5, 1); - for (int i = 0; i < 5; i++) { - var phi = (float) i / 5f * Mth.TWO_PI; - var x = Mth.cos(theta) * Mth.cos(phi); - var y = Mth.sin(theta); - var z = Mth.cos(theta) * Mth.sin(phi); - TOP_RING[i] = new float[]{x, y, z}; - BOTTOM_RING[i] = new float[]{-x, -y, -z}; - } - } - } - - private static void tryRenderScryingLensOverlay(GuiGraphics graphics, float partialTicks) { - var mc = Minecraft.getInstance(); - var ps = graphics.pose(); - - LocalPlayer player = mc.player; - ClientLevel level = mc.level; - if (player == null || level == null) { - return; - } - - if (player.getAttributeValue(HexAttributes.SCRY_SIGHT) <= 0.0) - return; - - var hitRes = mc.hitResult; - if (hitRes != null && hitRes.getType() == HitResult.Type.BLOCK) { - var bhr = (BlockHitResult) hitRes; - var pos = bhr.getBlockPos(); - var bs = level.getBlockState(pos); - - var lines = ScryingLensOverlayRegistry.getLines(bs, pos, player, level, bhr.getDirection()); - - int totalHeight = 8; - List>> actualLines = Lists.newArrayList(); - - var window = mc.getWindow(); - var maxWidth = (int) (window.getGuiScaledWidth() / 2f * 0.8f); - - for (var pair : lines) { - totalHeight += mc.font.lineHeight + 6; - var text = pair.getSecond(); - var textLines = mc.font.getSplitter().splitLines(text, maxWidth, Style.EMPTY); - - actualLines.add(Pair.of(pair.getFirst(), textLines)); - - if (textLines.size() > 1) { - totalHeight += mc.font.lineHeight * (textLines.size() - 1); - } - } - - if (!lines.isEmpty()) { - var x = window.getGuiScaledWidth() / 2f + 8f; - var y = window.getGuiScaledHeight() / 2f - totalHeight; - ps.pushPose(); - ps.translate(x, y, 0); - - for (var pair : actualLines) { - var stack = pair.getFirst(); - if (!stack.isEmpty()) { - // this draws centered in the Y ... - graphics.renderItem(pair.getFirst(), 0, 0); - } - int tx = stack.isEmpty() ? 0 : 18; - int ty = 5; - // but this draws where y=0 is the baseline - var text = pair.getSecond(); - - for (var line : text) { - var actualLine = Language.getInstance().getVisualOrder(line); - graphics.drawString(mc.font, actualLine, tx, ty, 0xffffffff); - ps.translate(0, mc.font.lineHeight, 0); - } - if (text.isEmpty()) { - ps.translate(0, mc.font.lineHeight, 0); - } - ps.translate(0, 6, 0); - } - - ps.popPose(); - } - } - } + public static void overlayLevel(PoseStack ps, float partialTick) { + var player = Minecraft.getInstance().player; + if (player != null) { + var sentinel = IXplatAbstractions.INSTANCE.getSentinel(player); + if (sentinel != null && player.level().dimension().equals(sentinel.dimension())) { + renderSentinel(sentinel, player, ps, partialTick); + } + } + } + + public static void overlayGui(GuiGraphics graphics, float partialTicks) { + tryRenderScryingLensOverlay(graphics, partialTicks); + } + + private static void renderSentinel( + Sentinel sentinel, LocalPlayer owner, PoseStack ps, float partialTicks) { + ps.pushPose(); + + // zero vector is the player + var mc = Minecraft.getInstance(); + var camera = mc.gameRenderer.getMainCamera(); + var playerPos = camera.getPosition(); + ps.translate( + sentinel.position().x - playerPos.x, + sentinel.position().y - playerPos.y, + sentinel.position().z - playerPos.z); + + var time = ClientTickCounter.getTotal() / 2; + var bobSpeed = 1f / 20; + var magnitude = 0.1f; + ps.translate(0, Mth.sin(bobSpeed * time) * magnitude, 0); + var spinSpeed = 1f / 30; + ps.mulPose(QuaternionfUtils.fromXYZ(new Vector3f(0, spinSpeed * time, 0))); + if (sentinel.extendsRange()) { + ps.mulPose(QuaternionfUtils.fromXYZ(new Vector3f(spinSpeed * time / 8f, 0, 0))); + } + + float scale = 0.5f; + ps.scale(scale, scale, scale); + + var tess = Tesselator.getInstance(); + var buf = tess.getBuilder(); + var neo = ps.last().pose(); + RenderSystem.enableBlend(); + RenderSystem.setShader(GameRenderer::getRendertypeLinesShader); + RenderSystem.disableDepthTest(); + RenderSystem.disableCull(); + RenderSystem.blendFunc( + GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + RenderSystem.lineWidth(5f); + + var pigment = IXplatAbstractions.INSTANCE.getPigment(owner); + var colProvider = pigment.getColorProvider(); + BiConsumer v = + (l, r) -> { + int lcolor = colProvider.getColor(time, new Vec3(l[0], l[1], l[2])), + rcolor = colProvider.getColor(time, new Vec3(r[0], r[1], r[2])); + var normal = new Vector3f(r[0] - l[0], r[1] - l[1], r[2] - l[2]); + normal.normalize(); + buf.vertex(neo, l[0], l[1], l[2]) + .color(lcolor) + .normal(ps.last().normal(), normal.x(), normal.y(), normal.z()) + .endVertex(); + buf.vertex(neo, r[0], r[1], r[2]) + .color(rcolor) + .normal(ps.last().normal(), -normal.x(), -normal.y(), -normal.z()) + .endVertex(); + }; + + // Icosahedron inscribed inside the unit sphere + buf.begin(VertexFormat.Mode.LINES, DefaultVertexFormat.POSITION_COLOR_NORMAL); + for (int side = 0; side <= 1; side++) { + var ring = (side == 0) ? Icos.BOTTOM_RING : Icos.TOP_RING; + var apex = (side == 0) ? Icos.BOTTOM : Icos.TOP; + + // top & bottom spider + for (int i = 0; i < 5; i++) { + v.accept(apex, ring[i]); + } + + // ring around + for (int i = 0; i < 5; i++) { + v.accept(ring[i % 5], ring[(i + 1) % 5]); + } + } + // center band + for (int i = 0; i < 5; i++) { + var bottom = Icos.BOTTOM_RING[i]; + v.accept(Icos.TOP_RING[(i + 2) % 5], bottom); + v.accept(bottom, Icos.TOP_RING[(i + 3) % 5]); + } + tess.end(); + + RenderSystem.enableDepthTest(); + RenderSystem.enableCull(); + ps.popPose(); + } + + private static class Icos { + public static float[] TOP = {0, 1, 0}; + public static float[] BOTTOM = {0, -1, 0}; + public static float[][] TOP_RING = new float[5][]; + public static float[][] BOTTOM_RING = new float[5][]; + + static { + var theta = (float) Mth.atan2(0.5, 1); + for (int i = 0; i < 5; i++) { + var phi = (float) i / 5f * Mth.TWO_PI; + var x = Mth.cos(theta) * Mth.cos(phi); + var y = Mth.sin(theta); + var z = Mth.cos(theta) * Mth.sin(phi); + TOP_RING[i] = new float[] {x, y, z}; + BOTTOM_RING[i] = new float[] {-x, -y, -z}; + } + } + } + + private static void tryRenderScryingLensOverlay(GuiGraphics graphics, float partialTicks) { + var mc = Minecraft.getInstance(); + var ps = graphics.pose(); + + LocalPlayer player = mc.player; + ClientLevel level = mc.level; + if (player == null || level == null) { + return; + } + + if (player.getAttributeValue(HexAttributes.SCRY_SIGHT) <= 0.0) return; + + var hitRes = mc.hitResult; + if (hitRes != null && hitRes.getType() == HitResult.Type.BLOCK) { + var bhr = (BlockHitResult) hitRes; + var pos = bhr.getBlockPos(); + var bs = level.getBlockState(pos); + + var lines = ScryingLensOverlayRegistry.getLines(bs, pos, player, level, bhr.getDirection()); + + int totalHeight = 8; + List>> actualLines = Lists.newArrayList(); + + var window = mc.getWindow(); + var maxWidth = (int) (window.getGuiScaledWidth() / 2f * 0.8f); + + for (var pair : lines) { + totalHeight += mc.font.lineHeight + 6; + var text = pair.getSecond(); + var textLines = mc.font.getSplitter().splitLines(text, maxWidth, Style.EMPTY); + + actualLines.add(Pair.of(pair.getFirst(), textLines)); + + if (textLines.size() > 1) { + totalHeight += mc.font.lineHeight * (textLines.size() - 1); + } + } + + if (!lines.isEmpty()) { + var x = window.getGuiScaledWidth() / 2f + 8f; + var y = window.getGuiScaledHeight() / 2f - totalHeight; + ps.pushPose(); + ps.translate(x, y, 0); + + for (var pair : actualLines) { + var stack = pair.getFirst(); + if (!stack.isEmpty()) { + // this draws centered in the Y ... + graphics.renderItem(pair.getFirst(), 0, 0); + } + int tx = stack.isEmpty() ? 0 : 18; + int ty = 5; + // but this draws where y=0 is the baseline + var text = pair.getSecond(); + + for (var line : text) { + var actualLine = Language.getInstance().getVisualOrder(line); + graphics.drawString(mc.font, actualLine, tx, ty, 0xffffffff); + ps.translate(0, mc.font.lineHeight, 0); + } + if (text.isEmpty()) { + ps.translate(0, mc.font.lineHeight, 0); + } + ps.translate(0, 6, 0); + } + + 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 index 1b82992398..97015f37d6 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternLike.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternLike.java @@ -1,70 +1,75 @@ 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; +import net.minecraft.world.phys.Vec2; /** * 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. + *

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(); + List getNonZappyPoints(); - String getName(); + String getName(); - Set getDups(); + Set getDups(); - static HexPatternLike of(HexPattern pat){ - return new HexPatternLikeBecauseItsActuallyAHexPattern(pat); - } + static HexPatternLike of(HexPattern pat) { + return new HexPatternLikeBecauseItsActuallyAHexPattern(pat); + } - static HexPatternLike of(List lines, String name){ - return new PureLines(lines, name); - } + 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); - } + record HexPatternLikeBecauseItsActuallyAHexPattern(HexPattern pat) implements HexPatternLike { + public List getNonZappyPoints() { + return pat.toLines(1, Vec2.ZERO); + } - public String getName(){ - return pat.getStartDir() + "-" + pat.anglesSignature(); - } + public String getName() { + return pat.getStartDir() + "-" + pat.anglesSignature(); + } - public Set getDups(){ - return RenderLib.findDupIndices(pat.positions()); - } - } + public Set getDups() { + return RenderLib.findDupIndices(pat.positions()); + } + } - record PureLines(List lines, String name) implements HexPatternLike{ + record PureLines(List lines, String name) implements HexPatternLike { - public List getNonZappyPoints(){ - return lines; - } + public List getNonZappyPoints() { + return lines; + } - public String getName(){ - return name; - } + 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; - } + 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() - ); - } - } + @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 1a55d06b5e..7ff0f86e21 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,134 +1,158 @@ 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; +import net.minecraft.world.phys.Vec2; /** - * 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. + * 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 final ImmutableList zappyPoints; - public final ImmutableList zappyPointsScaled; - - 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 + public final ImmutableList zappyPoints; + public final ImmutableList zappyPointsScaled; + + 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)); + } +} 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 index fb64669834..6936c8d3e8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternColors.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternColors.java @@ -2,67 +2,89 @@ /** * 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. + * + *

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){ +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); + // 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 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 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 :) + 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); - } + // 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); - } + // 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); - } + // 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); - } + 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); - } + // 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(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); - } + 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); - } + // 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); - } + // 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 index 8fd307a9a4..be510ab733 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternRenderer.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternRenderer.java @@ -1,11 +1,15 @@ 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 java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import javax.annotation.Nullable; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.resources.ResourceLocation; @@ -13,139 +17,213 @@ 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){} + 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 index 4cb223f6f6..9ee5b4167f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternSettings.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternSettings.java @@ -3,140 +3,225 @@ /** * 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. + *

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; } + 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 8486124920..f4fe73f78e 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,134 +1,174 @@ package at.petrak.hexcasting.client.render; import com.mojang.blaze3d.platform.NativeImage; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.*; +import java.util.List; +import java.util.concurrent.*; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Tuple; import net.minecraft.world.phys.Vec2; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.List; -import java.util.*; -import java.util.concurrent.*; - public class PatternTextureManager { - //TODO: remove if not needed anymore for comparison - public static boolean useTextures = true; - public static int repaintIndex = 0; - - 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<>(); - - public static Optional> getTextures(HexPatternLike patternlike, PatternSettings patSets, double seed, int resPerUnit) { - String patCacheKey = patSets.getCacheKey(patternlike, seed) + "_" + resPerUnit; - - // 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(); - - return Optional.empty(); // try not giving it immediately to avoid flickering? - } - if (patternTextures.containsKey(patCacheKey)) - return Optional.of(patternTextures.get(patCacheKey)); - - // 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); - }); - }); - } - return Optional.empty(); - } - - private static Map createTextures(HexPatternLike patternlike, PatternSettings patSets, double seed, int resPerUnit) { - HexPatternPoints staticPoints = HexPatternPoints.getStaticPoints(patternlike, patSets, seed); - - List zappyRenderSpace = staticPoints.scaleVecs(staticPoints.zappyPoints); - - Map patTexts = new HashMap<>(); - - NativeImage innerLines = drawLines(zappyRenderSpace, staticPoints, (float)patSets.getInnerWidth((staticPoints.finalScale)), resPerUnit); - patTexts.put("inner", new DynamicTexture(innerLines)); - - NativeImage outerLines = drawLines(zappyRenderSpace, staticPoints, (float)patSets.getOuterWidth((staticPoints.finalScale)), resPerUnit); - patTexts.put("outer", new DynamicTexture(outerLines)); - - return patTexts; - } - - 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; - } - - 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(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 nativeImage; - } - - 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(); - - for (int i = 0; i < fracOfCircle; i++) { - double theta = (i / (double) fracOfCircle) * Math.PI * 2; - int hx = (int) (x + Math.cos(theta) * radius); - int hy = (int) (y + Math.sin(theta) * radius); - hexagon.addPoint(hx, hy); - } - - g2d.fill(hexagon); - } - - public static void repaint() { - repaintIndex++; - patternTexturesToAdd.clear(); - patternTextures.clear(); - } -} \ No newline at end of file + // TODO: remove if not needed anymore for comparison + public static boolean useTextures = true; + public static int repaintIndex = 0; + + 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<>(); + + public static Optional> getTextures( + HexPatternLike patternlike, PatternSettings patSets, double seed, int resPerUnit) { + String patCacheKey = patSets.getCacheKey(patternlike, seed) + "_" + resPerUnit; + + // 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(); + + return Optional.empty(); // try not giving it immediately to avoid flickering? + } + if (patternTextures.containsKey(patCacheKey)) + return Optional.of(patternTextures.get(patCacheKey)); + + // 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); + }); + }); + } + return Optional.empty(); + } + + private static Map createTextures( + HexPatternLike patternlike, PatternSettings patSets, double seed, int resPerUnit) { + HexPatternPoints staticPoints = HexPatternPoints.getStaticPoints(patternlike, patSets, seed); + + List zappyRenderSpace = staticPoints.scaleVecs(staticPoints.zappyPoints); + + Map patTexts = new HashMap<>(); + + NativeImage innerLines = + drawLines( + zappyRenderSpace, + staticPoints, + (float) patSets.getInnerWidth((staticPoints.finalScale)), + resPerUnit); + patTexts.put("inner", new DynamicTexture(innerLines)); + + NativeImage outerLines = + drawLines( + zappyRenderSpace, + staticPoints, + (float) patSets.getOuterWidth((staticPoints.finalScale)), + resPerUnit); + patTexts.put("outer", new DynamicTexture(outerLines)); + + return patTexts; + } + + 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; + } + + 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( + 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 nativeImage; + } + + 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(); + + for (int i = 0; i < fracOfCircle; i++) { + double theta = (i / (double) fracOfCircle) * Math.PI * 2; + int hx = (int) (x + Math.cos(theta) * radius); + int hy = (int) (y + Math.sin(theta) * radius); + hexagon.addPoint(hx, hy); + } + + g2d.fill(hexagon); + } + + public static void repaint() { + repaintIndex++; + patternTexturesToAdd.clear(); + patternTextures.clear(); + } +} 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 823068439f..f144bcc335 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 @@ -12,6 +12,10 @@ import com.mojang.blaze3d.vertex.PoseStack import com.mojang.blaze3d.vertex.Tesselator import com.mojang.blaze3d.vertex.VertexFormat import com.mojang.math.Axis +import kotlin.math.abs +import kotlin.math.min +import kotlin.math.roundToInt +import kotlin.math.sin import net.minecraft.client.Minecraft import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.screens.Screen @@ -25,33 +29,22 @@ import net.minecraft.world.level.levelgen.SingleThreadedRandomSource import net.minecraft.world.level.levelgen.synth.SimplexNoise import net.minecraft.world.phys.Vec2 import org.joml.Matrix4f -import kotlin.math.abs -import kotlin.math.min -import kotlin.math.roundToInt -import kotlin.math.sin val NOISE: SimplexNoise = SimplexNoise(SingleThreadedRandomSource(9001L)) // see the test; perlin noise seems to output almost exclusively between -0.5 and 0.5 -// i could probably impl this "properly" with some kind of exponent but it's faster and easier to divide +// i could probably impl this "properly" with some kind of exponent but it's faster and easier to +// divide fun getNoise(x: Double, y: Double, z: Double): Double = - NOISE.getValue(x * 0.6, y * 0.6, z * 0.6) / 2.0 + NOISE.getValue(x * 0.6, y * 0.6, z * 0.6) / 2.0 // how many degrees are between each triangle on the smooth caps of the lines 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)) +fun drawLineSeq(mat: Matrix4f, points: List, width: Float, z: Float, tail: Int, head: Int) { + return drawLineSeq(mat, points, width, tail, head, VCDrawHelper.Basic(z)) } /** @@ -60,148 +53,157 @@ fun drawLineSeq( * Please make sure to enable the right asinine shaders; see [GuiSpellcasting] */ fun drawLineSeq( - mat: Matrix4f, - points: List, - width: Float, - tail: Int, - head: Int, - vcHelper: VCDrawHelper + mat: Matrix4f, + points: List, + width: Float, + tail: Int, + head: Int, + vcHelper: VCDrawHelper ) { - if (points.size <= 1) return - - val r1 = FastColor.ARGB32.red(tail).toFloat() - 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 - tail - 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() - - var vc = vcHelper.vcSetupAndSupply(VertexFormat.Mode.TRIANGLES) - - val n = points.size - val joinAngles = FloatArray(n) - val joinOffsets = FloatArray(n) - for (i in 2 until n) { - val p0 = points[i - 2] - val p1 = points[i - 1] - val p2 = points[i] - val prev = p1.add(p0.negated()) - val next = p2.add(p1.negated()) - val angle = - Mth.atan2((prev.x * next.y - prev.y * next.x).toDouble(), (prev.x * next.x + prev.y * next.y).toDouble()) - .toFloat() - joinAngles[i - 1] = angle - val clamp = prev.length().coerceAtMost(next.length()) / (width * 0.5f) - joinOffsets[i - 1] = Mth.clamp(Mth.sin(angle) / (1 + Mth.cos(angle)), -clamp, clamp) - } - - for (i in 0 until points.size - 1) { - val p1 = points[i] - val p2 = points[i + 1] - // https://github.com/not-fl3/macroquad/blob/master/src/shapes.rs#L163 - // GuiComponent::innerFill line 52 - // fedor have useful variable names challenge (99% can't beat) - val tangent = p2.add(p1.negated()).normalized().scale(width * 0.5f) - val normal = Vec2(-tangent.y, tangent.x) - - 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) - val jlow = joinOffsets[i] - val jhigh = joinOffsets[i + 1] - // Draw the line segment as a hexagon, sort of - // I can't imagine what the hell alwinfy is up to but this is implementing what TRIANGLE_FAN does - // using normal triangles so we can send the entire segment to the buffer at once - val p1Down = p1.add(tangent.scale(Math.max(0f, jlow))).add(normal) - val p1Up = p1.add(tangent.scale(Math.max(0f, -jlow))).add(normal.negated()) - 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()) - - vcHelper.vertex(vc, color1, p1Down, mat) - vcHelper.vertex(vc, color1, p1, mat) - vcHelper.vertex(vc, color1, p1Up, mat) - - vcHelper.vertex(vc, color1, p1Down, mat) - vcHelper.vertex(vc, color1, p1Up, mat) - vcHelper.vertex(vc, color2, p2Up, mat) - - vcHelper.vertex(vc, color1, p1Down, mat) - vcHelper.vertex(vc, color2, p2Up, mat) - vcHelper.vertex(vc, color2, p2, mat) - - 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 - val sangle = joinAngles[i] - val angle = Math.abs(sangle) - val rnormal = normal.negated() - val joinSteps = Mth.ceil(angle * 180 / (CAP_THETA * Mth.PI)) - if (joinSteps < 1) { - continue - } - - if (sangle < 0) { - var prevVert = Vec2(p1.x - rnormal.x, p1.y - rnormal.y) - for (j in 1..joinSteps) { - val fan = rotate(rnormal, -sangle * (j.toFloat() / joinSteps)) - val fanShift = Vec2(p1.x - fan.x, p1.y - fan.y) - - vcHelper.vertex(vc, color1, p1, mat) - vcHelper.vertex(vc, color1, prevVert, mat) - vcHelper.vertex(vc, color1, fanShift, mat) - prevVert = fanShift - } - } else { - val startFan = rotate(normal, -sangle) - var prevVert = Vec2(p1.x - startFan.x, p1.y - startFan.y) - for (j in joinSteps - 1 downTo 0) { - val fan = rotate(normal, -sangle * (j.toFloat() / joinSteps)) - val fanShift = Vec2(p1.x - fan.x, p1.y - fan.y) - - vcHelper.vertex(vc, color1, p1, mat) - vcHelper.vertex(vc, color1, prevVert, mat) - vcHelper.vertex(vc, color1, fanShift, mat) - prevVert = fanShift - } - } - } - } - vcHelper.vcEndDrawer(vc) - - 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) - 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)) - vcHelper.vertex(vc, color, Vec2(point.x + fan.x, point.y + fan.y), mat) - } - vcHelper.vcEndDrawer(vc) - } - 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]) + if (points.size <= 1) return + + val r1 = FastColor.ARGB32.red(tail).toFloat() + 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 tail + 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() + + var vc = vcHelper.vcSetupAndSupply(VertexFormat.Mode.TRIANGLES) + + val n = points.size + val joinAngles = FloatArray(n) + val joinOffsets = FloatArray(n) + for (i in 2 until n) { + val p0 = points[i - 2] + val p1 = points[i - 1] + val p2 = points[i] + val prev = p1.add(p0.negated()) + val next = p2.add(p1.negated()) + val angle = + Mth.atan2( + (prev.x * next.y - prev.y * next.x).toDouble(), + (prev.x * next.x + prev.y * next.y).toDouble() + ) + .toFloat() + joinAngles[i - 1] = angle + val clamp = prev.length().coerceAtMost(next.length()) / (width * 0.5f) + joinOffsets[i - 1] = Mth.clamp(Mth.sin(angle) / (1 + Mth.cos(angle)), -clamp, clamp) + } + + for (i in 0 until points.size - 1) { + val p1 = points[i] + val p2 = points[i + 1] + // https://github.com/not-fl3/macroquad/blob/master/src/shapes.rs#L163 + // GuiComponent::innerFill line 52 + // fedor have useful variable names challenge (99% can't beat) + val tangent = p2.add(p1.negated()).normalized().scale(width * 0.5f) + val normal = Vec2(-tangent.y, tangent.x) + + 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) + val jlow = joinOffsets[i] + val jhigh = joinOffsets[i + 1] + // Draw the line segment as a hexagon, sort of + // I can't imagine what the hell alwinfy is up to but this is implementing what TRIANGLE_FAN + // does + // using normal triangles so we can send the entire segment to the buffer at once + val p1Down = p1.add(tangent.scale(Math.max(0f, jlow))).add(normal) + val p1Up = p1.add(tangent.scale(Math.max(0f, -jlow))).add(normal.negated()) + 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()) + + vcHelper.vertex(vc, color1, p1Down, mat) + vcHelper.vertex(vc, color1, p1, mat) + vcHelper.vertex(vc, color1, p1Up, mat) + + vcHelper.vertex(vc, color1, p1Down, mat) + vcHelper.vertex(vc, color1, p1Up, mat) + vcHelper.vertex(vc, color2, p2Up, mat) + + vcHelper.vertex(vc, color1, p1Down, mat) + vcHelper.vertex(vc, color2, p2Up, mat) + vcHelper.vertex(vc, color2, p2, mat) + + 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 + val sangle = joinAngles[i] + val angle = Math.abs(sangle) + val rnormal = normal.negated() + val joinSteps = Mth.ceil(angle * 180 / (CAP_THETA * Mth.PI)) + if (joinSteps < 1) { + continue + } + + if (sangle < 0) { + var prevVert = Vec2(p1.x - rnormal.x, p1.y - rnormal.y) + for (j in 1..joinSteps) { + val fan = rotate(rnormal, -sangle * (j.toFloat() / joinSteps)) + val fanShift = Vec2(p1.x - fan.x, p1.y - fan.y) + + vcHelper.vertex(vc, color1, p1, mat) + vcHelper.vertex(vc, color1, prevVert, mat) + vcHelper.vertex(vc, color1, fanShift, mat) + prevVert = fanShift + } + } else { + val startFan = rotate(normal, -sangle) + var prevVert = Vec2(p1.x - startFan.x, p1.y - startFan.y) + for (j in joinSteps - 1 downTo 0) { + val fan = rotate(normal, -sangle * (j.toFloat() / joinSteps)) + val fanShift = Vec2(p1.x - fan.x, p1.y - fan.y) + + vcHelper.vertex(vc, color1, p1, mat) + vcHelper.vertex(vc, color1, prevVert, mat) + vcHelper.vertex(vc, color1, fanShift, mat) + prevVert = fanShift + } + } + } + } + vcHelper.vcEndDrawer(vc) + + 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) + 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)) + vcHelper.vertex(vc, color, Vec2(point.x + fan.x, point.y + fan.y), mat) + } + vcHelper.vcEndDrawer(vc) + } + 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) - return Vec2(vec.x * cos - vec.y * sin, vec.y * cos + vec.x * sin) + val cos = Mth.cos(theta) + val sin = Mth.sin(theta) + return Vec2(vec.x * cos - vec.y * sin, vec.y * cos + vec.x * sin) } /** @@ -209,257 +211,276 @@ fun rotate(vec: Vec2, theta: Float): Vec2 { * you have to do the conversion yourself.) */ fun drawPatternFromPoints( - mat: Matrix4f, - points: List, - dupIndices: Set?, - drawLast: Boolean, - tail: Int, - head: Int, - flowIrregular: Float, - readabilityOffset: Float, - lastSegmentLenProportion: Float, - seed: Double + mat: Matrix4f, + points: List, + dupIndices: Set?, + drawLast: Boolean, + tail: Int, + head: Int, + flowIrregular: Float, + readabilityOffset: Float, + lastSegmentLenProportion: Float, + seed: Double ) { - val zappyPts = makeZappy(points, dupIndices, 10, 2.5f, 0.1f, flowIrregular, readabilityOffset, lastSegmentLenProportion, seed) - val nodes = if (drawLast) { - points - } else { - points.dropLast(1) - } - drawLineSeq(mat, zappyPts, 5f, 0f, tail, head) - drawLineSeq(mat, zappyPts, 2f, 1f, screenCol(tail), screenCol(head)) - for (node in nodes) { - drawSpot( - mat, - node, - 2f, - dodge(FastColor.ARGB32.red(head)) / 255f, - dodge(FastColor.ARGB32.green(head)) / 255f, - dodge(FastColor.ARGB32.blue(head)) / 255f, - FastColor.ARGB32.alpha(head) / 255f - ) - } + val zappyPts = + makeZappy( + points, + dupIndices, + 10, + 2.5f, + 0.1f, + flowIrregular, + readabilityOffset, + lastSegmentLenProportion, + seed + ) + val nodes = + if (drawLast) { + points + } else { + points.dropLast(1) + } + drawLineSeq(mat, zappyPts, 5f, 0f, tail, head) + drawLineSeq(mat, zappyPts, 2f, 1f, screenCol(tail), screenCol(head)) + for (node in nodes) { + drawSpot( + mat, + node, + 2f, + dodge(FastColor.ARGB32.red(head)) / 255f, + dodge(FastColor.ARGB32.green(head)) / 255f, + dodge(FastColor.ARGB32.blue(head)) / 255f, + FastColor.ARGB32.alpha(head) / 255f + ) + } } /** * Split up a sequence of linePoints with a lightning effect + * * @param hops: rough number of points to subdivide each segment into * @param speed: rate at which the lightning effect should move/shake/etc */ fun makeZappy( - barePoints: List, dupIndices: Set?, hops: Int, variance: Float, speed: Float, flowIrregular: Float, - readabilityOffset: Float, lastSegmentLenProportion: Float, seed: Double + barePoints: List, + dupIndices: Set?, + hops: Int, + variance: Float, + speed: Float, + flowIrregular: Float, + readabilityOffset: Float, + lastSegmentLenProportion: Float, + seed: Double ): List { - // Nothing in, nothing out - if (barePoints.isEmpty()) { - return emptyList() - } - fun zappify(points: List, truncateLast: Boolean): List { - val scaleVariance = { it: Double -> 1.0.coerceAtMost(8 * (0.5 - abs(0.5 - it))) } - val zSeed = ClientTickCounter.getTotal().toDouble() * speed - // Create our output list of zap points - val zappyPts = ArrayList(points.size * hops) - zappyPts.add(points[0]) - // For each segment in the original... - for ((i, pair) in points.zipWithNext().withIndex()) { - val (src, target) = pair - val delta = target.add(src.negated()) - // Take hop distance - val hopDist = Mth.sqrt(src.distanceToSqr(target)) / hops - // Compute how big the radius of variance should be - val maxVariance = hopDist * variance - - // for a list of length n, there will be n-1 pairs, - // and so the last index will be (n-1)-1 - val maxJ = if (truncateLast && i == points.size - 2) { - (lastSegmentLenProportion * hops.toFloat()).roundToInt() - } else hops - - for (j in 1..maxJ) { - val progress = j.toDouble() / (hops + 1) - // Add the next hop... - val pos = src.add(delta.scale(progress.toFloat())) - // as well as some random variance... - // (We use i, j (segment #, subsegment #) as seeds for the Perlin noise, - // and zSeed (i.e. time elapsed) to perturb the shape gradually over time) - val minorPerturb = getNoise(i.toDouble(), j.toDouble(), sin(zSeed)) * flowIrregular - val theta = (3 * getNoise( - i + progress + minorPerturb - zSeed, - 1337.0, - seed - ) * TAU).toFloat() - val r = (getNoise( - i + progress - zSeed, - 69420.0, - seed - ) * maxVariance * scaleVariance(progress)).toFloat() - val randomHop = Vec2(r * Mth.cos(theta), r * Mth.sin(theta)) - // Then record the new location. - zappyPts.add(pos.add(randomHop)) - - if (j == hops) { - // Finally, we hit the destination, add that too - // but we might not hit the destination if we want to stop short - zappyPts.add(target) - } - } - } - return zappyPts - } - - val points = mutableListOf() - val daisyChain = mutableListOf() - return if (dupIndices != null) { - for ((i, pair) in barePoints.zipWithNext().withIndex()) { - val (head, tail) = pair - val tangent = tail.add(head.negated()).scale(readabilityOffset) - if (i != 0 && dupIndices.contains(i)) { - daisyChain.add(head.add(tangent)) - } else { - daisyChain.add(head) - } - if (i == barePoints.size - 2) { - daisyChain.add(tail) - points.addAll(zappify(daisyChain, true)) - } else if (dupIndices.contains(i + 1)) { - daisyChain.add(tail.add(tangent.negated())) - points.addAll(zappify(daisyChain, false)) - daisyChain.clear() - } - } - points - } else { - zappify(barePoints, true) - } + // Nothing in, nothing out + if (barePoints.isEmpty()) { + return emptyList() + } + fun zappify(points: List, truncateLast: Boolean): List { + val scaleVariance = { it: Double -> 1.0.coerceAtMost(8 * (0.5 - abs(0.5 - it))) } + val zSeed = ClientTickCounter.getTotal().toDouble() * speed + // Create our output list of zap points + val zappyPts = ArrayList(points.size * hops) + zappyPts.add(points[0]) + // For each segment in the original... + for ((i, pair) in points.zipWithNext().withIndex()) { + val (src, target) = pair + val delta = target.add(src.negated()) + // Take hop distance + val hopDist = Mth.sqrt(src.distanceToSqr(target)) / hops + // Compute how big the radius of variance should be + val maxVariance = hopDist * variance + + // for a list of length n, there will be n-1 pairs, + // and so the last index will be (n-1)-1 + val maxJ = + if (truncateLast && i == points.size - 2) { + (lastSegmentLenProportion * hops.toFloat()).roundToInt() + } else hops + + for (j in 1..maxJ) { + val progress = j.toDouble() / (hops + 1) + // Add the next hop... + val pos = src.add(delta.scale(progress.toFloat())) + // as well as some random variance... + // (We use i, j (segment #, subsegment #) as seeds for the Perlin noise, + // and zSeed (i.e. time elapsed) to perturb the shape gradually over time) + val minorPerturb = getNoise(i.toDouble(), j.toDouble(), sin(zSeed)) * flowIrregular + val theta = + (3 * getNoise(i + progress + minorPerturb - zSeed, 1337.0, seed) * TAU).toFloat() + val r = + (getNoise(i + progress - zSeed, 69420.0, seed) * maxVariance * scaleVariance(progress)) + .toFloat() + val randomHop = Vec2(r * Mth.cos(theta), r * Mth.sin(theta)) + // Then record the new location. + zappyPts.add(pos.add(randomHop)) + + if (j == hops) { + // Finally, we hit the destination, add that too + // but we might not hit the destination if we want to stop short + zappyPts.add(target) + } + } + } + return zappyPts + } + + val points = mutableListOf() + val daisyChain = mutableListOf() + return if (dupIndices != null) { + for ((i, pair) in barePoints.zipWithNext().withIndex()) { + val (head, tail) = pair + val tangent = tail.add(head.negated()).scale(readabilityOffset) + if (i != 0 && dupIndices.contains(i)) { + daisyChain.add(head.add(tangent)) + } else { + daisyChain.add(head) + } + if (i == barePoints.size - 2) { + daisyChain.add(tail) + points.addAll(zappify(daisyChain, true)) + } else if (dupIndices.contains(i + 1)) { + daisyChain.add(tail.add(tangent.negated())) + points.addAll(zappify(daisyChain, false)) + daisyChain.clear() + } + } + points + } else { + zappify(barePoints, true) + } } fun findDupIndices(pts: Iterable): Set { - val dedup = HashMap() - val found = HashSet() - for ((i, pt) in pts.withIndex()) { - val ix = dedup[pt] - if (ix != null) { - found.add(i) - found.add(ix) - } else { - dedup.put(pt, i) - } - } - return found + val dedup = HashMap() + val found = HashSet() + for ((i, pt) in pts.withIndex()) { + val ix = dedup[pt] + if (ix != null) { + found.add(i) + found.add(ix) + } else { + dedup.put(pt, i) + } + } + return found } /** - * Draw a little circle, because Minecraft rendering code is a nightmare and doesn't - * include primitive drawing code... + * Draw a little circle, because Minecraft rendering code is a nightmare and doesn't include + * primitive drawing code... */ fun drawSpot(mat: Matrix4f, point: Vec2, radius: Float, r: Float, g: Float, b: Float, a: Float) { - drawSpot(mat, point, radius, ARGB32.color((a*255).toInt(), (r*255).toInt(), (g*255).toInt(), (b*255).toInt()), VCDrawHelper.Basic(1f)) + 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 - val fracOfCircle = 6 - // run 0 AND last; this way the circle closes - for (i in 0..fracOfCircle) { - val theta = i.toFloat() / fracOfCircle * TAU.toFloat() - val rx = Mth.cos(theta) * radius + point.x - val ry = Mth.sin(theta) * radius + point.y - vcHelper.vertex(vc, color, Vec2(rx, ry), mat) - } - - vcHelper.vcEndDrawer(vc) + 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 + val fracOfCircle = 6 + // run 0 AND last; this way the circle closes + for (i in 0..fracOfCircle) { + val theta = i.toFloat() / fracOfCircle * TAU.toFloat() + val rx = Mth.cos(theta) * radius + point.x + val ry = Mth.sin(theta) * radius + point.y + vcHelper.vertex(vc, color, Vec2(rx, ry), mat) + } + + vcHelper.vcEndDrawer(vc) } fun screenCol(n: Int): Int { - return FastColor.ARGB32.color( - FastColor.ARGB32.alpha(n), - screen(FastColor.ARGB32.red(n)), - screen(FastColor.ARGB32.green(n)), - screen(FastColor.ARGB32.blue(n)), - ) + return FastColor.ARGB32.color( + FastColor.ARGB32.alpha(n), + screen(FastColor.ARGB32.red(n)), + screen(FastColor.ARGB32.green(n)), + screen(FastColor.ARGB32.blue(n)), + ) } fun screen(n: Int) = (n + 255) / 2 + fun dodge(n: Int) = n * 0.9f -/** - * Return the scale and dots formed by the pattern when centered. - */ -fun getCenteredPattern(pattern: HexPattern, width: Float, height: Float, minSize: Float): Pair> { - // Do two passes: one with a random size to find a good COM and one with the real calculation - val com1: Vec2 = pattern.getCenter(1f) - val lines1: List = pattern.toLines(1f, Vec2.ZERO) - var maxDx = -1f - var maxDy = -1f - for (dot in lines1) { - val dx = Mth.abs(dot.x - com1.x) - if (dx > maxDx) { - maxDx = dx - } - val dy = Mth.abs(dot.y - com1.y) - if (dy > maxDy) { - maxDy = dy - } - } - val scale = - min(minSize, min(width / 3f / maxDx, height / 3f / maxDy)) - val com2: Vec2 = pattern.getCenter(scale) - val lines2: List = pattern.toLines(scale, com2.negated()) - return scale to lines2 +/** Return the scale and dots formed by the pattern when centered. */ +fun getCenteredPattern( + pattern: HexPattern, + width: Float, + height: Float, + minSize: Float +): Pair> { + // Do two passes: one with a random size to find a good COM and one with the real calculation + val com1: Vec2 = pattern.getCenter(1f) + val lines1: List = pattern.toLines(1f, Vec2.ZERO) + var maxDx = -1f + var maxDy = -1f + for (dot in lines1) { + val dx = Mth.abs(dot.x - com1.x) + if (dx > maxDx) { + maxDx = dx + } + val dy = Mth.abs(dot.y - com1.y) + if (dy > maxDy) { + maxDy = dy + } + } + val scale = min(minSize, min(width / 3f / maxDx, height / 3f / maxDy)) + val com2: Vec2 = pattern.getCenter(scale) + val lines2: List = pattern.toLines(scale, com2.negated()) + return scale to lines2 } @JvmOverloads fun renderEntity( - graphics: GuiGraphics, entity: Entity, world: Level, x: Float, y: Float, rotation: Float, - renderScale: Float, offset: Float, - bufferTransformer: (MultiBufferSource) -> MultiBufferSource = { it -> it } + graphics: GuiGraphics, + entity: Entity, + world: Level, + x: Float, + y: Float, + rotation: Float, + renderScale: Float, + offset: Float, + bufferTransformer: (MultiBufferSource) -> MultiBufferSource = { it -> it } ) { - val rotation = if (Screen.hasShiftDown()) 0.0f else rotation - - // TODO: Figure out why this is here and whether removing it will break things -// entity.level = world - val ps = graphics.pose() - - ps.pushPose() - ps.translate(x.toDouble(), y.toDouble(), 50.0) - ps.scale(renderScale, renderScale, renderScale) - ps.translate(0.0, offset.toDouble(), 0.0) - ps.mulPose(Axis.ZP.rotationDegrees(180.0f)) - ps.mulPose(Axis.YP.rotationDegrees(rotation)) - val erd = Minecraft.getInstance().entityRenderDispatcher - val immediate = Minecraft.getInstance().renderBuffers().bufferSource() - erd.setRenderShadow(false) - erd.render(entity, 0.0, 0.0, 0.0, 0.0f, 1.0f, ps, bufferTransformer(immediate), 0xf000f0) - erd.setRenderShadow(true) - immediate.endBatch() - ps.popPose() + val rotation = if (Screen.hasShiftDown()) 0.0f else rotation + + // TODO: Figure out why this is here and whether removing it will break things + // entity.level = world + val ps = graphics.pose() + + ps.pushPose() + ps.translate(x.toDouble(), y.toDouble(), 50.0) + ps.scale(renderScale, renderScale, renderScale) + ps.translate(0.0, offset.toDouble(), 0.0) + ps.mulPose(Axis.ZP.rotationDegrees(180.0f)) + ps.mulPose(Axis.YP.rotationDegrees(rotation)) + val erd = Minecraft.getInstance().entityRenderDispatcher + val immediate = Minecraft.getInstance().renderBuffers().bufferSource() + erd.setRenderShadow(false) + erd.render(entity, 0.0, 0.0, 0.0, 0.0f, 1.0f, ps, bufferTransformer(immediate), 0xf000f0) + erd.setRenderShadow(true) + immediate.endBatch() + ps.popPose() } -/** - * Make sure you have the `PositionColorShader` set - */ -fun renderQuad( - ps: PoseStack, x: Float, y: Float, w: Float, h: Float, color: Int -) { - val mat = ps.last().pose() - val tess = Tesselator.getInstance() - val buf = tess.builder - buf.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR) - buf.vertex(mat, x, y, 0f) - .color(color) - .endVertex() - buf.vertex(mat, x, y + h, 0f) - .color(color) - .endVertex() - buf.vertex(mat, x + w, y + h, 0f) - .color(color) - .endVertex() - buf.vertex(mat, x + w, y, 0f) - .color(color) - .endVertex() - tess.end() +/** Make sure you have the `PositionColorShader` set */ +fun renderQuad(ps: PoseStack, x: Float, y: Float, w: Float, h: Float, color: Int) { + val mat = ps.last().pose() + val tess = Tesselator.getInstance() + val buf = tess.builder + buf.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR) + buf.vertex(mat, x, y, 0f).color(color).endVertex() + buf.vertex(mat, x, y + h, 0f).color(color).endVertex() + buf.vertex(mat, x + w, y + h, 0f).color(color).endVertex() + buf.vertex(mat, x + w, y, 0f).color(color).endVertex() + tess.end() } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/ScryingLensOverlays.java b/Common/src/main/java/at/petrak/hexcasting/client/render/ScryingLensOverlays.java index 32aed05c7a..d3a6cb5e4c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/ScryingLensOverlays.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/ScryingLensOverlays.java @@ -7,6 +7,7 @@ import at.petrak.hexcasting.common.blocks.akashic.BlockEntityAkashicBookshelf; import at.petrak.hexcasting.common.lib.HexBlocks; import com.mojang.datafixers.util.Pair; +import java.util.function.UnaryOperator; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -24,243 +25,260 @@ import net.minecraft.world.level.block.state.properties.RailShape; import net.minecraft.world.level.material.MapColor; -import java.util.function.UnaryOperator; - public class ScryingLensOverlays { - public static void addScryingLensStuff() { - ScryingLensOverlayRegistry.addPredicateDisplayer( - (state, pos, observer, world, direction) -> state.getBlock() instanceof BlockAbstractImpetus, - (lines, state, pos, observer, world, direction) -> { - if (world.getBlockEntity(pos) instanceof BlockEntityAbstractImpetus beai) { - beai.applyScryingLensOverlay(lines, state, pos, observer, world, direction); - } - }); - - ScryingLensOverlayRegistry.addDisplayer(Blocks.NOTE_BLOCK, - (lines, state, pos, observer, world, direction) -> { - int note = state.getValue(NoteBlock.NOTE); + public static void addScryingLensStuff() { + ScryingLensOverlayRegistry.addPredicateDisplayer( + (state, pos, observer, world, direction) -> + state.getBlock() instanceof BlockAbstractImpetus, + (lines, state, pos, observer, world, direction) -> { + if (world.getBlockEntity(pos) instanceof BlockEntityAbstractImpetus beai) { + beai.applyScryingLensOverlay(lines, state, pos, observer, world, direction); + } + }); - float rCol = Math.max(0.0F, Mth.sin((note / 24F + 0.0F) * Mth.TWO_PI) * 0.65F + 0.35F); - float gCol = Math.max(0.0F, Mth.sin((note / 24F + 0.33333334F) * Mth.TWO_PI) * 0.65F + 0.35F); - float bCol = Math.max(0.0F, Mth.sin((note / 24F + 0.6666667F) * Mth.TWO_PI) * 0.65F + 0.35F); + ScryingLensOverlayRegistry.addDisplayer( + Blocks.NOTE_BLOCK, + (lines, state, pos, observer, world, direction) -> { + int note = state.getValue(NoteBlock.NOTE); - int noteColor = 0xFF_000000 | Mth.color(rCol, gCol, bCol); + float rCol = Math.max(0.0F, Mth.sin((note / 24F + 0.0F) * Mth.TWO_PI) * 0.65F + 0.35F); + float gCol = + Math.max(0.0F, Mth.sin((note / 24F + 0.33333334F) * Mth.TWO_PI) * 0.65F + 0.35F); + float bCol = + Math.max(0.0F, Mth.sin((note / 24F + 0.6666667F) * Mth.TWO_PI) * 0.65F + 0.35F); - var instrument = state.getValue(NoteBlock.INSTRUMENT); + int noteColor = 0xFF_000000 | Mth.color(rCol, gCol, bCol); - lines.add(new Pair<>( - new ItemStack(Items.MUSIC_DISC_CHIRP), - Component.literal(String.valueOf(instrument.ordinal())) - .withStyle(color(instrumentColor(instrument))))); - lines.add(new Pair<>( - new ItemStack(Items.NOTE_BLOCK), - Component.literal(String.valueOf(note)) - .withStyle(color(noteColor)))); - }); + var instrument = state.getValue(NoteBlock.INSTRUMENT); - ScryingLensOverlayRegistry.addDisplayer(HexBlocks.AKASHIC_BOOKSHELF, - (lines, state, pos, observer, world, direction) -> { - if (world.getBlockEntity(pos) instanceof BlockEntityAkashicBookshelf tile) { - var iotaTag = tile.getIotaTag(); - if (iotaTag != null) { - var display = IotaType.getDisplay(iotaTag); - lines.add(new Pair<>(new ItemStack(Items.BOOK), display)); - } - } - }); + lines.add( + new Pair<>( + new ItemStack(Items.MUSIC_DISC_CHIRP), + Component.literal(String.valueOf(instrument.ordinal())) + .withStyle(color(instrumentColor(instrument))))); + lines.add( + new Pair<>( + new ItemStack(Items.NOTE_BLOCK), + Component.literal(String.valueOf(note)).withStyle(color(noteColor)))); + }); - ScryingLensOverlayRegistry.addDisplayer(Blocks.COMPARATOR, - (lines, state, pos, observer, world, direction) -> { - int comparatorValue = state.getAnalogOutputSignal(world, pos); - lines.add(new Pair<>( - new ItemStack(Items.REDSTONE), - Component.literal(comparatorValue == -1 ? "" : String.valueOf(comparatorValue)) - .withStyle(redstoneColor(comparatorValue)))); + ScryingLensOverlayRegistry.addDisplayer( + HexBlocks.AKASHIC_BOOKSHELF, + (lines, state, pos, observer, world, direction) -> { + if (world.getBlockEntity(pos) instanceof BlockEntityAkashicBookshelf tile) { + var iotaTag = tile.getIotaTag(); + if (iotaTag != null) { + var display = IotaType.getDisplay(iotaTag); + lines.add(new Pair<>(new ItemStack(Items.BOOK), display)); + } + } + }); - boolean compare = state.getValue(ComparatorBlock.MODE) == ComparatorMode.COMPARE; + ScryingLensOverlayRegistry.addDisplayer( + Blocks.COMPARATOR, + (lines, state, pos, observer, world, direction) -> { + int comparatorValue = state.getAnalogOutputSignal(world, pos); + lines.add( + new Pair<>( + new ItemStack(Items.REDSTONE), + Component.literal(comparatorValue == -1 ? "" : String.valueOf(comparatorValue)) + .withStyle(redstoneColor(comparatorValue)))); - lines.add(new Pair<>( - new ItemStack(Items.REDSTONE_TORCH), - Component.literal(compare ? ">=" : "-") - .withStyle(redstoneColor(compare ? 0 : 15)))); - }); + boolean compare = state.getValue(ComparatorBlock.MODE) == ComparatorMode.COMPARE; - ScryingLensOverlayRegistry.addDisplayer(Blocks.POWERED_RAIL, - (lines, state, pos, observer, world, direction) -> { - int power = getPoweredRailStrength(world, pos, state); - lines.add(new Pair<>( - new ItemStack(Items.POWERED_RAIL), - Component.literal(String.valueOf(power)) - .withStyle(redstoneColor(power, 9)))); - }); + lines.add( + new Pair<>( + new ItemStack(Items.REDSTONE_TORCH), + Component.literal(compare ? ">=" : "-") + .withStyle(redstoneColor(compare ? 0 : 15)))); + }); - ScryingLensOverlayRegistry.addDisplayer(Blocks.REPEATER, - (lines, state, pos, observer, world, direction) -> lines.add(new Pair<>( - new ItemStack(Items.CLOCK), - Component.literal(String.valueOf(state.getValue(RepeaterBlock.DELAY))) - .withStyle(ChatFormatting.YELLOW)))); + ScryingLensOverlayRegistry.addDisplayer( + Blocks.POWERED_RAIL, + (lines, state, pos, observer, world, direction) -> { + int power = getPoweredRailStrength(world, pos, state); + lines.add( + new Pair<>( + new ItemStack(Items.POWERED_RAIL), + Component.literal(String.valueOf(power)).withStyle(redstoneColor(power, 9)))); + }); - ScryingLensOverlayRegistry.addPredicateDisplayer( - (state, pos, observer, world, direction) -> state.isSignalSource() && !state.is( - Blocks.COMPARATOR), - (lines, state, pos, observer, world, direction) -> { - int signalStrength = 0; - if (state.getBlock() instanceof RedStoneWireBlock) { - signalStrength = state.getValue(RedStoneWireBlock.POWER); - } else { - for (Direction dir : Direction.values()) { - signalStrength = Math.max(signalStrength, state.getSignal(world, pos, dir)); - } - } + ScryingLensOverlayRegistry.addDisplayer( + Blocks.REPEATER, + (lines, state, pos, observer, world, direction) -> + lines.add( + new Pair<>( + new ItemStack(Items.CLOCK), + Component.literal(String.valueOf(state.getValue(RepeaterBlock.DELAY))) + .withStyle(ChatFormatting.YELLOW)))); - lines.add(0, new Pair<>( - new ItemStack(Items.REDSTONE), - Component.literal(String.valueOf(signalStrength)) - .withStyle(redstoneColor(signalStrength)))); - }); - } + ScryingLensOverlayRegistry.addPredicateDisplayer( + (state, pos, observer, world, direction) -> + state.isSignalSource() && !state.is(Blocks.COMPARATOR), + (lines, state, pos, observer, world, direction) -> { + int signalStrength = 0; + if (state.getBlock() instanceof RedStoneWireBlock) { + signalStrength = state.getValue(RedStoneWireBlock.POWER); + } else { + for (Direction dir : Direction.values()) { + signalStrength = Math.max(signalStrength, state.getSignal(world, pos, dir)); + } + } - private static int getPoweredRailStrength(Level level, BlockPos pos, BlockState state) { - if (level.hasNeighborSignal(pos)) - return 9; - int positiveValue = findPoweredRailSignal(level, pos, state, true, 0); - int negativeValue = findPoweredRailSignal(level, pos, state, false, 0); - return Math.max(positiveValue, negativeValue); - } + lines.add( + 0, + new Pair<>( + new ItemStack(Items.REDSTONE), + Component.literal(String.valueOf(signalStrength)) + .withStyle(redstoneColor(signalStrength)))); + }); + } - // Copypasta from PoweredRailBlock.class - private static int findPoweredRailSignal(Level level, BlockPos pos, BlockState state, boolean travelPositive, - int depth) { - if (depth >= 8) { - return 0; - } else { - int x = pos.getX(); - int y = pos.getY(); - int z = pos.getZ(); - boolean descending = true; - RailShape shape = state.getValue(PoweredRailBlock.SHAPE); - switch (shape) { - case NORTH_SOUTH: - if (travelPositive) { - ++z; - } else { - --z; - } - break; - case EAST_WEST: - if (travelPositive) { - --x; - } else { - ++x; - } - break; - case ASCENDING_EAST: - if (travelPositive) { - --x; - } else { - ++x; - ++y; - descending = false; - } + private static int getPoweredRailStrength(Level level, BlockPos pos, BlockState state) { + if (level.hasNeighborSignal(pos)) return 9; + int positiveValue = findPoweredRailSignal(level, pos, state, true, 0); + int negativeValue = findPoweredRailSignal(level, pos, state, false, 0); + return Math.max(positiveValue, negativeValue); + } - shape = RailShape.EAST_WEST; - break; - case ASCENDING_WEST: - if (travelPositive) { - --x; - ++y; - descending = false; - } else { - ++x; - } + // Copypasta from PoweredRailBlock.class + private static int findPoweredRailSignal( + Level level, BlockPos pos, BlockState state, boolean travelPositive, int depth) { + if (depth >= 8) { + return 0; + } else { + int x = pos.getX(); + int y = pos.getY(); + int z = pos.getZ(); + boolean descending = true; + RailShape shape = state.getValue(PoweredRailBlock.SHAPE); + switch (shape) { + case NORTH_SOUTH: + if (travelPositive) { + ++z; + } else { + --z; + } + break; + case EAST_WEST: + if (travelPositive) { + --x; + } else { + ++x; + } + break; + case ASCENDING_EAST: + if (travelPositive) { + --x; + } else { + ++x; + ++y; + descending = false; + } - shape = RailShape.EAST_WEST; - break; - case ASCENDING_NORTH: - if (travelPositive) { - ++z; - } else { - --z; - ++y; - descending = false; - } + shape = RailShape.EAST_WEST; + break; + case ASCENDING_WEST: + if (travelPositive) { + --x; + ++y; + descending = false; + } else { + ++x; + } - shape = RailShape.NORTH_SOUTH; - break; - case ASCENDING_SOUTH: - if (travelPositive) { - ++z; - ++y; - descending = false; - } else { - --z; - } + shape = RailShape.EAST_WEST; + break; + case ASCENDING_NORTH: + if (travelPositive) { + ++z; + } else { + --z; + ++y; + descending = false; + } - shape = RailShape.NORTH_SOUTH; - } + shape = RailShape.NORTH_SOUTH; + break; + case ASCENDING_SOUTH: + if (travelPositive) { + ++z; + ++y; + descending = false; + } else { + --z; + } - int power = getPowerFromRail(level, new BlockPos(x, y, z), travelPositive, depth, - shape); + shape = RailShape.NORTH_SOUTH; + } - if (power > 0) { - return power; - } else if (descending) { - return getPowerFromRail(level, new BlockPos(x, y - 1, z), travelPositive, depth, - shape); - } else { - return 0; - } - } - } + int power = getPowerFromRail(level, new BlockPos(x, y, z), travelPositive, depth, shape); + if (power > 0) { + return power; + } else if (descending) { + return getPowerFromRail(level, new BlockPos(x, y - 1, z), travelPositive, depth, shape); + } else { + return 0; + } + } + } - private static UnaryOperator