From 7b976917ff337822e9545a19a8d2e8adfae7d269 Mon Sep 17 00:00:00 2001 From: leawind Date: Sun, 8 Dec 2024 16:47:09 +0800 Subject: [PATCH] port from 1.20.1 --- .editorconfig | 691 ------------------ CONTRIBUTING.md | 37 +- LICENSE.txt | 4 +- README-ZH.md | 52 ++ README.md | 57 +- build.gradle | 315 +++++++- changelog.md | 9 + changelog_latest.md | 5 - common/build.gradle | 30 +- .../leawind/thirdperson/ThirdPerson.java | 62 ++ .../thirdperson/ThirdPersonConstants.java | 91 +++ .../thirdperson/ThirdPersonEvents.java | 346 +++++++++ .../leawind/thirdperson/ThirdPersonKeys.java | 85 +++ .../thirdperson/ThirdPersonResources.java | 15 + .../thirdperson/ThirdPersonStatus.java | 156 ++++ .../thirdperson/api/base/GameEvents.java | 20 + .../thirdperson/api/base/ModEvent.java | 5 + .../event/CalculateMoveImpulseEvent.java | 22 + .../client/event/EntityTurnStartEvent.java | 33 + .../event/MouseTurnPlayerStartEvent.java | 31 + .../api/client/event/RenderEntityEvent.java | 13 + .../client/event/RenderTickStartEvent.java | 16 + .../event/ThirdPersonCameraSetupEvent.java | 31 + .../thirdperson/config/AbstractConfig.java | 133 ++++ .../leawind/thirdperson/config/Config.java | 106 +++ .../thirdperson/config/ConfigManager.java | 136 ++++ .../core/AimingTargetComparator.java | 35 + .../leawind/thirdperson/core/CameraAgent.java | 687 +++++++++++++++++ .../leawind/thirdperson/core/EntityAgent.java | 435 +++++++++++ .../AbstractCameraOffsetMode.java | 92 +++ .../cameraoffset/CameraOffsetModeAiming.java | 88 +++ .../cameraoffset/CameraOffsetModeNormal.java | 85 +++ .../core/cameraoffset/CameraOffsetScheme.java | 89 +++ .../core/rotation/RotateStrategy.java | 153 ++++ .../core/rotation/RotateTargetEnum.java | 152 ++++ .../core/rotation/SmoothTypeEnum.java | 77 ++ .../thirdperson/mixin/CameraInvoker.java | 27 + .../thirdperson/mixin/CameraTypeMixin.java | 54 ++ .../thirdperson/mixin/ClientLevelInvoker.java | 7 +- .../thirdperson/mixin/EntityMixin.java | 93 +++ .../mixin/GameRendererInvoker.java | 12 + .../thirdperson/mixin/GameRendererMixin.java | 126 ++++ .../leawind/thirdperson/mixin/GuiMixin.java | 17 + .../thirdperson/mixin/KeyboardInputMixin.java | 31 + .../thirdperson/mixin/LevelRendererMixin.java | 58 ++ .../thirdperson/mixin/LocalPlayerMixin.java | 25 + .../thirdperson/mixin/MinecraftMixin.java | 20 + .../thirdperson/mixin/ModelPartCubeMixin.java | 23 + .../thirdperson/mixin/MouseHandlerMixin.java | 35 + .../thirdperson/mixin/RenderTypeInvoker.java | 16 + .../thirdperson/mixin/RenderTypeMixin.java | 87 +++ .../resources/ItemPredicateManager.java | 160 ++++ .../screen/ClothConfigScreenBuilder.java | 599 +++++++++++++++ .../screen/ConfigScreenBuilder.java | 86 +++ .../thirdperson/util/FiniteChecker.java | 94 +++ .../thirdperson/util/ItemPredicateUtil.java | 224 ++++++ .../thirdperson/util/PossibleSupplier.java | 54 ++ .../thirdperson/util/Surroundings.java | 221 ++++++ .../util/annotation/VersionSensitive.java | 23 + .../leawind/thirdperson/util/math/LMath.java | 262 +++++++ .../leawind/thirdperson/util/math/Zone.java | 284 +++++++ .../util/math/decisionmap/DecisionFactor.java | 47 ++ .../util/math/decisionmap/DecisionMap.java | 181 +++++ .../math/decisionmap/DecisionMapBuilder.java | 243 ++++++ .../util/math/monolist/DeferedMonoList.java | 106 +++ .../util/math/monolist/MonoList.java | 93 +++ .../util/math/monolist/StaticMonoList.java | 139 ++++ .../math/smoothvalue/ExpRotSmoothDouble.java | 77 ++ .../math/smoothvalue/ExpSmoothDouble.java | 89 +++ .../math/smoothvalue/ExpSmoothRotation.java | 113 +++ .../util/math/smoothvalue/ExpSmoothValue.java | 100 +++ .../math/smoothvalue/ExpSmoothVector2d.java | 97 +++ .../math/smoothvalue/ExpSmoothVector3d.java | 100 +++ .../util/math/smoothvalue/ISmoothValue.java | 52 ++ .../util/modkeymapping/ModKeyMapping.java | 125 ++++ .../util/modkeymapping/ModKeyMappingImpl.java | 149 ++++ .../mc/thirdperson/ExpectPlatformExample.java | 13 - .../leawind/mc/thirdperson/ThirdPerson.java | 58 -- .../mc/thirdperson/ThirdPersonConstants.java | 57 -- .../mc/thirdperson/ThirdPersonEvents.java | 302 -------- .../mc/thirdperson/ThirdPersonKeys.java | 65 -- .../mc/thirdperson/ThirdPersonResources.java | 23 - .../mc/thirdperson/ThirdPersonStatus.java | 106 --- .../api/cameraoffset/CameraOffsetMode.java | 92 --- .../api/cameraoffset/CameraOffsetScheme.java | 69 -- .../api/config/AbstractConfig.java | 73 -- .../mc/thirdperson/api/config/Config.java | 99 --- .../thirdperson/api/config/ConfigManager.java | 77 -- .../mc/thirdperson/api/core/CameraAgent.java | 158 ---- .../mc/thirdperson/api/core/EntityAgent.java | 160 ---- .../api/core/rotation/SmoothType.java | 50 -- .../api/screen/ConfigScreenBuilder.java | 24 - .../AbstractCameraOffsetMode.java | 32 - .../cameraoffset/CameraOffsetModeAiming.java | 84 --- .../cameraoffset/CameraOffsetModeNormal.java | 83 --- .../cameraoffset/CameraOffsetSchemeImpl.java | 66 -- .../thirdperson/impl/config/ConfigImpl.java | 76 -- .../impl/config/ConfigManagerImpl.java | 105 --- .../impl/core/AimingTargetComparator.java | 34 - .../impl/core/CameraAgentImpl.java | 378 ---------- .../impl/core/EntityAgentImpl.java | 331 --------- .../impl/core/rotation/RotateStrategy.java | 82 --- .../impl/core/rotation/RotateTarget.java | 103 --- .../impl/screen/ClothConfigScreenBuilder.java | 161 ---- .../impl/screen/ConfigScreenBuilders.java | 62 -- .../impl/screen/YaclConfigScreenBuilder.java | 15 - .../mc/thirdperson/mixin/CameraInvoker.java | 27 - .../mc/thirdperson/mixin/CameraMixin.java | 37 - .../mc/thirdperson/mixin/EntityMixin.java | 96 --- .../thirdperson/mixin/GameRendererMixin.java | 90 --- .../mc/thirdperson/mixin/GuiMixin.java | 23 - .../thirdperson/mixin/KeyboardInputMixin.java | 56 -- .../thirdperson/mixin/LevelRendererMixin.java | 37 - .../thirdperson/mixin/LocalPlayerInvoker.java | 11 - .../mc/thirdperson/mixin/MinecraftMixin.java | 27 - .../thirdperson/mixin/ModelPartCubeMixin.java | 17 - .../thirdperson/mixin/MouseHandlerMixin.java | 57 -- .../mc/thirdperson/mixin/RenderTypeMixin.java | 49 -- .../resources/ItemPatternManager.java | 96 --- .../net/leawind/mc/util/OptionalFunction.java | 24 - .../mc/util/annotations/VersionSensitive.java | 19 - .../mc/util/itempattern/ItemPattern.java | 234 ------ .../mc/util/itempattern/ItemPatternImpl.java | 81 -- .../java/net/leawind/mc/util/math/LMath.java | 177 ----- .../math/decisionmap/api/DecisionFactor.java | 50 -- .../math/decisionmap/api/DecisionMap.java | 119 --- .../decisionmap/api/anno/ADecisionFactor.java | 34 - .../decisionmap/impl/DecisionFactorImpl.java | 49 -- .../decisionmap/impl/DecisionMapImpl.java | 285 -------- .../util/math/monolist/DeferedMonoList.java | 108 --- .../mc/util/math/monolist/MonoList.java | 96 --- .../mc/util/math/monolist/StaticMonoList.java | 135 ---- .../math/smoothvalue/ExpRotSmoothDouble.java | 78 -- .../math/smoothvalue/ExpSmoothDouble.java | 89 --- .../math/smoothvalue/ExpSmoothRotation.java | 114 --- .../util/math/smoothvalue/ExpSmoothValue.java | 95 --- .../math/smoothvalue/ExpSmoothVector2d.java | 87 --- .../math/smoothvalue/ExpSmoothVector3d.java | 90 --- .../util/math/smoothvalue/ISmoothValue.java | 52 -- .../mc/util/math/vector/api/Vector2d.java | 161 ---- .../mc/util/math/vector/api/Vector3d.java | 176 ----- .../util/math/vector/impl/Vector2dImpl.java | 356 --------- .../util/math/vector/impl/Vector3dImpl.java | 402 ---------- .../mc/util/modkeymapping/ModKeyMapping.java | 144 ---- .../util/modkeymapping/ModKeyMappingImpl.java | 146 ---- .../item_patterns/hold_to_aim/vanilla.json | 11 + .../assets/minecraft/lang/en_us.json | 103 ++- .../assets/minecraft/lang/zh_cn.json | 139 ++-- .../leawind_third_person-common.mixins.json | 8 +- .../leawind_third_person.accesswidener | 2 +- .../decisionmap/test/DecisionMapTest.java | 93 +++ fabric/build.gradle | 74 +- .../thirdperson/fabric/ModMenuEntry.java | 14 + .../thirdperson/fabric/ThirdPersonFabric.java | 11 + .../thirdperson/fabric/mixin/CameraMixin.java | 49 ++ .../fabric/ExpectPlatformExampleImpl.java | 8 - .../mc/thirdperson/fabric/ModMenuEntry.java | 17 - .../thirdperson/fabric/ThirdPersonFabric.java | 12 - .../fabric/ThirdPersonResourcesImpl.java | 21 - .../IdentifiableItemPatternManager.java | 14 - fabric/src/main/resources/fabric.mod.json | 25 +- .../leawind_third_person.mixins.json | 6 +- fabric/src/main/resources/pack.mcmeta | 6 - forge/build.gradle | 57 +- .../forge/ThirdPersonEventsForge.java | 29 + .../thirdperson/forge/ThirdPersonForge.java | 27 + .../forge/ExpectPlatformExampleImpl.java | 8 - .../thirdperson/forge/ThirdPersonForge.java | 27 - .../forge/ThirdPersonResourcesImpl.java | 22 - forge/src/main/resources/META-INF/mods.toml | 22 +- .../leawind_third_person.mixins.json | 2 +- forge/src/main/resources/pack.mcmeta | 10 +- gradle.properties | 94 ++- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle | 4 +- 175 files changed, 8498 insertions(+), 8224 deletions(-) delete mode 100644 .editorconfig create mode 100644 README-ZH.md create mode 100644 changelog.md delete mode 100644 changelog_latest.md create mode 100644 common/src/main/java/com/github/leawind/thirdperson/ThirdPerson.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/ThirdPersonConstants.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/ThirdPersonEvents.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/ThirdPersonKeys.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/ThirdPersonResources.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/ThirdPersonStatus.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/api/base/GameEvents.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/api/base/ModEvent.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/api/client/event/CalculateMoveImpulseEvent.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/api/client/event/EntityTurnStartEvent.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/api/client/event/MouseTurnPlayerStartEvent.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/api/client/event/RenderEntityEvent.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/api/client/event/RenderTickStartEvent.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/api/client/event/ThirdPersonCameraSetupEvent.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/config/AbstractConfig.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/config/Config.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/config/ConfigManager.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/core/AimingTargetComparator.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/core/CameraAgent.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/core/EntityAgent.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/AbstractCameraOffsetMode.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/CameraOffsetModeAiming.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/CameraOffsetModeNormal.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/CameraOffsetScheme.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/core/rotation/RotateStrategy.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/core/rotation/RotateTargetEnum.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/core/rotation/SmoothTypeEnum.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/CameraInvoker.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/CameraTypeMixin.java rename common/src/main/java/{net/leawind/mc => com/github/leawind}/thirdperson/mixin/ClientLevelInvoker.java (71%) create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/EntityMixin.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/GameRendererInvoker.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/GameRendererMixin.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/GuiMixin.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/KeyboardInputMixin.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/LevelRendererMixin.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/LocalPlayerMixin.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/MinecraftMixin.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/ModelPartCubeMixin.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/MouseHandlerMixin.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/RenderTypeInvoker.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/mixin/RenderTypeMixin.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/resources/ItemPredicateManager.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/screen/ClothConfigScreenBuilder.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/screen/ConfigScreenBuilder.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/FiniteChecker.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/ItemPredicateUtil.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/PossibleSupplier.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/Surroundings.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/annotation/VersionSensitive.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/LMath.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/Zone.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/decisionmap/DecisionFactor.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/decisionmap/DecisionMap.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/decisionmap/DecisionMapBuilder.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/monolist/DeferedMonoList.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/monolist/MonoList.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/monolist/StaticMonoList.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpRotSmoothDouble.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothDouble.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothRotation.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothValue.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothVector2d.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothVector3d.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ISmoothValue.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/modkeymapping/ModKeyMapping.java create mode 100644 common/src/main/java/com/github/leawind/thirdperson/util/modkeymapping/ModKeyMappingImpl.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/ExpectPlatformExample.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/ThirdPerson.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonConstants.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonEvents.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonKeys.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonResources.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonStatus.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/api/cameraoffset/CameraOffsetMode.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/api/cameraoffset/CameraOffsetScheme.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/api/config/AbstractConfig.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/api/config/Config.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/api/config/ConfigManager.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/api/core/CameraAgent.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/api/core/EntityAgent.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/api/core/rotation/SmoothType.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/api/screen/ConfigScreenBuilder.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/AbstractCameraOffsetMode.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/CameraOffsetModeAiming.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/CameraOffsetModeNormal.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/CameraOffsetSchemeImpl.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/config/ConfigImpl.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/config/ConfigManagerImpl.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/core/AimingTargetComparator.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/core/CameraAgentImpl.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/core/EntityAgentImpl.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/core/rotation/RotateStrategy.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/core/rotation/RotateTarget.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/screen/ClothConfigScreenBuilder.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/screen/ConfigScreenBuilders.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/impl/screen/YaclConfigScreenBuilder.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/mixin/CameraInvoker.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/mixin/CameraMixin.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/mixin/EntityMixin.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/mixin/GameRendererMixin.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/mixin/GuiMixin.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/mixin/KeyboardInputMixin.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/mixin/LevelRendererMixin.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/mixin/LocalPlayerInvoker.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/mixin/MinecraftMixin.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/mixin/ModelPartCubeMixin.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/mixin/MouseHandlerMixin.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/mixin/RenderTypeMixin.java delete mode 100644 common/src/main/java/net/leawind/mc/thirdperson/resources/ItemPatternManager.java delete mode 100644 common/src/main/java/net/leawind/mc/util/OptionalFunction.java delete mode 100644 common/src/main/java/net/leawind/mc/util/annotations/VersionSensitive.java delete mode 100644 common/src/main/java/net/leawind/mc/util/itempattern/ItemPattern.java delete mode 100644 common/src/main/java/net/leawind/mc/util/itempattern/ItemPatternImpl.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/LMath.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/decisionmap/api/DecisionFactor.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/decisionmap/api/DecisionMap.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/decisionmap/api/anno/ADecisionFactor.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/decisionmap/impl/DecisionFactorImpl.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/decisionmap/impl/DecisionMapImpl.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/monolist/DeferedMonoList.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/monolist/MonoList.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/monolist/StaticMonoList.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpRotSmoothDouble.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothDouble.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothRotation.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothValue.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothVector2d.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothVector3d.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/smoothvalue/ISmoothValue.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/vector/api/Vector2d.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/vector/api/Vector3d.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/vector/impl/Vector2dImpl.java delete mode 100644 common/src/main/java/net/leawind/mc/util/math/vector/impl/Vector3dImpl.java delete mode 100644 common/src/main/java/net/leawind/mc/util/modkeymapping/ModKeyMapping.java delete mode 100644 common/src/main/java/net/leawind/mc/util/modkeymapping/ModKeyMappingImpl.java create mode 100644 common/src/test/java/com/github/leawind/thirdperson/util/math/decisionmap/test/DecisionMapTest.java create mode 100644 fabric/src/main/java/com/github/leawind/thirdperson/fabric/ModMenuEntry.java create mode 100644 fabric/src/main/java/com/github/leawind/thirdperson/fabric/ThirdPersonFabric.java create mode 100644 fabric/src/main/java/com/github/leawind/thirdperson/fabric/mixin/CameraMixin.java delete mode 100644 fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ExpectPlatformExampleImpl.java delete mode 100644 fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ModMenuEntry.java delete mode 100644 fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ThirdPersonFabric.java delete mode 100644 fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ThirdPersonResourcesImpl.java delete mode 100644 fabric/src/main/java/net/leawind/mc/thirdperson/fabric/resources/IdentifiableItemPatternManager.java delete mode 100644 fabric/src/main/resources/pack.mcmeta create mode 100644 forge/src/main/java/com/github/leawind/thirdperson/forge/ThirdPersonEventsForge.java create mode 100644 forge/src/main/java/com/github/leawind/thirdperson/forge/ThirdPersonForge.java delete mode 100644 forge/src/main/java/net/leawind/mc/thirdperson/forge/ExpectPlatformExampleImpl.java delete mode 100644 forge/src/main/java/net/leawind/mc/thirdperson/forge/ThirdPersonForge.java delete mode 100644 forge/src/main/java/net/leawind/mc/thirdperson/forge/ThirdPersonResourcesImpl.java diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 284892c1..00000000 --- a/.editorconfig +++ /dev/null @@ -1,691 +0,0 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_size = 4 -indent_style = tab -insert_final_newline = true -max_line_length = 256 -tab_width = 4 -ij_continuation_indent_size = 4 -ij_formatter_off_tag = @formatter:off -ij_formatter_on_tag = @formatter : on -ij_formatter_tags_enabled = true -ij_smart_tabs = true -ij_visual_guides = none -ij_wrap_on_typing = false - - -[*.java] -ij_continuation_indent_size = 4 -ij_smart_tabs = false -ij_visual_guides = 64 -ij_java_align_consecutive_assignments = true -ij_java_align_consecutive_variable_declarations = true -ij_java_align_group_field_declarations = true -ij_java_align_multiline_annotation_parameters = true -ij_java_align_multiline_array_initializer_expression = true -ij_java_align_multiline_assignment = true -ij_java_align_multiline_binary_operation = true -ij_java_align_multiline_chained_methods = true -ij_java_align_multiline_deconstruction_list_components = true -ij_java_align_multiline_extends_list = true -ij_java_align_multiline_for = false -ij_java_align_multiline_method_parentheses = true -ij_java_align_multiline_parameters = true -ij_java_align_multiline_parameters_in_calls = true -ij_java_align_multiline_parenthesized_expression = true -ij_java_align_multiline_records = true -ij_java_align_multiline_resources = true -ij_java_align_multiline_ternary_operation = true -ij_java_align_multiline_text_blocks = false -ij_java_align_multiline_throws_list = true -ij_java_align_subsequent_simple_methods = true -ij_java_align_throws_keyword = false -ij_java_align_types_in_multi_catch = true -ij_java_annotation_parameter_wrap = normal -ij_java_array_initializer_new_line_after_left_brace = false -ij_java_array_initializer_right_brace_on_new_line = false -ij_java_array_initializer_wrap = normal -ij_java_assert_statement_colon_on_next_line = false -ij_java_assert_statement_wrap = on_every_item -ij_java_assignment_wrap = off -ij_java_binary_operation_sign_on_next_line = false -ij_java_binary_operation_wrap = normal -ij_java_blank_lines_after_anonymous_class_header = 0 -ij_java_blank_lines_after_class_header = 0 -ij_java_blank_lines_after_imports = 1 -ij_java_blank_lines_after_package = 2 -ij_java_blank_lines_around_class = 1 -ij_java_blank_lines_around_field = 0 -ij_java_blank_lines_around_field_in_interface = 0 -ij_java_blank_lines_around_initializer = 1 -ij_java_blank_lines_around_method = 1 -ij_java_blank_lines_around_method_in_interface = 1 -ij_java_blank_lines_before_class_end = 0 -ij_java_blank_lines_before_imports = 1 -ij_java_blank_lines_before_method_body = 0 -ij_java_blank_lines_before_package = 0 -ij_java_block_brace_style = end_of_line -ij_java_block_comment_add_space = false -ij_java_block_comment_at_first_column = true -ij_java_builder_methods = none -ij_java_call_parameters_new_line_after_left_paren = false -ij_java_call_parameters_right_paren_on_new_line = false -ij_java_call_parameters_wrap = on_every_item -ij_java_case_statement_on_separate_line = true -ij_java_catch_on_new_line = false -ij_java_class_annotation_wrap = split_into_lines -ij_java_class_brace_style = end_of_line -ij_java_class_count_to_use_import_on_demand = 5 -ij_java_class_names_in_javadoc = 1 -ij_java_deconstruction_list_wrap = normal -ij_java_do_not_indent_top_level_class_members = false -ij_java_do_not_wrap_after_single_annotation = false -ij_java_do_not_wrap_after_single_annotation_in_parameter = false -ij_java_do_while_brace_force = always -ij_java_doc_add_blank_line_after_description = true -ij_java_doc_add_blank_line_after_param_comments = false -ij_java_doc_add_blank_line_after_return = false -ij_java_doc_add_p_tag_on_empty_lines = true -ij_java_doc_align_exception_comments = true -ij_java_doc_align_param_comments = true -ij_java_doc_do_not_wrap_if_one_line = false -ij_java_doc_enable_formatting = true -ij_java_doc_enable_leading_asterisks = true -ij_java_doc_indent_on_continuation = false -ij_java_doc_keep_empty_lines = true -ij_java_doc_keep_empty_parameter_tag = true -ij_java_doc_keep_empty_return_tag = true -ij_java_doc_keep_empty_throws_tag = true -ij_java_doc_keep_invalid_tags = true -ij_java_doc_param_description_on_new_line = false -ij_java_doc_preserve_line_breaks = false -ij_java_doc_use_throws_not_exception_tag = true -ij_java_else_on_new_line = false -ij_java_enum_constants_wrap = split_into_lines -ij_java_extends_keyword_wrap = normal -ij_java_extends_list_wrap = on_every_item -ij_java_field_annotation_wrap = off -ij_java_finally_on_new_line = false -ij_java_for_brace_force = always -ij_java_for_statement_new_line_after_left_paren = false -ij_java_for_statement_right_paren_on_new_line = false -ij_java_for_statement_wrap = off -ij_java_generate_final_locals = false -ij_java_generate_final_parameters = false -ij_java_if_brace_force = always -ij_java_imports_layout = *, |, javax.**, java.**, |, $* -ij_java_indent_case_from_switch = true -ij_java_insert_inner_class_imports = false -ij_java_insert_override_annotation = true -ij_java_keep_blank_lines_before_right_brace = 0 -ij_java_keep_blank_lines_between_package_declaration_and_header = 0 -ij_java_keep_blank_lines_in_code = 0 -ij_java_keep_blank_lines_in_declarations = 0 -ij_java_keep_builder_methods_indents = false -ij_java_keep_control_statement_in_one_line = false -ij_java_keep_first_column_comment = false -ij_java_keep_indents_on_empty_lines = false -ij_java_keep_line_breaks = false -ij_java_keep_multiple_expressions_in_one_line = false -ij_java_keep_simple_blocks_in_one_line = false -ij_java_keep_simple_classes_in_one_line = false -ij_java_keep_simple_lambdas_in_one_line = false -ij_java_keep_simple_methods_in_one_line = false -ij_java_label_indent_absolute = false -ij_java_label_indent_size = 0 -ij_java_lambda_brace_style = end_of_line -ij_java_layout_static_imports_separately = true -ij_java_line_comment_add_space = false -ij_java_line_comment_add_space_on_reformat = false -ij_java_line_comment_at_first_column = true -ij_java_method_annotation_wrap = split_into_lines -ij_java_method_brace_style = end_of_line -ij_java_method_call_chain_wrap = on_every_item -ij_java_method_parameters_new_line_after_left_paren = false -ij_java_method_parameters_right_paren_on_new_line = false -ij_java_method_parameters_wrap = on_every_item -ij_java_modifier_list_wrap = false -ij_java_multi_catch_types_wrap = on_every_item -ij_java_names_count_to_use_import_on_demand = 3 -ij_java_new_line_after_lparen_in_annotation = false -ij_java_new_line_after_lparen_in_deconstruction_pattern = true -ij_java_new_line_after_lparen_in_record_header = false -ij_java_packages_to_use_import_on_demand = java.awt.*, javax.swing.* -ij_java_parameter_annotation_wrap = split_into_lines -ij_java_parentheses_expression_new_line_after_left_paren = false -ij_java_parentheses_expression_right_paren_on_new_line = false -ij_java_place_assignment_sign_on_next_line = false -ij_java_prefer_longer_names = true -ij_java_prefer_parameters_wrap = true -ij_java_record_components_wrap = normal -ij_java_repeat_synchronized = true -ij_java_replace_instanceof_and_cast = false -ij_java_replace_null_check = true -ij_java_replace_sum_lambda_with_method_ref = true -ij_java_resource_list_new_line_after_left_paren = false -ij_java_resource_list_right_paren_on_new_line = true -ij_java_resource_list_wrap = on_every_item -ij_java_rparen_on_new_line_in_annotation = false -ij_java_rparen_on_new_line_in_deconstruction_pattern = true -ij_java_rparen_on_new_line_in_record_header = false -ij_java_space_after_closing_angle_bracket_in_type_argument = false -ij_java_space_after_colon = true -ij_java_space_after_comma = true -ij_java_space_after_comma_in_type_arguments = true -ij_java_space_after_for_semicolon = true -ij_java_space_after_quest = true -ij_java_space_after_type_cast = false -ij_java_space_before_annotation_array_initializer_left_brace = false -ij_java_space_before_annotation_parameter_list = false -ij_java_space_before_array_initializer_left_brace = false -ij_java_space_before_catch_keyword = true -ij_java_space_before_catch_left_brace = true -ij_java_space_before_catch_parentheses = true -ij_java_space_before_class_left_brace = true -ij_java_space_before_colon = false -ij_java_space_before_colon_in_foreach = false -ij_java_space_before_comma = false -ij_java_space_before_deconstruction_list = false -ij_java_space_before_do_left_brace = true -ij_java_space_before_else_keyword = true -ij_java_space_before_else_left_brace = true -ij_java_space_before_finally_keyword = true -ij_java_space_before_finally_left_brace = true -ij_java_space_before_for_left_brace = true -ij_java_space_before_for_parentheses = true -ij_java_space_before_for_semicolon = false -ij_java_space_before_if_left_brace = true -ij_java_space_before_if_parentheses = true -ij_java_space_before_method_call_parentheses = false -ij_java_space_before_method_left_brace = true -ij_java_space_before_method_parentheses = true -ij_java_space_before_opening_angle_bracket_in_type_parameter = false -ij_java_space_before_quest = true -ij_java_space_before_switch_left_brace = true -ij_java_space_before_switch_parentheses = true -ij_java_space_before_synchronized_left_brace = true -ij_java_space_before_synchronized_parentheses = true -ij_java_space_before_try_left_brace = true -ij_java_space_before_try_parentheses = true -ij_java_space_before_type_parameter_list = false -ij_java_space_before_while_keyword = true -ij_java_space_before_while_left_brace = true -ij_java_space_before_while_parentheses = true -ij_java_space_inside_one_line_enum_braces = false -ij_java_space_within_empty_array_initializer_braces = false -ij_java_space_within_empty_method_call_parentheses = false -ij_java_space_within_empty_method_parentheses = false -ij_java_spaces_around_additive_operators = true -ij_java_spaces_around_annotation_eq = false -ij_java_spaces_around_assignment_operators = true -ij_java_spaces_around_bitwise_operators = true -ij_java_spaces_around_equality_operators = true -ij_java_spaces_around_lambda_arrow = true -ij_java_spaces_around_logical_operators = true -ij_java_spaces_around_method_ref_dbl_colon = false -ij_java_spaces_around_multiplicative_operators = true -ij_java_spaces_around_relational_operators = true -ij_java_spaces_around_shift_operators = true -ij_java_spaces_around_type_bounds_in_type_parameters = true -ij_java_spaces_around_unary_operator = false -ij_java_spaces_within_angle_brackets = false -ij_java_spaces_within_annotation_parentheses = false -ij_java_spaces_within_array_initializer_braces = false -ij_java_spaces_within_braces = false -ij_java_spaces_within_brackets = false -ij_java_spaces_within_cast_parentheses = false -ij_java_spaces_within_catch_parentheses = false -ij_java_spaces_within_deconstruction_list = false -ij_java_spaces_within_for_parentheses = false -ij_java_spaces_within_if_parentheses = false -ij_java_spaces_within_method_call_parentheses = false -ij_java_spaces_within_method_parentheses = false -ij_java_spaces_within_parentheses = false -ij_java_spaces_within_record_header = false -ij_java_spaces_within_switch_parentheses = false -ij_java_spaces_within_synchronized_parentheses = false -ij_java_spaces_within_try_parentheses = false -ij_java_spaces_within_while_parentheses = false -ij_java_special_else_if_treatment = true -ij_java_subclass_name_suffix = Impl -ij_java_ternary_operation_signs_on_next_line = true -ij_java_ternary_operation_wrap = on_every_item -ij_java_test_name_suffix = Test -ij_java_throws_keyword_wrap = normal -ij_java_throws_list_wrap = normal -ij_java_use_external_annotations = false -ij_java_use_fq_class_names = false -ij_java_use_relative_indents = false -ij_java_use_single_class_imports = true -ij_java_variable_annotation_wrap = split_into_lines -ij_java_visibility = public -ij_java_while_brace_force = always -ij_java_while_on_new_line = false -ij_java_wrap_comments = true -ij_java_wrap_first_method_in_call_chain = false -ij_java_wrap_long_lines = true - -[*ConfigScreenBuilder.java] -max_line_length = 1048576 - -[*.nbtt] -max_line_length = 150 -ij_continuation_indent_size = 4 -ij_smart_tabs = false -ij_nbtt_keep_indents_on_empty_lines = false -ij_nbtt_space_after_colon = true -ij_nbtt_space_after_comma = true -ij_nbtt_space_before_colon = true -ij_nbtt_space_before_comma = false -ij_nbtt_spaces_within_brackets = false -ij_nbtt_spaces_within_parentheses = false - -[*.properties] -ij_properties_align_group_field_declarations = true -ij_properties_keep_blank_lines = false -ij_properties_key_value_delimiter = equals -ij_properties_spaces_around_key_value_delimiter = true - -[*.yml] -indent_size = 2 -ij_yaml_align_values_properties = do_not_align -ij_yaml_autoinsert_sequence_marker = true -ij_yaml_block_mapping_on_new_line = false -ij_yaml_indent_sequence_value = true -ij_yaml_keep_indents_on_empty_lines = false -ij_yaml_keep_line_breaks = true -ij_yaml_sequence_on_new_line = false -ij_yaml_space_before_colon = false -ij_yaml_spaces_within_braces = true -ij_yaml_spaces_within_brackets = true - -[.editorconfig] -ij_editorconfig_align_group_field_declarations = true -ij_editorconfig_space_after_colon = true -ij_editorconfig_space_after_comma = true -ij_editorconfig_space_before_colon = true -ij_editorconfig_space_before_comma = false -ij_editorconfig_spaces_around_assignment_operators = true - -[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.jspx,*.pom,*.rng,*.tagx,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] -ij_smart_tabs = false -ij_xml_align_attributes = true -ij_xml_align_text = false -ij_xml_attribute_wrap = normal -ij_xml_block_comment_add_space = false -ij_xml_block_comment_at_first_column = true -ij_xml_keep_blank_lines = 2 -ij_xml_keep_indents_on_empty_lines = false -ij_xml_keep_line_breaks = true -ij_xml_keep_line_breaks_in_text = true -ij_xml_keep_whitespaces = false -ij_xml_keep_whitespaces_around_cdata = preserve -ij_xml_keep_whitespaces_inside_cdata = false -ij_xml_line_comment_at_first_column = true -ij_xml_space_after_tag_name = false -ij_xml_space_around_equals_in_attribute = false -ij_xml_space_inside_empty_tag = false -ij_xml_text_wrap = normal - -[{*.bash,*.sh,*.zsh}] -indent_size = 2 -indent_style = space -tab_width = 2 -ij_shell_binary_ops_start_line = false -ij_shell_keep_column_alignment_padding = false -ij_shell_minify_program = false -ij_shell_redirect_followed_by_space = false -ij_shell_switch_cases_indented = false -ij_shell_use_unix_line_separator = true - -[{*.gant,*.groovy,*.gy}] -end_of_line = lf -ij_smart_tabs = false -ij_groovy_align_group_field_declarations = false -ij_groovy_align_multiline_array_initializer_expression = true -ij_groovy_align_multiline_assignment = false -ij_groovy_align_multiline_binary_operation = false -ij_groovy_align_multiline_chained_methods = true -ij_groovy_align_multiline_extends_list = true -ij_groovy_align_multiline_for = true -ij_groovy_align_multiline_list_or_map = true -ij_groovy_align_multiline_method_parentheses = false -ij_groovy_align_multiline_parameters = true -ij_groovy_align_multiline_parameters_in_calls = true -ij_groovy_align_multiline_resources = true -ij_groovy_align_multiline_ternary_operation = true -ij_groovy_align_multiline_throws_list = true -ij_groovy_align_named_args_in_map = true -ij_groovy_align_throws_keyword = false -ij_groovy_array_initializer_new_line_after_left_brace = false -ij_groovy_array_initializer_right_brace_on_new_line = false -ij_groovy_array_initializer_wrap = normal -ij_groovy_assert_statement_wrap = off -ij_groovy_assignment_wrap = off -ij_groovy_binary_operation_wrap = off -ij_groovy_blank_lines_after_class_header = 0 -ij_groovy_blank_lines_after_imports = 1 -ij_groovy_blank_lines_after_package = 1 -ij_groovy_blank_lines_around_class = 1 -ij_groovy_blank_lines_around_field = 0 -ij_groovy_blank_lines_around_field_in_interface = 0 -ij_groovy_blank_lines_around_method = 1 -ij_groovy_blank_lines_around_method_in_interface = 1 -ij_groovy_blank_lines_before_imports = 1 -ij_groovy_blank_lines_before_method_body = 0 -ij_groovy_blank_lines_before_package = 0 -ij_groovy_block_brace_style = end_of_line -ij_groovy_block_comment_add_space = false -ij_groovy_block_comment_at_first_column = true -ij_groovy_call_parameters_new_line_after_left_paren = false -ij_groovy_call_parameters_right_paren_on_new_line = false -ij_groovy_call_parameters_wrap = on_every_item -ij_groovy_catch_on_new_line = false -ij_groovy_class_annotation_wrap = split_into_lines -ij_groovy_class_brace_style = end_of_line -ij_groovy_class_count_to_use_import_on_demand = 5 -ij_groovy_do_while_brace_force = never -ij_groovy_else_on_new_line = false -ij_groovy_enable_groovydoc_formatting = true -ij_groovy_enum_constants_wrap = off -ij_groovy_extends_keyword_wrap = normal -ij_groovy_extends_list_wrap = normal -ij_groovy_field_annotation_wrap = split_into_lines -ij_groovy_finally_on_new_line = false -ij_groovy_for_brace_force = never -ij_groovy_for_statement_new_line_after_left_paren = false -ij_groovy_for_statement_right_paren_on_new_line = false -ij_groovy_for_statement_wrap = off -ij_groovy_ginq_general_clause_wrap_policy = 2 -ij_groovy_ginq_having_wrap_policy = 1 -ij_groovy_ginq_indent_having_clause = true -ij_groovy_ginq_indent_on_clause = true -ij_groovy_ginq_on_wrap_policy = 1 -ij_groovy_ginq_space_after_keyword = true -ij_groovy_if_brace_force = never -ij_groovy_import_annotation_wrap = 2 -ij_groovy_imports_layout = *, |, javax.**, java.**, |, $* -ij_groovy_indent_case_from_switch = true -ij_groovy_indent_label_blocks = true -ij_groovy_insert_inner_class_imports = false -ij_groovy_keep_blank_lines_before_right_brace = 0 -ij_groovy_keep_blank_lines_in_code = 0 -ij_groovy_keep_blank_lines_in_declarations = 0 -ij_groovy_keep_control_statement_in_one_line = false -ij_groovy_keep_first_column_comment = false -ij_groovy_keep_indents_on_empty_lines = false -ij_groovy_keep_line_breaks = false -ij_groovy_keep_multiple_expressions_in_one_line = false -ij_groovy_keep_simple_blocks_in_one_line = false -ij_groovy_keep_simple_classes_in_one_line = false -ij_groovy_keep_simple_lambdas_in_one_line = false -ij_groovy_keep_simple_methods_in_one_line = false -ij_groovy_label_indent_absolute = false -ij_groovy_label_indent_size = 0 -ij_groovy_lambda_brace_style = end_of_line -ij_groovy_layout_static_imports_separately = true -ij_groovy_line_comment_add_space = false -ij_groovy_line_comment_add_space_on_reformat = false -ij_groovy_line_comment_at_first_column = true -ij_groovy_method_annotation_wrap = split_into_lines -ij_groovy_method_brace_style = end_of_line -ij_groovy_method_call_chain_wrap = on_every_item -ij_groovy_method_parameters_new_line_after_left_paren = false -ij_groovy_method_parameters_right_paren_on_new_line = true -ij_groovy_method_parameters_wrap = on_every_item -ij_groovy_modifier_list_wrap = false -ij_groovy_names_count_to_use_import_on_demand = 3 -ij_groovy_packages_to_use_import_on_demand = java.awt.*, javax.swing.* -ij_groovy_parameter_annotation_wrap = off -ij_groovy_parentheses_expression_new_line_after_left_paren = false -ij_groovy_parentheses_expression_right_paren_on_new_line = false -ij_groovy_prefer_parameters_wrap = false -ij_groovy_resource_list_new_line_after_left_paren = false -ij_groovy_resource_list_right_paren_on_new_line = false -ij_groovy_resource_list_wrap = off -ij_groovy_space_after_assert_separator = true -ij_groovy_space_after_colon = true -ij_groovy_space_after_comma = true -ij_groovy_space_after_comma_in_type_arguments = true -ij_groovy_space_after_for_semicolon = true -ij_groovy_space_after_quest = true -ij_groovy_space_after_type_cast = true -ij_groovy_space_before_annotation_parameter_list = false -ij_groovy_space_before_array_initializer_left_brace = false -ij_groovy_space_before_assert_separator = false -ij_groovy_space_before_catch_keyword = true -ij_groovy_space_before_catch_left_brace = true -ij_groovy_space_before_catch_parentheses = true -ij_groovy_space_before_class_left_brace = true -ij_groovy_space_before_closure_left_brace = true -ij_groovy_space_before_colon = true -ij_groovy_space_before_comma = false -ij_groovy_space_before_do_left_brace = true -ij_groovy_space_before_else_keyword = true -ij_groovy_space_before_else_left_brace = true -ij_groovy_space_before_finally_keyword = true -ij_groovy_space_before_finally_left_brace = true -ij_groovy_space_before_for_left_brace = true -ij_groovy_space_before_for_parentheses = true -ij_groovy_space_before_for_semicolon = false -ij_groovy_space_before_if_left_brace = true -ij_groovy_space_before_if_parentheses = true -ij_groovy_space_before_method_call_parentheses = false -ij_groovy_space_before_method_left_brace = true -ij_groovy_space_before_method_parentheses = false -ij_groovy_space_before_quest = true -ij_groovy_space_before_record_parentheses = false -ij_groovy_space_before_switch_left_brace = true -ij_groovy_space_before_switch_parentheses = true -ij_groovy_space_before_synchronized_left_brace = true -ij_groovy_space_before_synchronized_parentheses = true -ij_groovy_space_before_try_left_brace = true -ij_groovy_space_before_try_parentheses = true -ij_groovy_space_before_while_keyword = true -ij_groovy_space_before_while_left_brace = true -ij_groovy_space_before_while_parentheses = true -ij_groovy_space_in_named_argument = true -ij_groovy_space_in_named_argument_before_colon = false -ij_groovy_space_within_empty_array_initializer_braces = false -ij_groovy_space_within_empty_method_call_parentheses = false -ij_groovy_spaces_around_additive_operators = true -ij_groovy_spaces_around_assignment_operators = true -ij_groovy_spaces_around_bitwise_operators = true -ij_groovy_spaces_around_equality_operators = true -ij_groovy_spaces_around_lambda_arrow = true -ij_groovy_spaces_around_logical_operators = true -ij_groovy_spaces_around_multiplicative_operators = true -ij_groovy_spaces_around_regex_operators = true -ij_groovy_spaces_around_relational_operators = true -ij_groovy_spaces_around_shift_operators = true -ij_groovy_spaces_within_annotation_parentheses = false -ij_groovy_spaces_within_array_initializer_braces = false -ij_groovy_spaces_within_braces = true -ij_groovy_spaces_within_brackets = false -ij_groovy_spaces_within_cast_parentheses = false -ij_groovy_spaces_within_catch_parentheses = false -ij_groovy_spaces_within_for_parentheses = false -ij_groovy_spaces_within_gstring_injection_braces = false -ij_groovy_spaces_within_if_parentheses = false -ij_groovy_spaces_within_list_or_map = false -ij_groovy_spaces_within_method_call_parentheses = false -ij_groovy_spaces_within_method_parentheses = false -ij_groovy_spaces_within_parentheses = false -ij_groovy_spaces_within_switch_parentheses = false -ij_groovy_spaces_within_synchronized_parentheses = false -ij_groovy_spaces_within_try_parentheses = false -ij_groovy_spaces_within_tuple_expression = false -ij_groovy_spaces_within_while_parentheses = false -ij_groovy_special_else_if_treatment = true -ij_groovy_ternary_operation_wrap = off -ij_groovy_throws_keyword_wrap = normal -ij_groovy_throws_list_wrap = normal -ij_groovy_use_flying_geese_braces = false -ij_groovy_use_fq_class_names = false -ij_groovy_use_fq_class_names_in_javadoc = true -ij_groovy_use_relative_indents = false -ij_groovy_use_single_class_imports = true -ij_groovy_variable_annotation_wrap = off -ij_groovy_while_brace_force = never -ij_groovy_while_on_new_line = false -ij_groovy_wrap_chain_calls_after_dot = false -ij_groovy_wrap_long_lines = false - -[{*.har,*.png.mcmeta,mcmod.info,pack.mcmeta}] -ij_continuation_indent_size = 4 -ij_json_array_wrapping = normal -ij_json_keep_blank_lines_in_code = 0 -ij_json_keep_indents_on_empty_lines = false -ij_json_keep_line_breaks = false -ij_json_keep_trailing_comma = false -ij_json_object_wrapping = on_every_item -ij_json_property_alignment = do_not_align -ij_json_space_after_colon = true -ij_json_space_after_comma = true -ij_json_space_before_colon = false -ij_json_space_before_comma = false -ij_json_spaces_within_braces = false -ij_json_spaces_within_brackets = false -ij_json_wrap_long_lines = false - -[{*.htm,*.html,*.sht,*.shtm,*.shtml}] -ij_smart_tabs = false -ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3 -ij_html_align_attributes = true -ij_html_align_text = false -ij_html_attribute_wrap = normal -ij_html_block_comment_add_space = false -ij_html_block_comment_at_first_column = true -ij_html_do_not_align_children_of_min_lines = 0 -ij_html_do_not_break_if_inline_tags = title, h1, h2, h3, h4, h5, h6, p -ij_html_do_not_indent_children_of_tags = html, body, thead, tbody, tfoot -ij_html_enforce_quotes = false -ij_html_inline_tags = a, abbr, acronym, b, basefont, bdo, big, br, cite, cite, code, dfn, em, font, i, img, input, kbd, label, q, s, samp, select, small, span, strike, strong, sub, sup, textarea, tt, u, var -ij_html_keep_blank_lines = 2 -ij_html_keep_indents_on_empty_lines = false -ij_html_keep_line_breaks = true -ij_html_keep_line_breaks_in_text = true -ij_html_keep_whitespaces = false -ij_html_keep_whitespaces_inside = span, pre, textarea -ij_html_line_comment_at_first_column = true -ij_html_new_line_after_last_attribute = never -ij_html_new_line_before_first_attribute = never -ij_html_quote_style = double -ij_html_remove_new_line_before_tags = br -ij_html_space_after_tag_name = false -ij_html_space_around_equality_in_attribute = false -ij_html_space_inside_empty_tag = false -ij_html_text_wrap = normal - -[{*.kt,*.kts}] -indent_style = space -ij_smart_tabs = false -ij_kotlin_align_in_columns_case_branch = false -ij_kotlin_align_multiline_binary_operation = false -ij_kotlin_align_multiline_extends_list = false -ij_kotlin_align_multiline_method_parentheses = false -ij_kotlin_align_multiline_parameters = true -ij_kotlin_align_multiline_parameters_in_calls = false -ij_kotlin_allow_trailing_comma = false -ij_kotlin_allow_trailing_comma_on_call_site = false -ij_kotlin_assignment_wrap = off -ij_kotlin_blank_lines_after_class_header = 0 -ij_kotlin_blank_lines_around_block_when_branches = 0 -ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 -ij_kotlin_block_comment_add_space = false -ij_kotlin_block_comment_at_first_column = true -ij_kotlin_call_parameters_new_line_after_left_paren = false -ij_kotlin_call_parameters_right_paren_on_new_line = false -ij_kotlin_call_parameters_wrap = off -ij_kotlin_catch_on_new_line = false -ij_kotlin_class_annotation_wrap = split_into_lines -ij_kotlin_continuation_indent_for_chained_calls = true -ij_kotlin_continuation_indent_for_expression_bodies = true -ij_kotlin_continuation_indent_in_argument_lists = true -ij_kotlin_continuation_indent_in_elvis = true -ij_kotlin_continuation_indent_in_if_conditions = true -ij_kotlin_continuation_indent_in_parameter_lists = true -ij_kotlin_continuation_indent_in_supertype_lists = true -ij_kotlin_else_on_new_line = false -ij_kotlin_enum_constants_wrap = off -ij_kotlin_extends_list_wrap = off -ij_kotlin_field_annotation_wrap = split_into_lines -ij_kotlin_finally_on_new_line = false -ij_kotlin_if_rparen_on_new_line = false -ij_kotlin_import_nested_classes = false -ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^ -ij_kotlin_insert_whitespaces_in_simple_one_line_method = true -ij_kotlin_keep_blank_lines_before_right_brace = 2 -ij_kotlin_keep_blank_lines_in_code = 2 -ij_kotlin_keep_blank_lines_in_declarations = 2 -ij_kotlin_keep_first_column_comment = true -ij_kotlin_keep_indents_on_empty_lines = false -ij_kotlin_keep_line_breaks = true -ij_kotlin_lbrace_on_next_line = false -ij_kotlin_line_break_after_multiline_when_entry = true -ij_kotlin_line_comment_add_space = false -ij_kotlin_line_comment_add_space_on_reformat = false -ij_kotlin_line_comment_at_first_column = true -ij_kotlin_method_annotation_wrap = split_into_lines -ij_kotlin_method_call_chain_wrap = off -ij_kotlin_method_parameters_new_line_after_left_paren = false -ij_kotlin_method_parameters_right_paren_on_new_line = false -ij_kotlin_method_parameters_wrap = off -ij_kotlin_name_count_to_use_star_import = 5 -ij_kotlin_name_count_to_use_star_import_for_members = 3 -ij_kotlin_packages_to_use_import_on_demand = java.util.*, kotlinx.android.synthetic.**, io.ktor.** -ij_kotlin_parameter_annotation_wrap = off -ij_kotlin_space_after_comma = true -ij_kotlin_space_after_extend_colon = true -ij_kotlin_space_after_type_colon = true -ij_kotlin_space_before_catch_parentheses = true -ij_kotlin_space_before_comma = false -ij_kotlin_space_before_extend_colon = true -ij_kotlin_space_before_for_parentheses = true -ij_kotlin_space_before_if_parentheses = true -ij_kotlin_space_before_lambda_arrow = true -ij_kotlin_space_before_type_colon = false -ij_kotlin_space_before_when_parentheses = true -ij_kotlin_space_before_while_parentheses = true -ij_kotlin_spaces_around_additive_operators = true -ij_kotlin_spaces_around_assignment_operators = true -ij_kotlin_spaces_around_equality_operators = true -ij_kotlin_spaces_around_function_type_arrow = true -ij_kotlin_spaces_around_logical_operators = true -ij_kotlin_spaces_around_multiplicative_operators = true -ij_kotlin_spaces_around_range = false -ij_kotlin_spaces_around_relational_operators = true -ij_kotlin_spaces_around_unary_operator = false -ij_kotlin_spaces_around_when_arrow = true -ij_kotlin_variable_annotation_wrap = off -ij_kotlin_while_on_new_line = false -ij_kotlin_wrap_elvis_expressions = 1 -ij_kotlin_wrap_expression_body_functions = 0 -ij_kotlin_wrap_first_method_in_call_chain = false - -[{*.markdown,*.md}] -ij_continuation_indent_size = 4 -ij_visual_guides = 64 -ij_markdown_force_one_space_after_blockquote_symbol = true -ij_markdown_force_one_space_after_header_symbol = true -ij_markdown_force_one_space_after_list_bullet = true -ij_markdown_force_one_space_between_words = true -ij_markdown_format_tables = true -ij_markdown_insert_quote_arrows_on_wrap = true -ij_markdown_keep_indents_on_empty_lines = false -ij_markdown_keep_line_breaks_inside_text_blocks = true -ij_markdown_max_lines_around_block_elements = 1 -ij_markdown_max_lines_around_header = 1 -ij_markdown_max_lines_between_paragraphs = 1 -ij_markdown_min_lines_around_block_elements = 1 -ij_markdown_min_lines_around_header = 1 -ij_markdown_min_lines_between_paragraphs = 1 -ij_markdown_wrap_text_if_long = true -ij_markdown_wrap_text_inside_blockquotes = true - -[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}] -ij_smart_tabs = false -ij_toml_keep_indents_on_empty_lines = false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b8418f0..2591b147 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,35 +1,20 @@ # Contributing -Welcome contributions! +* Use google-java-format -According to Github Community Standards I put this file here. +## How to add a new config item -## About this repository +Define config item in class `AbstractConfig` -### branches +```java +public abstract class AbstractConfig { + @Expose public double my_option = 0.5; +} -#### `1.19.2` +``` -It's obviously the main branch for Minecraft 1.19.2, so does `1.19.4` and `1.20.1` .etc. +Use the config item somewhere. -#### `Documentation` +Find all screen builders in `ConfigScreenBuilder#builders`, and add the config item to the builder. -It's an independent branch for documentation. - -## About files - -### [`.editorconfig`](./.editorconfig) - -EditorConfig defines code format. - -### [`changelog_latest.md`](./changelog_latest.md) - -It contains changes since last published version. - -It should be manually edited before publishing a new version. - -It should be manually cleared once a new version is published. - -### Versioning - -Reference to [SemVer](https://semver.org/) +Add translation to `resource/assets/minecraft/lang/*.json` diff --git a/LICENSE.txt b/LICENSE.txt index 1cc0b55f..8e8fb8ec 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,7 @@ The MIT License (MIT) -Copyright (c) 2023 Leawind + +Copyright (c) 2023-2024 Leawind (Username) + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/README-ZH.md b/README-ZH.md new file mode 100644 index 00000000..3801ac20 --- /dev/null +++ b/README-ZH.md @@ -0,0 +1,52 @@ +
+ +| [English](./README.md) | 中文 | +|------------------------|----| + +[![CurseForge下载量](https://img.shields.io/curseforge/dt/930880?style=flat&logo=curseforge&color=F1643%5E&cacheSeconds=3600&label=下载量)](https://www.curseforge.com/minecraft/mc-mods/leawind-third-person) +[![Modrinth下载量](https://img.shields.io/modrinth/dt/S3D3QF0M?style=flat&logo=modrinth&color=17B85A&cacheSeconds=3600&label=下载量)](https://modrinth.com/mod/leawind-third-person) + +[![Codacy Badge](https://img.shields.io/codacy/grade/41e70a17218c4773aefb62382b9547a6?logo=codacy&label=代码质量)](https://app.codacy.com/gh/Leawind/Third-Person/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) +[![Stars](https://img.shields.io/github/stars/LEAWIND/Third-Person?style=flat&logo=github&color=daaa3f&label=星标)](https://github.com/LEAWIND/Third-Person) + +[![上次提交](https://img.shields.io/github/last-commit/LEAWIND/Third-Person?logo=github&label=上次提交)](https://github.com/LEAWIND/Third-Person) +[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?label=开源协议)](https://github.com/LEAWIND/Third-Person?tab=MIT-1-ov-file) +[![文档](https://img.shields.io/github/deployments/LEAWIND/Third-Person/github-pages?style=flat&logo=github&label=文档&cacheSeconds=900)](https://leawind.github.io/Third-Person/en-US/?autolang) + +# Leawind 的第三人称 + +一个实用、丝滑、功能丰富的第三人称模组。 + +
+ +* **纯客户端** 不需要在服务端安装此模组,因此可以在服务器中使用 +* **自由转动视角** 视角可以自由转动,同时保持玩家身体不动。 +* **自由调整相机位置** + * 按住 `Z` 时,移动鼠标可以调整相机偏移量(玩家在屏幕上的位置),鼠标滚轮可以调整相机到玩家的距离 + * **快速切换相机偏移位置(左|中|右)** 短按 `CapsLock` 可切换左右,按住 `CapsLock` 可以切换到居中 +* **智能瞄准模式** 根据玩家手持物品和使用状态自动切换到瞄准模式。规则可自定义以兼容其他模组的物品 +* **类似第一人称的射击** 第三人称下瞄准远处的敌人时,模组会自动预测你想要射击的目标实体,你只需要像第一人称那样将准星放在敌人上方即可 +* **玩家半透明** 当玩家实体阻挡视线时,会变得半透明(可能不兼容 Sodium,默认禁用此功能) +* **平滑切换视角** 在第一/第三人称视角间平滑过渡 +* **随时禁用** 如果此模组引发了故障,你随时可以在游戏中通过配置界面或快捷键来禁用此模组,恢复成原版第三人称视角 + +
+
+捐赠 + +ΨQ + +>
+> 通过微信捐赠 +> wechat +>
+>
+> 其他方式 +> +> [Buy Me a Coffee](https://www.buymeacoffee.com/leawind) +> [爱发电](https://afdian.com/a/Leawind) +> +>
+ +
+
diff --git a/README.md b/README.md index 3b0d37b7..9db893ea 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,58 @@ +
+ +| English | [中文](./README-ZH.md) | +|---------|----------------------| + +[![CurseForge downloads](https://img.shields.io/curseforge/dt/930880?style=flat&logo=curseforge&color=F1643%5E&cacheSeconds=3600&label=Downloads)](https://www.curseforge.com/minecraft/mc-mods/leawind-third-person) +[![Modrinth downloads](https://img.shields.io/modrinth/dt/S3D3QF0M?style=flat&logo=modrinth&color=17B85A&cacheSeconds=3600&label=Downloads)](https://modrinth.com/mod/leawind-third-person) + +[![Codacy Badge](https://img.shields.io/codacy/grade/41e70a17218c4773aefb62382b9547a6?logo=codacy)](https://app.codacy.com/gh/Leawind/Third-Person/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) +[![Stars](https://img.shields.io/github/stars/LEAWIND/Third-Person?style=flat&logo=github&color=daaa3f)](https://github.com/LEAWIND/Third-Person) + +[![Last commit](https://img.shields.io/github/last-commit/LEAWIND/Third-Person?logo=github)](https://github.com/LEAWIND/Third-Person) +[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/LEAWIND/Third-Person?tab=MIT-1-ov-file) +[![Documentation](https://img.shields.io/github/deployments/LEAWIND/Third-Person/github-pages?style=flat&logo=github&label=Documentation&cacheSeconds=900)](https://leawind.github.io/Third-Person/en-US/?autolang) + # Leawind's Third Person -[Mod Document](https://leawind.github.io/Third-Person/en-US/) +A practical, smooth, feature-rich third person mod for all Minecraft players. + +
+ +* **Client Side Only** No need to install on server. So you can join servers with this mod. +* **Free Rotation** Freely rotate the view while keeping the player's body stationary. +* **Free Adjustment of Camera Position** + * While holding `Z`, moving the mouse adjusts the camera offset (the player's position on the screen), and the mouse + wheel adjusts the distance between the camera and the player + * **Quick Switching of Camera Offset (Left|Center|Right)** Short press `CapsLock` to toggle between left and right. + Hold `CapsLock` to center the camera +* **Smart Aim Mode** Automatically switches to aim mode based on the item the player is holding and its using status. + Rules can be customized. So it can be compatible with items from other mods +* **Shooting like first-person** When aiming at enemies in third-person, it predicts the target entity you want to shoot + at, allowing you to simply place the crosshair above the enemy as if in first-person +* **Player Transparency** When the player entity obstructs the view, it becomes semi-transparent (May not be compatible + with Sodium. This feature is disabled by default) +* **Smooth Perspective Switching** Smooth transition between first and third person perspectives +* **Disable Anytime** If the mod causes issues, you can disable it anytime in-game through the configuration menu or + hotkey, reverting to the original third-person perspective + +
+
+Donates ---- +ΨQ -# Leawind的第三人称 +>
+> Donate using Wechat +> wechat +>
+>
+> Other ways +> +> [Buy Me a Coffee](https://www.buymeacoffee.com/leawind) +> [Afdian (爱发电)](https://afdian.com/a/Leawind) +> +>
-[模组文档](https://leawind.github.io/Third-Person/zh-CN/) +
+
diff --git a/build.gradle b/build.gradle index a01d657e..6afe01da 100644 --- a/build.gradle +++ b/build.gradle @@ -1,25 +1,70 @@ +import groovy.json.JsonOutput +import groovy.json.JsonSlurper +import me.modmuss50.mpp.ReleaseType + +import java.nio.file.Files + plugins { - id "architectury-plugin" version "3.4-SNAPSHOT" + // https://github.com/architectury/architectury-plugin + // https://maven.architectury.dev/architectury-plugin/architectury-plugin.gradle.plugin/ + id "architectury-plugin" version "3.4.159" // https://maven.architectury.dev/dev/architectury/loom/dev.architectury.loom.gradle.plugin/ - id "dev.architectury.loom" version "1.5-SNAPSHOT" apply false + id "dev.architectury.loom" version "1.7.414" apply false + // 用于发布至 Modrinth 和 CurseForge + // https://github.com/modmuss50/mod-publish-plugin + id "me.modmuss50.mod-publish-plugin" version "0.7.4" } + architectury { minecraft = minecraft_version } + subprojects { apply plugin: "dev.architectury.loom" loom { silentMojangMappingsLicense() } - dependencies { - minecraft "com.mojang:minecraft:${minecraft_version}" - mappings loom.officialMojangMappings() - } processResources { - eachFile { expand rootProject.properties } + filesMatching("pack.mcmeta") { fcd -> + File f = fcd.file + if(!f.exists()){ + println "File not exist: " + f + return + } + Map meta + try{ + meta = new JsonSlurper().parse(f) as Map + }catch(Exception e){ + throw new RuntimeException("Invalid json file: ${f}\n$e.message}") + } + ((Map) meta.pack).pack_format = Integer.parseInt(resource_pack_format) + // meta.pack.pack_format = Integer.parseInt(resource_pack_format) + def writer = new FileWriter(f) + writer.write(JsonOutput.prettyPrint(JsonOutput.toJson(meta))) + writer.close() + } + eachFile { + expand rootProject.properties + } + } + tasks.named("build") { + if(project.name in getLoaders()){ + doLast { + var fileName = "${archiveFileNameOfLoader(project.name)}.jar" + var srcFile = project.file("build/libs/${fileName}") + if(srcFile.exists()){ + var dstFile = project.rootProject.file("build/libs/${fileName}") + if(dstFile.exists()) + delete dstFile.toPath() + println "Copying from \n\t${srcFile} to \n\t${dstFile}" + Files.copy(srcFile.toPath(), dstFile.toPath()) + }else{ + println "Jar file not found: ${srcFile}" + } + } + } } } - allprojects { apply plugin: "java" apply plugin: "architectury-plugin" @@ -35,15 +80,14 @@ allprojects { maven { url "https://jitpack.io" } // Cloth Config API maven { url "https://maven.shedaniel.me/" } + // YACL + // maven { url "https://maven.isxander.dev/releases" } + // maven { url "https://maven.quiltmc.org/repository/release" } + // maven { url "https://oss.sonatype.org/content/repositories/snapshots" } // Mod Menu maven { url "https://maven.terraformersmc.com/releases" } // Modrinth gradlePluginPortal() - - } - tasks.withType(JavaCompile) { - options.encoding = "UTF-8" - options.release = JavaLanguageVersion.of(java_version).asInt() } java { withSourcesJar() @@ -52,39 +96,232 @@ allprojects { setDuplicatesStrategy DuplicatesStrategy.INCLUDE from rootProject.file("LICENSE.txt") } + javadoc.options.encoding = "UTF-8" + tasks.withType(JavaCompile).configureEach { + options.encoding "UTF-8" + options.release = JavaLanguageVersion.of(java_version).asInt() + } + tasks.withType(GroovyCompile).configureEach { + options.encoding "UTF-8" + } +} + +String[] getLoaders(){ + return enabled_loaders.split(/\s*,\s*/) +} + +// 版本名 +// 2.1.0-mc1.20.1-fabric +// 2.1.0-mc1.20-1.20.1-forge +String archiveVersionNameOfLoader(String loader){ + final MC_VERSION_PATTERN = '^\\d+(\\.\\d+){1,2}$' + assert getLoaders().contains(loader) + + assert minecraft_version =~ MC_VERSION_PATTERN + assert minecraft_version_min =~ MC_VERSION_PATTERN + assert minecraft_version_max =~ MC_VERSION_PATTERN + + def minecraft_version_text = "${minecraft_version_min}" + if(minecraft_version_max != minecraft_version_min) + minecraft_version_text += "-${minecraft_version_max}" + + return "${mod_version}-mc${minecraft_version_text}-${loader}" } -// 生成文件名格式 -String archiveFileNameOfPlatform(String platform){ - switch(platform) { - case "forge": - return "${mod_id}-v${mod_version}-mc${minecraft_version}-forge"; - case "fabric": - return "${mod_id}-v${mod_version}-mc${minecraft_version}-fabric"; - default: - throw new IllegalArgumentException("Unknown platform: ${platform}"); +// 文件名 +// leawind_third_person_v2.1.0-mc1.20.1-fabric +// leawind_third_person_v2.1.0-mc1.20-1.20.1-fabric +// leawind_third_person_v2.1.0-mc1.19-1.19.4-fabric +String archiveFileNameOfLoader(String loader){ + return "${mod_id}-v${archiveVersionNameOfLoader(loader)}" +} + +// 检查更新日志的格式 +static void validateChangelog(String cl){ + cl.eachLine { + if(it.startsWith("###")) + assert it =~ /^### (Added|Changed|Removed|Fixed|Compatibility|Other)$/ + // 首字母不能小写 + assert !(it =~ (/\s*^\*\s+[a-z]/)) + // 配置键应当用反引号包裹 `` + assert !(it =~ /[^a-z_`][a-z]+(_[a-z]+)+/) + assert !(it =~ /[a-z]+(_[a-z]+)+[^a-z_`]/) + // 句末不能有句号 + assert !(it =~ /[。.]\s*(#\d+)?\u0024/) } } -// 读取更新日志 -String readChangeLog(){ - File changelogFile = getProjectDir().toPath().resolve("changelog_latest.md").toFile() - if(changelogFile.canRead()){ - return changelogFile.text - }else{ - return "No changelog" +/** + * 读取更新日志 + * + * @param doCheck 是否检查合法性 + */ +String readChangelog(boolean doCheck = false){ + String cl = rootProject.file(changelog_file).text + if(doCheck) + validateChangelog(cl) + return cl.replaceAll(/### [A-Za-z]+[\s\n]+(?=###|\u0024)/, '') + .replaceAll(/\n\s+\n/, '\n\n') + .replaceAll(/\n+$/, '\n') +} + +// 注册任务:检查更新日志 +tasks.register("checkChangelog") { + group "verification" + description "Check changelog format" + doLast { + var cl = readChangelog(true) + var width = 0 + cl.eachLine (li) -> width = Math.max(width, li.length()) + println "# Changelog:" + println "=".repeat(width) + println cl + println "=".repeat(width) + } +} +// 将检查更新日志设为 check 的依赖 +check { + dependsOn "checkChangelog" +} +tasks.register("linkRuns") { + group "loom" + description "Link running dirs" + def linkFileTo = { File src, File tar -> + if(tar.exists()){ + if(!src.isFile() || !Files.isSameFile(src.toPath(), tar.toPath())){ + delete src.toPath() + Files.createLink(src.toPath(), tar.toPath()) + } + }else{ + if(src.isFile()){ + Files.move(src.toPath(), tar.toPath()) + Files.createLink(src.toPath(), tar.toPath()) + } + } + } + def linkDirTo = { File src, File tar -> + if(src.exists()){ + if(tar.exists()){ + if(!Files.isSameFile(src.toPath(), tar.toPath())){ + src.listFiles().each { + var dFile = new File(tar, it.name) + if(dFile.exists()){ + delete it.toPath() + }else{ + Files.move(it.toPath(), dFile.toPath()) + } + } + delete src.toPath() + } + }else{ + Files.move(src.toPath(), tar.toPath()) + } + }else if(tar.exists()){ + Files.createSymbolicLink(src.toPath(), tar.toPath()) + } + } + doLast { + String[] linkedFiles = ["options.txt"] + String[] linkedDirs = ["saves", "resourcepacks", "generated", "shaderpacks", "screenshots"] + def commonRunDir = project("common").file("run") + mkdir commonRunDir + linkedFiles.each { + File file = new File(commonRunDir, it) + assert file.exists() || file.isFile() + } + linkedDirs.each { + mkdir new File(commonRunDir, it).toPath() + } + getLoaders().each { + File loaderRunDir = project(it).file("run") + if(!loaderRunDir.isDirectory()){ + mkdir loaderRunDir + return + } + linkedFiles.each { + linkFileTo(new File(loaderRunDir, it), new File(commonRunDir, it)) + } + linkedDirs.each { + linkDirTo(new File(loaderRunDir, it), new File(commonRunDir, it)) + } + } } } -tasks.register("testBuild") { - println "<<< Testing build scripts >>>" - - println "Enabled platforms: ${enabled_platforms}" - println "Archive file name for each platform:" - enabled_platforms.split(",").each { - println "\t$it: ${archiveFileNameOfPlatform(it)}" +publishMods { + def toVersionType = { String type -> + switch(type) { + case "release": + return ReleaseType.STABLE + case "stable": + return ReleaseType.STABLE + case "beta": + return ReleaseType.BETA + case "alpha": + return ReleaseType.ALPHA + default: + throw new IllegalArgumentException("Unknown version type: ${type}") + } + } + dryRun = providers.environmentVariable("DO_PUBLISH_MOD").getOrNull() != "true" + version = mod_version + // me.modmuss50.mpp.ReleaseType + type = toVersionType(mod_version_type) + changelog = readChangelog(true) + if(!dryRun){ + assert providers.environmentVariable("MODRINTH_TOKEN").present + assert providers.environmentVariable("CURSEFORGE_TOKEN").present + } + def loaderOptions = { String loader -> + publishOptions { + modLoaders.add(loader) + // import net.fabricmc.loom.task.RemapJarTask + file = project(":${loader}").remapJar.archiveFile + file = file.get().asFile.toPath().parent.resolve("${archiveFileNameOfLoader(loader)}.jar").toFile() + displayName = archiveVersionNameOfLoader(loader) + } + } + def modrinthOptions = modrinthOptions { + accessToken = providers.environmentVariable("MODRINTH_TOKEN") + projectId = modrinth_project_id + minecraft_version_list.split('[^\\d.]+').each { + minecraftVersions.add(it) + } + } + def curseforgeOptions = curseforgeOptions { + accessToken = providers.environmentVariable("CURSEFORGE_TOKEN") + projectId = curseforge_project_id + minecraft_version_list.split('[^\\d.]+').each { + minecraftVersions.add(it) + } + javaVersions.add(JavaVersion.toVersion(java_version)) + clientRequired = true + serverRequired = false + } + modrinth("modrinthFabric") { + from modrinthOptions, loaderOptions("fabric") + requires "fabric-api" + requires "architectury-api" + optional "cloth-config" + // optional "yacl" + optional "modmenu" + } + curseforge("curseforgeFabric") { + from curseforgeOptions, loaderOptions("fabric") + requires "fabric-api" + requires "architectury-api" + optional "cloth-config" + // optional "yacl" + optional "modmenu" + } + modrinth("modrinthForge") { + from modrinthOptions, loaderOptions("forge") + requires "architectury-api" + optional "cloth-config" + } + curseforge("curseforgeForge") { + from curseforgeOptions, loaderOptions("forge") + requires "architectury-api" + optional "cloth-config" } - println "\tModrinth API key exists: ${System.getenv("MODRINTH_TOKEN") != null}" - - println "Changelog: ${readChangeLog().replaceAll("^|\n", "\n\t")}" } diff --git a/changelog.md b/changelog.md new file mode 100644 index 00000000..c4d40469 --- /dev/null +++ b/changelog.md @@ -0,0 +1,9 @@ +### Changed + +* Port from v2.2.0-1.20-1.20.1 + +### Fixed + +### Compatibility + +### Other diff --git a/changelog_latest.md b/changelog_latest.md deleted file mode 100644 index 57f92085..00000000 --- a/changelog_latest.md +++ /dev/null @@ -1,5 +0,0 @@ -### Features - -### Bug fix - -* fix: key `force_aiming`, `toggle_aiming` now working diff --git a/common/build.gradle b/common/build.gradle index b695af30..c6bb6266 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,23 +1,31 @@ architectury { - common(enabled_platforms.split(",")) + common(enabled_loaders.split(",")) } loom { accessWidenerPath = file("src/main/resources/${mod_id}.accesswidener") } dependencies { + minecraft "com.mojang:minecraft:${minecraft_version}" + mappings loom.officialMojangMappings() + testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}" + + modImplementation "org.joml:joml:${joml_version}" + modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}" modApi "dev.architectury:architectury:${architectury_version}" // Cloth config - modApi("me.shedaniel.cloth:cloth-config-fabric:${cloth_config_api_version}") { exclude(group: "net.fabricmc.fabric-api") } + modApi("me.shedaniel.cloth:cloth-config-fabric:${cloth_config_api_version}") { + exclude group: "net.fabricmc.fabric-api" + exclude module: 'modmenu' + } + // YACL + // modImplementation "dev.isxander.yacl:yet-another-config-lib-fabric:${yacl_mc_version}" } publishing { - publications { - mavenCommon(MavenPublication) { - artifactId = mod_id - from components.java - } - } - - repositories { - } + publications {} + repositories {} +} +test { + useJUnitPlatform() } diff --git a/common/src/main/java/com/github/leawind/thirdperson/ThirdPerson.java b/common/src/main/java/com/github/leawind/thirdperson/ThirdPerson.java new file mode 100644 index 00000000..3f253f09 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/ThirdPerson.java @@ -0,0 +1,62 @@ +package com.github.leawind.thirdperson; + +import com.github.leawind.thirdperson.config.Config; +import com.github.leawind.thirdperson.config.ConfigManager; +import com.github.leawind.thirdperson.core.CameraAgent; +import com.github.leawind.thirdperson.core.EntityAgent; +import com.github.leawind.thirdperson.util.FiniteChecker; +import com.llamalad7.mixinextras.MixinExtrasBootstrap; +import dev.architectury.platform.Platform; +import net.minecraft.client.Minecraft; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class ThirdPerson { + public static final Logger LOGGER = LoggerFactory.getLogger(ThirdPersonConstants.MOD_NAME); + + public static final FiniteChecker FINITE_CHECKER = + new FiniteChecker(err -> ThirdPerson.LOGGER.error(err.toString())); + public static final ConfigManager CONFIG_MANAGER = new ConfigManager(); + + public static EntityAgent ENTITY_AGENT; + public static CameraAgent CAMERA_AGENT; + + public static void init() { + var minecraft = Minecraft.getInstance(); + LOGGER.debug("Initializing mod {}", ThirdPersonConstants.MOD_NAME); + MixinExtrasBootstrap.init(); + + ENTITY_AGENT = new EntityAgent(minecraft); + CAMERA_AGENT = new CameraAgent(minecraft); + + CONFIG_MANAGER.tryLoad(); + + ThirdPersonResources.register(); + ThirdPersonKeys.register(); + ThirdPersonEvents.register(); + + Platform.getMod(ThirdPersonConstants.MOD_ID) + .registerConfigurationScreen(ThirdPerson.CONFIG_MANAGER::getConfigScreen); + } + + /** 判断:模组功能已启用,且相机和玩家都已经初始化 */ + public static boolean isAvailable() { + var minecraft = Minecraft.getInstance(); + return minecraft.player != null + && minecraft.cameraEntity != null + && getConfig().is_mod_enabled + && minecraft.gameRenderer.getMainCamera().isInitialized(); + } + + /** 获取当前配置实例 */ + public static @NotNull Config getConfig() { + return CONFIG_MANAGER.getConfig(); + } + + public static void resetFiniteCheckers() { + ThirdPerson.FINITE_CHECKER.reset(); + ENTITY_AGENT.FINITE_CHECKER.reset(); + CAMERA_AGENT.FINITE_CHECKER.reset(); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonConstants.java b/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonConstants.java new file mode 100644 index 00000000..2da3b945 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonConstants.java @@ -0,0 +1,91 @@ +package com.github.leawind.thirdperson; + +import com.github.leawind.thirdperson.core.EntityAgent; +import com.github.leawind.thirdperson.util.Surroundings; +import java.io.File; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.world.level.ClipContext; + +@SuppressWarnings("unused") +public final class ThirdPersonConstants { + public static final String MOD_ID = "leawind_third_person"; + public static final String MOD_NAME = "Leawind's Third Person"; + + public static final String KEY_CATEGORY = "key.categories." + MOD_ID; + public static final File CONFIG_FILE = + Minecraft.getInstance().gameDirectory.toPath().resolve("config/" + MOD_ID + ".json").toFile(); + public static final long CONFIG_LAZY_SAVE_DELAY = 60000L; + + public static final double CAMERA_PITCH_DEGREE_LIMIT = 89.800; + public static final double CAMERA_THROUGH_WALL_DETECTION = 0.180; + public static final double ROTATE_CENTER_RADIUS = 0.5; + + /// 当俯仰角的绝对值大于这个阈值时,将偏移量减小,以便与头顶和脚下的方块交互(角度制) + public static final double CAMERA_OFFSET_SQUEEZE_PITCH_THRESHOLD = 80; + + /** + * true: 相机距离为从相机平面到旋转中心的距离 + * + *

false: 相机距离为从相机位置到旋转中心的距离 + */ + public static final boolean USE_CAMERA_PLAIN_DISTANCE = true; + + public static final double OPACITY_HALFLIFE = 0.0625; + + /** + * 渲染相机实体的不透明度下限阈值,当不透明度低于这个值时,将不渲染实体。 + * + * @see EntityAgent#getSmoothOpacity(float) + */ + public static final float RENDERED_OPACITY_THRESHOLD_MIN = 0.01F; + + /** 渲染相机实体的不透明度上限阈值,当不透明度高于这个值时,将以原版方式渲染实体。 */ + public static final float RENDERED_OPACITY_THRESHOLD_MAX = 0.99F; + + /** 预测目标实体时仅考虑视锥角内的实体 */ + public static final double TARGET_PREDICTION_DEGREES_LIMIT = 30; + + public static final double FIRST_PERSON_TRANSITION_END_THRESHOLD = 0.05; + + /** 平滑眼睛的半衰期乘数 */ + public static final double EYE_HALFLIFE_MULTIPLIER = 0.1; + + /** 阻挡相机的方块外形获取器 */ + public static final ClipContext.Block CAMERA_OBSTACLE_BLOCK_SHAPE_GETTER = + ClipContext.Block.VISUAL; + + /** 离开狭窄空间后至少经过多少tick才能恢复第三人称 */ + public static final int LEAVE_NARROW_SPACE_DELAY_TICKS = 16; + + public static final Surroundings SURROUNDINGS_MATCHING = + new Surroundings( + """ + B B B C M C T S T + B B B M C M S T S + B B B C M C T S T + """); + + /** + * 成像平面到相机的距离 + * + * @see Camera#getNearPlane() + */ + public static final double VANILLA_NEAR_PLANE_DISTANCE = 0.050; + + /** 玩家转头角度限制 */ + public static final float VANILLA_PLAYER_HEAD_ROTATE_LIMIT_DEGREES = 50; + + /** LocalPlayer#hasEnoughImpulseToStartSprinting */ + public static final double VANILLA_SPRINT_IMPULSE_THRESHOLD = 0.8; + + /** + * getPlayerPOVHitResult 方法中的探测距离 + * + *

Item#getPlayerPOVHitResult(Level, Player, ClipContext.Fluid) + */ + public static final double VANILLA_POV_REACH = 5.0; + + /** Client tick 时间 */ + public static final double VANILLA_CLIENT_TICK_TIME = 0.05; +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonEvents.java b/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonEvents.java new file mode 100644 index 00000000..00692f99 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonEvents.java @@ -0,0 +1,346 @@ +package com.github.leawind.thirdperson; + +import com.github.leawind.thirdperson.api.base.GameEvents; +import com.github.leawind.thirdperson.api.client.event.CalculateMoveImpulseEvent; +import com.github.leawind.thirdperson.api.client.event.EntityTurnStartEvent; +import com.github.leawind.thirdperson.api.client.event.MouseTurnPlayerStartEvent; +import com.github.leawind.thirdperson.api.client.event.RenderEntityEvent; +import com.github.leawind.thirdperson.api.client.event.RenderTickStartEvent; +import com.github.leawind.thirdperson.api.client.event.ThirdPersonCameraSetupEvent; +import com.github.leawind.thirdperson.util.ItemPredicateUtil; +import com.github.leawind.thirdperson.util.annotation.VersionSensitive; +import com.github.leawind.thirdperson.util.math.LMath; +import dev.architectury.event.EventResult; +import dev.architectury.event.events.client.ClientLifecycleEvent; +import dev.architectury.event.events.client.ClientPlayerEvent; +import dev.architectury.event.events.client.ClientRawInputEvent; +import dev.architectury.event.events.client.ClientTickEvent; +import net.minecraft.client.CameraType; +import net.minecraft.client.Minecraft; +import net.minecraft.client.MouseHandler; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.world.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2d; + +@SuppressWarnings("unused") +public final class ThirdPersonEvents { + public static void register() { + ClientTickEvent.CLIENT_PRE.register(ThirdPersonEvents::onClientTickPre); + ClientLifecycleEvent.CLIENT_STOPPING.register(ThirdPersonEvents::onClientStopping); + ClientPlayerEvent.CLIENT_PLAYER_RESPAWN.register(ThirdPersonEvents::onClientPlayerRespawn); + ClientPlayerEvent.CLIENT_PLAYER_JOIN.register(ThirdPersonEvents::onClientPlayerJoin); + ClientRawInputEvent.MOUSE_SCROLLED.register(ThirdPersonEvents::onMouseScrolled); + + GameEvents.thirdPersonCameraSetup = ThirdPersonEvents::onThirdPersonCameraSetup; + GameEvents.renderTickStart = ThirdPersonEvents::onRenderTickStart; + GameEvents.calculateMoveImpulse = ThirdPersonEvents::onCalculateMoveImpulse; + GameEvents.renderEntity = ThirdPersonEvents::onRenderEntity; + GameEvents.handleKeybindsStart = ThirdPersonEvents::onHandleKeybindsStart; + GameEvents.mouseTurnPlayerStart = ThirdPersonEvents::onMouseTurnPlayerStart; + GameEvents.entityTurnStart = ThirdPersonEvents::onEntityTurnStart; + } + + /** + * Client tick 前 + * + * @see ClientTickEvent#CLIENT_PRE + */ + private static void onClientTickPre(@NotNull Minecraft minecraft) { + if (minecraft.isPaused() || !ThirdPerson.isAvailable()) { + return; + } + var config = ThirdPerson.getConfig(); + if (minecraft.options.getCameraType() != CameraType.FIRST_PERSON) { + // 目标是第三人称 + var cameraEntity = ThirdPerson.ENTITY_AGENT.getRawCameraEntity(); + // 如果非旁观者模式的玩家在墙里边,就暂时切换到第一人称 + ThirdPersonStatus.isPerspectiveInverted = + !cameraEntity.isSpectator() && cameraEntity.isInWall(); + // 如果正在使用的物品符合相关配置,就暂时切换到第一人称 + if (cameraEntity instanceof LivingEntity livingEntity && livingEntity.isUsingItem()) { + ThirdPersonStatus.isPerspectiveInverted |= + ItemPredicateUtil.anyMatches( + livingEntity.getUseItem(), + config.getUseToFirstPersonItemPredicates(), + ThirdPersonResources.itemPredicateManager.useToFirstPersonItemPredicates); + } + // 如果位于狭窄空间内,暂时进入第一人称 + ThirdPersonStatus.ticksSinceLeaveNarrowSpace = + Math.min( + ThirdPersonConstants.LEAVE_NARROW_SPACE_DELAY_TICKS, + ThirdPersonStatus.ticksSinceLeaveNarrowSpace + 1); + if (config.temp_first_person_in_narrow_space && !cameraEntity.isSpectator()) { + boolean isInNarrowSpace = ThirdPersonStatus.calcIsInNarrowSpace(cameraEntity); + if (isInNarrowSpace) { + ThirdPersonStatus.ticksSinceLeaveNarrowSpace = 0; + } + ThirdPersonStatus.isPerspectiveInverted |= + ThirdPersonStatus.ticksSinceLeaveNarrowSpace + < ThirdPersonConstants.LEAVE_NARROW_SPACE_DELAY_TICKS; + } + } + ThirdPerson.ENTITY_AGENT.onClientTickStart(); + ThirdPerson.CAMERA_AGENT.onClientTickStart(); + } + + private static void onClientStopping(Minecraft minecraft) { + ThirdPerson.CONFIG_MANAGER.trySave(); + } + + /** + * 当玩家死亡后重生或加入新的维度时触发 + * + * @see ClientPlayerEvent#CLIENT_PLAYER_RESPAWN + */ + private static void onClientPlayerRespawn( + @NotNull LocalPlayer oldPlayer, @NotNull LocalPlayer newPlayer) { + if (ThirdPerson.getConfig().is_mod_enabled) { + resetPlayer(); + ThirdPerson.LOGGER.info("on Client player respawn"); + } + } + + /** + * 当玩家加入时触发 + * + * @see ClientPlayerEvent#CLIENT_PLAYER_JOIN + */ + private static void onClientPlayerJoin(@NotNull LocalPlayer player) { + var config = ThirdPerson.getConfig(); + if (config.is_mod_enabled) { + resetPlayer(); + ThirdPerson.LOGGER.info("on Client player join"); + } + config.updateItemPredicates(); + ThirdPersonResources.itemPredicateManager.reparse(); + } + + @VersionSensitive(value = "At latest architectury-api 9", until = "1.20.2") + private static @NotNull EventResult onMouseScrolled(@NotNull Minecraft minecraft, double amount) { + return onMouseScrolled(minecraft, 0, amount); + } + + /** 设置相机位置和朝向 */ + private static void onThirdPersonCameraSetup(ThirdPersonCameraSetupEvent event) { + if (ThirdPerson.isAvailable() + && ThirdPerson.ENTITY_AGENT.isCameraEntityExist() + && ThirdPersonStatus.isRenderingInThirdPerson()) { + ThirdPerson.CAMERA_AGENT.onCameraSetup(event); + } + } + + /** + * @see GameRenderer#render(float, long, boolean) + */ + private static void onRenderTickStart(RenderTickStartEvent event) { + ThirdPersonStatus.forceThirdPersonCrosshair = + ThirdPersonStatus.shouldRenderThirdPersonCrosshair(); + if (!ThirdPerson.getConfig().is_mod_enabled) { + return; + } + ThirdPerson.CAMERA_AGENT.checkGameStatus(); + // in seconds + double now = System.currentTimeMillis() / 1000D; + double period = now - ThirdPersonStatus.lastRenderTickTimeStamp; + ThirdPersonStatus.lastRenderTickTimeStamp = now; + + final boolean isRenderingInThirdPerson = ThirdPersonStatus.isRenderingInThirdPerson(); + + if (isRenderingInThirdPerson != ThirdPersonStatus.wasRenderInThirdPersonLastRenderTick) { + if (isRenderingInThirdPerson) { + // 进入第三人称 + + // 重置状态 + resetPlayer(); + // 将玩家朝向设为与当前相机一致 + if (ThirdPersonStatus.isRenderingInThirdPerson()) { + var cameraRot = ThirdPerson.CAMERA_AGENT.getRawRotation(); + ThirdPerson.ENTITY_AGENT.setRawRotation(cameraRot); + ThirdPerson.ENTITY_AGENT.getSmoothRotation().set(cameraRot); + ThirdPerson.CAMERA_AGENT.setRotation(cameraRot); + } + } else { + // 退出第三人称 + ThirdPerson.ENTITY_AGENT.setRawRotation(ThirdPerson.CAMERA_AGENT.getRotation()); + } + var minecraft = Minecraft.getInstance(); + minecraft.gameRenderer.checkEntityPostEffect(minecraft.getCameraEntity()); + minecraft.levelRenderer.needsUpdate(); + + ThirdPersonStatus.wasRenderInThirdPersonLastRenderTick = isRenderingInThirdPerson; + } + + if (isRenderingInThirdPerson) { + boolean shouldCameraTurnWithEntity = ThirdPersonStatus.shouldCameraTurnWithEntity(); + if (shouldCameraTurnWithEntity && !ThirdPersonStatus.wasShouldCameraTurnWithEntity) { + // 将玩家朝向设为与相机一致 + if (ThirdPersonStatus.isRenderingInThirdPerson()) { + ThirdPerson.ENTITY_AGENT.setRawRotation(ThirdPerson.CAMERA_AGENT.getRotation()); + } + } + ThirdPersonStatus.wasShouldCameraTurnWithEntity = shouldCameraTurnWithEntity; + } + + if (ThirdPerson.isAvailable() && ThirdPerson.ENTITY_AGENT.isCameraEntityExist()) { + ThirdPerson.ENTITY_AGENT.onRenderTickStart(now, period, event.partialTick); + ThirdPerson.CAMERA_AGENT.onRenderTickStart(now, period, event.partialTick); + } + } + + /** + * 当前的 impulse 代表玩家希望前进的方向(世界坐标) + * + *

结合当前玩家实体的朝向重新计算 impulse + */ + private static void onCalculateMoveImpulse(CalculateMoveImpulseEvent event) { + if (ThirdPerson.isAvailable() + && ThirdPersonStatus.isRenderingInThirdPerson() + && ThirdPerson.ENTITY_AGENT.isControlled()) { + var camera = ThirdPerson.CAMERA_AGENT.getRawCamera(); + + // 计算世界坐标系下的向前和向左 impulse + // 视线向量 + var lookImpulse = LMath.toVector3d(camera.getLookVector()).normalize(); + var leftImpulse = LMath.toVector3d(camera.getLeftVector()).normalize(); + + // 水平方向上的视线向量 + var lookImpulseHorizon = + new Vector2d(lookImpulse.x, lookImpulse.z).normalize(event.forwardImpulse); + var leftImpulseHorizon = + new Vector2d(leftImpulse.x, leftImpulse.z).normalize(event.leftImpulse); + lookImpulseHorizon.add(leftImpulseHorizon, ThirdPersonStatus.impulseHorizon); + + // 世界坐标系下的 impulse + lookImpulse.mul(event.forwardImpulse); // 这才是 impulse + leftImpulse.mul(event.leftImpulse); + lookImpulse.add(leftImpulse, ThirdPersonStatus.impulse); + + // impulse 不为0, + final double length = ThirdPersonStatus.impulseHorizon.length(); + if (length > 1E-5) { + if (length > 1.0D) { + ThirdPersonStatus.impulseHorizon.div(length, length); + } + float playerYRot = + ThirdPerson.ENTITY_AGENT + .getRawPlayerEntity() + .getViewYRot(Minecraft.getInstance().getFrameTime()); + + var playerLookHorizon = LMath.directionFromRotationDegree(playerYRot).normalize(); + var playerLeftHorizon = LMath.directionFromRotationDegree(playerYRot - 90).normalize(); + + event.forwardImpulse = (float) (ThirdPersonStatus.impulseHorizon.dot(playerLookHorizon)); + event.leftImpulse = (float) (ThirdPersonStatus.impulseHorizon.dot(playerLeftHorizon)); + } + } + } + + private static boolean onRenderEntity(RenderEntityEvent event) { + if (ThirdPerson.isAvailable() + && ThirdPersonStatus.isRenderingInThirdPerson() + && event.entity == ThirdPerson.ENTITY_AGENT.getRawCameraEntity()) { + return ThirdPersonStatus.shouldRenderCameraEntity(event.partialTick); + } + return true; + } + + private static void onHandleKeybindsStart() { + if (ThirdPerson.isAvailable()) { + var config = ThirdPerson.getConfig(); + if (ThirdPersonStatus.isRenderingInThirdPerson()) { + if (ThirdPerson.ENTITY_AGENT.isInteracting()) { + // 立即更新玩家注视着的目标 Minecraft#hitResult + Minecraft.getInstance().gameRenderer.pick(1f); + } + } + } + } + + /** + * @see MouseHandler#turnPlayer() + */ + private static void onMouseTurnPlayerStart(MouseTurnPlayerStartEvent event) { + if (ThirdPerson.isAvailable() + && ThirdPersonStatus.isAdjustingCameraOffset() + && !ThirdPersonStatus.shouldCameraTurnWithEntity()) { + if (event.accumulatedDX == 0 && event.accumulatedDY == 0) { + return; + } + var config = ThirdPerson.getConfig(); + var window = Minecraft.getInstance().getWindow(); + var screenSize = new Vector2d(window.getScreenWidth(), window.getScreenHeight()); + var scheme = config.getCameraOffsetScheme(); + var mode = scheme.getMode(); + if (mode.isCentered()) { + // 相机在头顶,只能上下调整 + double topOffset = mode.getCenterOffsetRatio(); + topOffset += -event.accumulatedDY / screenSize.y; + topOffset = LMath.clamp(topOffset, -1, 1); + mode.setCenterOffsetRatio(topOffset); + } else { + // 相机没固定在头顶,可以上下左右调整 + var offset = mode.getSideOffsetRatio(new Vector2d()); + offset.sub(new Vector2d(event.accumulatedDX, event.accumulatedDY).div(screenSize)); + LMath.clamp(offset, -1, 1); + scheme.setSide(Math.signum(offset.x)); + mode.setSideOffsetRatio(offset); + } + event.cancelDefault(); + } + } + + private static void onEntityTurnStart(EntityTurnStartEvent event) { + if (ThirdPerson.isAvailable() + && ThirdPersonStatus.isRenderingInThirdPerson() + && !ThirdPersonStatus.shouldCameraTurnWithEntity()) { + ThirdPerson.CAMERA_AGENT.turnCamera(event.dYRot, event.dXRot); + event.cancelDefault(); + } + } + + /** + * 重置玩家 + * + * @see ThirdPersonEvents#onClientPlayerRespawn(LocalPlayer, LocalPlayer) + * @see ThirdPersonEvents#onClientPlayerJoin(LocalPlayer) + */ + public static void resetPlayer() { + ThirdPerson.resetFiniteCheckers(); + ThirdPerson.ENTITY_AGENT.reset(); + ThirdPerson.CAMERA_AGENT.reset(); + } + + /** + * 使用滚轮调整距离 + * + * @see ClientRawInputEvent.MouseScrolled + */ + @VersionSensitive(value = "Since architectury-api 10", since = "1.20.2") + private static @NotNull EventResult onMouseScrolled( + @NotNull Minecraft minecraft, double amountX, double amountY) { + int offset = (int) -Math.signum(amountY); + if (offset == 0 || !ThirdPersonStatus.isAdjustingCameraDistance()) { + return EventResult.pass(); + } + var config = ThirdPerson.getConfig(); + double dist = config.getCameraOffsetScheme().getMode().getDistanceLimit(); + dist = config.getDistanceMonoList().offset(dist, offset); + config.getCameraOffsetScheme().getMode().setDistanceLimit(dist); + return EventResult.interruptFalse(); + } + + /** + * @see ThirdPersonKeys#ADJUST_POSITION + */ + @SuppressWarnings("EmptyMethod") + public static void onStartAdjustingCameraOffset() {} + + /** + * @see ThirdPersonKeys#ADJUST_POSITION + */ + public static void onStopAdjustingCameraOffset() { + ThirdPerson.CONFIG_MANAGER.lazySave(); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonKeys.java b/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonKeys.java new file mode 100644 index 00000000..4c5ab450 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonKeys.java @@ -0,0 +1,85 @@ +package com.github.leawind.thirdperson; + +import com.github.leawind.thirdperson.core.rotation.RotateTargetEnum; +import com.github.leawind.thirdperson.util.modkeymapping.ModKeyMapping; +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.Minecraft; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public final class ThirdPersonKeys { + public static final ModKeyMapping ADJUST_POSITION = + ModKeyMapping.of( + getId("adjust_position"), InputConstants.KEY_Z, ThirdPersonConstants.KEY_CATEGORY) + .onDown(ThirdPersonEvents::onStartAdjustingCameraOffset) + .onUp(ThirdPersonEvents::onStopAdjustingCameraOffset); + + public static final ModKeyMapping FORCE_AIMING = + ModKeyMapping.of(getId("force_aiming"), ThirdPersonConstants.KEY_CATEGORY); + + public static final ModKeyMapping TOGGLE_MOD_ENABLE = + ModKeyMapping.of(getId("toggle_mod_enable"), ThirdPersonConstants.KEY_CATEGORY) + .onDown( + () -> { + var config = ThirdPerson.getConfig(); + if (ThirdPersonStatus.isRenderingInThirdPerson()) { + if (config.is_mod_enabled) { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.CAMERA_ROTATION); + } else { + Minecraft.getInstance().gameRenderer.checkEntityPostEffect(null); + ThirdPersonEvents.resetPlayer(); + } + config.is_mod_enabled = !config.is_mod_enabled; + } + }); + + public static final ModKeyMapping OPEN_CONFIG_MENU = + ModKeyMapping.of(getId("open_config_menu"), ThirdPersonConstants.KEY_CATEGORY) + .onDown( + () -> { + var mc = Minecraft.getInstance(); + if (mc.screen == null) { + mc.setScreen(ThirdPerson.CONFIG_MANAGER.getConfigScreen(null)); + } + }); + + public static final ModKeyMapping TOGGLE_SIDE = + ModKeyMapping.of( + getId("toggle_side"), InputConstants.KEY_CAPSLOCK, ThirdPersonConstants.KEY_CATEGORY) + .onDown( + () -> { + var scheme = ThirdPerson.getConfig().getCameraOffsetScheme(); + boolean wasCentered = scheme.isCentered(); + if (wasCentered) { + scheme.toNextSide(); + } + return wasCentered; + }) // + .onHold(() -> ThirdPerson.getConfig().getCameraOffsetScheme().setCentered(true)) + .onPress(() -> ThirdPerson.getConfig().getCameraOffsetScheme().toNextSide()); + + public static final ModKeyMapping TOGGLE_AIMING = + ModKeyMapping.of(getId("toggle_aiming"), ThirdPersonConstants.KEY_CATEGORY) + .onDown( + () -> { + if (ThirdPerson.isAvailable() && ThirdPersonStatus.isRenderingInThirdPerson()) { + ThirdPersonStatus.isToggleToAiming = !ThirdPersonStatus.isToggleToAiming; + } + }); + + public static final ModKeyMapping TOGGLE_PITCH_LOCK = + ModKeyMapping.of(getId("toggle_pitch_lock"), ThirdPersonConstants.KEY_CATEGORY) + .onDown( + () -> { + var config = ThirdPerson.getConfig(); + config.lock_camera_pitch_angle = !config.lock_camera_pitch_angle; + }); + + private static @NotNull String getId(@NotNull String name) { + return "key." + ThirdPersonConstants.MOD_ID + "." + name; + } + + public static void register() { + ModKeyMapping.registerAll(); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonResources.java b/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonResources.java new file mode 100644 index 00000000..8f539e4d --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonResources.java @@ -0,0 +1,15 @@ +package com.github.leawind.thirdperson; + +import com.github.leawind.thirdperson.resources.ItemPredicateManager; +import dev.architectury.registry.ReloadListenerRegistry; +import net.minecraft.server.packs.PackType; + +/** 自定义资源包 */ +public final class ThirdPersonResources { + public static final ItemPredicateManager itemPredicateManager = new ItemPredicateManager(); + + public static void register() { + ReloadListenerRegistry.register( + PackType.CLIENT_RESOURCES, ThirdPersonResources.itemPredicateManager); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonStatus.java b/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonStatus.java new file mode 100644 index 00000000..2e3033fb --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/ThirdPersonStatus.java @@ -0,0 +1,156 @@ +package com.github.leawind.thirdperson; + +import com.github.leawind.thirdperson.core.rotation.RotateTargetEnum; +import com.github.leawind.thirdperson.core.rotation.SmoothTypeEnum; +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2d; +import org.joml.Vector3d; + +public final class ThirdPersonStatus { + /** 移动脉冲 */ + public static final @NotNull Vector3d impulse = new Vector3d(0); + + /** 移动脉冲的水平分量 */ + public static final @NotNull Vector2d impulseHorizon = new Vector2d(0); + + public static int clientTicks = 0; + + /** + * @see ThirdPersonKeys#TOGGLE_AIMING + */ + public static boolean isToggleToAiming = false; + + public static double lastRenderTickTimeStamp = 0; + + /** 上一tick中是否以第三人称视角渲染 mc.options.cameraType.isThirdPerson() */ + public static boolean wasRenderInThirdPersonLastRenderTick = false; + + /** + * 在 ThirdPersonEvents#onPreRender 中更新 + * + * @see ThirdPersonStatus#shouldCameraTurnWithEntity + */ + public static boolean wasShouldCameraTurnWithEntity = false; + + /** 自上次离开狭窄空间以来经过的 tick 数 */ + public static int ticksSinceLeaveNarrowSpace = + ThirdPersonConstants.LEAVE_NARROW_SPACE_DELAY_TICKS; + + /** + * 第三人称下是否强制显示准星 + * + *

false 表示按照原版(不显示) + * + *

true 表示显示准星 + */ + public static boolean forceThirdPersonCrosshair = false; + + public static boolean isPerspectiveInverted = false; + + /** 是否正在调整摄像机偏移量 */ + public static boolean isAdjustingCameraOffset() { + return isAdjustingCameraDistance(); + } + + /** 检查相机距离是否正在调整。 */ + public static boolean isAdjustingCameraDistance() { + return ThirdPerson.isAvailable() + && isRenderingInThirdPerson() + && ThirdPersonKeys.ADJUST_POSITION.isDown(); + } + + /** + * 当前是否以第三人称渲染 + * + *

当前相机模式既不是第一人称,也不是镜像的 + */ + public static boolean isRenderingInThirdPerson() { + var cameraType = Minecraft.getInstance().options.getCameraType(); + return !(cameraType.isFirstPerson() || cameraType.isMirrored()); + } + + /** 当前是否显示准星 */ + public static boolean shouldRenderThirdPersonCrosshair() { + var config = ThirdPerson.getConfig(); + return ThirdPerson.isAvailable() + && isRenderingInThirdPerson() + && (ThirdPerson.ENTITY_AGENT.wasAiming() + ? config.render_crosshair_when_aiming + : config.render_crosshair_when_not_aiming + && (!(ThirdPerson.ENTITY_AGENT.isFallFlying() + && config.hide_crosshair_when_flying))); + } + + /** 根据玩家的按键判断玩家是否想瞄准 */ + public static boolean doesPlayerWantToAim() { + return isToggleToAiming || ThirdPersonKeys.FORCE_AIMING.isDown(); + } + + /** 探测射线是否应当起始于相机处,而非玩家眼睛处 */ + public static boolean shouldPickFromCamera() { + if (!ThirdPerson.ENTITY_AGENT.isCameraEntityExist()) { + return false; + } else if (!ThirdPerson.getConfig().use_camera_pick_in_creative) { + return false; + } + return ThirdPerson.ENTITY_AGENT.getRawCameraEntity() instanceof Player player + && player.isCreative(); + } + + /** + * 是否渲染相机实体 + * + *

当透明度小于阈值,或相机距离实体太近时,不渲染相机实体 + * + * @return 是否渲染相机实体 + */ + public static boolean shouldRenderCameraEntity(float partialTick) { + return ThirdPerson.ENTITY_AGENT.getSmoothOpacity(partialTick) + > ThirdPersonConstants.RENDERED_OPACITY_THRESHOLD_MIN + && ThirdPerson.ENTITY_AGENT.columnDistanceTo( + ThirdPerson.CAMERA_AGENT.getRawCameraPosition(), partialTick) + > ThirdPerson.getConfig().player_invisible_threshold; + } + + /** + * 是否按照透明度渲染相机实体 + * + *

当透明度大于 {@link ThirdPersonConstants#RENDERED_OPACITY_THRESHOLD_MAX} 时,返回false + * + * @return 是否按照透明度渲染相机实体 + */ + public static boolean useCameraEntityOpacity(float partialTick) { + return ThirdPerson.ENTITY_AGENT.getSmoothOpacity(partialTick) + < ThirdPersonConstants.RENDERED_OPACITY_THRESHOLD_MAX; + } + + /** + * 第三人称下,通常是直接用鼠标控制相机的朝向 CameraAgentImpl#relativeRotation,再根据一些因素决定玩家的朝向。 + * + *

当飞行时,实体的旋转目标是相机朝向,且平滑类型是 {@link SmoothTypeEnum#HARD},相当于鼠标直接控制玩家朝向,而相机跟随玩家旋转。这样就可以兼容 Do a + * Barrel Roll + */ + public static boolean shouldCameraTurnWithEntity() { + return ThirdPerson.ENTITY_AGENT.getRotateTarget() == RotateTargetEnum.CAMERA_ROTATION + && ThirdPerson.ENTITY_AGENT.getRotationSmoothType() == SmoothTypeEnum.HARD; + } + + /** 实体是否位于狭窄空间内 */ + public static boolean calcIsInNarrowSpace(Entity entity) { + boolean isInNarrowSpace = true; + var center = new BlockPos(entity.getEyePosition(1)); + ThirdPersonConstants.SURROUNDINGS_MATCHING.rematch( + center, entity.getLevel(), s -> s.isViewBlocking(entity.getLevel(), center)); + + int countT = ThirdPersonConstants.SURROUNDINGS_MATCHING.getMatches("T").count(); + int countM = ThirdPersonConstants.SURROUNDINGS_MATCHING.getMatches("M").count(); + + isInNarrowSpace &= countT >= 3; + isInNarrowSpace &= countM >= 1; + return isInNarrowSpace; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/api/base/GameEvents.java b/common/src/main/java/com/github/leawind/thirdperson/api/base/GameEvents.java new file mode 100644 index 00000000..11edad55 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/api/base/GameEvents.java @@ -0,0 +1,20 @@ +package com.github.leawind.thirdperson.api.base; + +import com.github.leawind.thirdperson.api.client.event.CalculateMoveImpulseEvent; +import com.github.leawind.thirdperson.api.client.event.EntityTurnStartEvent; +import com.github.leawind.thirdperson.api.client.event.MouseTurnPlayerStartEvent; +import com.github.leawind.thirdperson.api.client.event.RenderEntityEvent; +import com.github.leawind.thirdperson.api.client.event.RenderTickStartEvent; +import com.github.leawind.thirdperson.api.client.event.ThirdPersonCameraSetupEvent; +import java.util.function.Consumer; +import java.util.function.Function; + +public final class GameEvents { + public static Consumer thirdPersonCameraSetup = null; + public static Consumer renderTickStart = null; + public static Consumer calculateMoveImpulse = null; + public static Function renderEntity = null; + public static Runnable handleKeybindsStart = null; + public static Consumer mouseTurnPlayerStart = null; + public static Consumer entityTurnStart = null; +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/api/base/ModEvent.java b/common/src/main/java/com/github/leawind/thirdperson/api/base/ModEvent.java new file mode 100644 index 00000000..c05feab7 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/api/base/ModEvent.java @@ -0,0 +1,5 @@ +package com.github.leawind.thirdperson.api.base; + +public interface ModEvent { + boolean set(); +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/api/client/event/CalculateMoveImpulseEvent.java b/common/src/main/java/com/github/leawind/thirdperson/api/client/event/CalculateMoveImpulseEvent.java new file mode 100644 index 00000000..279db313 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/api/client/event/CalculateMoveImpulseEvent.java @@ -0,0 +1,22 @@ +package com.github.leawind.thirdperson.api.client.event; + +import com.github.leawind.thirdperson.api.base.ModEvent; +import net.minecraft.client.player.KeyboardInput; + +public class CalculateMoveImpulseEvent implements ModEvent { + public final KeyboardInput input; + public final float impulseMultiplier; + + public float forwardImpulse = 0; + public float leftImpulse = 0; + + public CalculateMoveImpulseEvent(KeyboardInput input, float impulseMultiplier) { + this.input = input; + this.impulseMultiplier = impulseMultiplier; + } + + @Override + public boolean set() { + return true; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/api/client/event/EntityTurnStartEvent.java b/common/src/main/java/com/github/leawind/thirdperson/api/client/event/EntityTurnStartEvent.java new file mode 100644 index 00000000..cb5ddb5d --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/api/client/event/EntityTurnStartEvent.java @@ -0,0 +1,33 @@ +package com.github.leawind.thirdperson.api.client.event; + +import com.github.leawind.thirdperson.api.base.ModEvent; +import net.minecraft.world.entity.Entity; + +public class EntityTurnStartEvent implements ModEvent { + public final Entity entity; + + public final double dXRot; + public final double dYRot; + + private boolean isDefaultCancelled = false; + + public EntityTurnStartEvent(Entity entity, double dYRot, double dXRot) { + this.entity = entity; + this.dXRot = dXRot; + this.dYRot = dYRot; + } + + /** 取消默认操作 */ + public void cancelDefault() { + isDefaultCancelled = true; + } + + public boolean isDefaultCancelled() { + return isDefaultCancelled; + } + + @Override + public boolean set() { + return false; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/api/client/event/MouseTurnPlayerStartEvent.java b/common/src/main/java/com/github/leawind/thirdperson/api/client/event/MouseTurnPlayerStartEvent.java new file mode 100644 index 00000000..a80f94e0 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/api/client/event/MouseTurnPlayerStartEvent.java @@ -0,0 +1,31 @@ +package com.github.leawind.thirdperson.api.client.event; + +import com.github.leawind.thirdperson.api.base.ModEvent; + +public class MouseTurnPlayerStartEvent implements ModEvent { + /** 累积变化量 */ + public final double accumulatedDX; + + public final double accumulatedDY; + + private boolean isDefaultCancelled = false; + + public MouseTurnPlayerStartEvent(double accumulatedDX, double accumulatedDY) { + this.accumulatedDX = accumulatedDX; + this.accumulatedDY = accumulatedDY; + } + + /** 取消默认操作 */ + public void cancelDefault() { + isDefaultCancelled = true; + } + + public boolean isDefaultCancelled() { + return isDefaultCancelled; + } + + @Override + public boolean set() { + return false; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/api/client/event/RenderEntityEvent.java b/common/src/main/java/com/github/leawind/thirdperson/api/client/event/RenderEntityEvent.java new file mode 100644 index 00000000..e04a44fb --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/api/client/event/RenderEntityEvent.java @@ -0,0 +1,13 @@ +package com.github.leawind.thirdperson.api.client.event; + +import net.minecraft.world.entity.Entity; + +public class RenderEntityEvent { + public final Entity entity; + public final float partialTick; + + public RenderEntityEvent(Entity entity, float partialTick) { + this.entity = entity; + this.partialTick = partialTick; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/api/client/event/RenderTickStartEvent.java b/common/src/main/java/com/github/leawind/thirdperson/api/client/event/RenderTickStartEvent.java new file mode 100644 index 00000000..c05b450c --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/api/client/event/RenderTickStartEvent.java @@ -0,0 +1,16 @@ +package com.github.leawind.thirdperson.api.client.event; + +import com.github.leawind.thirdperson.api.base.ModEvent; + +public final class RenderTickStartEvent implements ModEvent { + public final float partialTick; + + public RenderTickStartEvent(float partialTick) { + this.partialTick = partialTick; + } + + @Override + public boolean set() { + return false; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/api/client/event/ThirdPersonCameraSetupEvent.java b/common/src/main/java/com/github/leawind/thirdperson/api/client/event/ThirdPersonCameraSetupEvent.java new file mode 100644 index 00000000..b3d14c9d --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/api/client/event/ThirdPersonCameraSetupEvent.java @@ -0,0 +1,31 @@ +package com.github.leawind.thirdperson.api.client.event; + +import com.github.leawind.thirdperson.api.base.ModEvent; +import net.minecraft.world.phys.Vec3; + +public final class ThirdPersonCameraSetupEvent implements ModEvent { + public final float partialTick; + public Vec3 pos; + + public float xRot = 0; + public float yRot = 0; + + public ThirdPersonCameraSetupEvent(float partialTick) { + this.partialTick = partialTick; + } + + /** Set camera position */ + public void setPosition(Vec3 pos) { + this.pos = pos; + } + + /** Set camera rotation */ + public void setRotation(float xRot, float yRot) { + this.xRot = xRot; + this.yRot = yRot; + } + + public boolean set() { + return pos != null; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/config/AbstractConfig.java b/common/src/main/java/com/github/leawind/thirdperson/config/AbstractConfig.java new file mode 100644 index 00000000..a7a344f4 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/config/AbstractConfig.java @@ -0,0 +1,133 @@ +package com.github.leawind.thirdperson.config; + +import com.google.gson.annotations.Expose; +import java.util.ArrayList; +import java.util.List; +import net.minecraft.network.chat.Component; + +/** + * 配置的抽象类 + * + *

定义了所有配置选项及其默认值,可以依照此类编辑配置屏幕 + */ +public abstract class AbstractConfig { + // ================================================================================================================ // + // 常用 + @Expose public boolean is_mod_enabled = true; + @Expose public boolean center_offset_when_flying = true; + @Expose public boolean temp_first_person_in_narrow_space = true; + // -------------------------------------------------------------------------------------------------- 玩家旋转 + @Expose public PlayerRotateMode normal_rotate_mode = PlayerRotateMode.INTEREST_POINT; + @Expose public boolean auto_rotate_interacting = true; + @Expose public boolean do_not_rotate_when_eating = true; + @Expose public boolean auto_turn_body_drawing_a_bow = false; + // -------------------------------------------------------------------------------------------------- 玩家实体虚化 + @Expose public boolean player_fade_out_enabled = false; + @Expose public double gaze_opacity = 0.28; + @Expose public double player_invisible_threshold = 0.55; + // -------------------------------------------------------------------------------------------------- 相机到玩家距离调节 + @Expose public int available_distance_count = 16; + @Expose public double camera_distance_min = 0; + @Expose public double camera_distance_max = 4; + // ================================================================================================================ // + // 平滑系数 + @Expose public double flying_smooth_halflife = 0.45; + @Expose public double t2f_transition_halflife = 0.1; + // -------------------------------------------------------------------------------------------------- 调节相机 + @Expose public double adjusting_camera_offset_smooth_halflife = 0.04; + @Expose public double adjusting_distance_smooth_halflife = 0.08; + // -------------------------------------------------------------------------------------------------- 正常模式 + @Expose public double normal_smooth_halflife_horizon = 0.25; + @Expose public double normal_smooth_halflife_vertical = 0.20; + @Expose public double normal_camera_offset_smooth_halflife = 0.08; + @Expose public double normal_distance_smooth_halflife = 0.72; + // -------------------------------------------------------------------------------------------------- 瞄准模式 + @Expose public double aiming_smooth_halflife_horizon = 0.05; + @Expose public double aiming_smooth_halflife_vertical = 0.05; + @Expose public double aiming_camera_offset_smooth_halflife = 0.03; + @Expose public double aiming_distance_smooth_halflife = 0.04; + // ================================================================================================================ // + // 相机偏移 + @Expose public double aiming_fov_divisor = 1.125; + // -------------------------------------------------------------------------------------------------- 正常模式 + @Expose public double normal_max_distance = 1.5625; + @Expose public double normal_offset_x = -0.145; + @Expose public double normal_offset_y = 0.12; + @Expose public boolean normal_is_centered = false; + @Expose public double normal_offset_center = 0.24; + // -------------------------------------------------------------------------------------------------- 瞄准模式 + @Expose public double aiming_max_distance = 0.56; + @Expose public double aiming_offset_x = -0.29; + @Expose public double aiming_offset_y = 0.19; + @Expose public boolean aiming_is_centered = false; + @Expose public double aiming_offset_center = 0.48; + // ================================================================================================================ // + // 物品谓词 + @Expose public boolean determine_aim_mode_by_animation = true; + @Expose public List hold_to_aim_item_patterns = new ArrayList<>(); + @Expose public List use_to_aim_item_patterns = new ArrayList<>(); + @Expose public List use_to_first_person_patterns = new ArrayList<>(); + // ================================================================================================================ // + // 其他 + @Expose public String config_screen_api = "YACL"; + @Expose public CameraDistanceMode camera_distance_mode = CameraDistanceMode.STRAIGHT; + @Expose public double rotate_center_height_offset = 0.3; + @Expose public boolean enable_target_entity_predict = true; + @Expose public boolean skip_vanilla_second_person_camera = true; + @Expose public boolean disable_third_person_bob_view = false; + @Expose public boolean allow_double_tap_sprint = false; + @Expose public boolean lock_camera_pitch_angle = false; + @Expose public boolean use_camera_pick_in_creative = false; + @Expose public double camera_ray_trace_length = 512; + // -------------------------------------------------------------------------------------------------- 准星 + @Expose public boolean render_crosshair_when_not_aiming = true; + @Expose public boolean render_crosshair_when_aiming = true; + @Expose public boolean hide_crosshair_when_flying = true; + + public enum PlayerRotateMode { + INTEREST_POINT("interest_point"), + CAMERA_CROSSHAIR("camera_crosshair"), + MOVING_DIRECTION("moving_direction"), + PARALLEL_WITH_CAMERA("parallel_with_camera"), + NONE("none"), + ; + public static final String KEY = "option.normal_rotate_mode"; + private final String key; + + PlayerRotateMode(String key) { + this.key = key; + } + + public static Component formatter(Enum value) { + return formatter((PlayerRotateMode) value); + } + + public static Component formatter(PlayerRotateMode value) { + return ConfigManager.getText(KEY + "." + value.key); + } + } + + public enum CameraDistanceMode { + PLANE("plane"), + STRAIGHT("straight"); + + public static final String KEY = "option.camera_distance_mode"; + private final String key; + + CameraDistanceMode(String key) { + this.key = key; + } + + public static CameraDistanceMode of(boolean b) { + return b ? PLANE : STRAIGHT; + } + + public static Component formatter(Enum value) { + return formatter((CameraDistanceMode) value); + } + + public static Component formatter(CameraDistanceMode value) { + return ConfigManager.getText(KEY + "." + value.key); + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/config/Config.java b/common/src/main/java/com/github/leawind/thirdperson/config/Config.java new file mode 100644 index 00000000..26b349bd --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/config/Config.java @@ -0,0 +1,106 @@ +package com.github.leawind.thirdperson.config; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.core.cameraoffset.CameraOffsetScheme; +import com.github.leawind.thirdperson.resources.ItemPredicateManager; +import com.github.leawind.thirdperson.util.ItemPredicateUtil; +import com.github.leawind.thirdperson.util.math.monolist.MonoList; +import com.github.leawind.thirdperson.util.math.monolist.StaticMonoList; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import net.minecraft.advancements.critereon.ItemPredicate; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * AbstractConfig 中包含了用户可以直接修改的配置项及默认值。 + * + *

但要在模组中使用这些配置项,还需要进行进一步的处理。 + */ +public class Config extends AbstractConfig { + public static final @NotNull Config DEFAULTS = new Config(); + + private final @NotNull CameraOffsetScheme cameraOffsetScheme = new CameraOffsetScheme(this); + + private final @NotNull Set holdToAimItemPredicates = new HashSet<>(); + private final @NotNull Set useToAimItemPredicates = new HashSet<>(); + private final @NotNull Set useToFirstPersonItemPredicates = new HashSet<>(); + + private @Nullable MonoList distanceMonoList; + + public Config() { + update(); + } + + /** 在配置项发生变化时更新 */ + public void update() { + // 确保不存在非法值 + if (normal_rotate_mode == null) { + normal_rotate_mode = DEFAULTS.normal_rotate_mode; + } + + updateDistancesMonoList(); + updateItemPredicates(); + } + + /** 更新相机到玩家的距离的可调挡位们 */ + public void updateDistancesMonoList() { + ThirdPerson.LOGGER.debug("Updating distances mono list"); + distanceMonoList = + StaticMonoList.of( + available_distance_count, + camera_distance_min, + camera_distance_max, + i -> i * i, + Math::sqrt); + } + + /** + * @see ItemPredicateManager#apply + */ + public void updateItemPredicates() { + holdToAimItemPredicates.clear(); + useToAimItemPredicates.clear(); + useToFirstPersonItemPredicates.clear(); + + int count; + count = + ItemPredicateUtil.addToSet("minecraft", holdToAimItemPredicates, hold_to_aim_item_patterns); + if (count > 0) { + ThirdPerson.LOGGER.info("Loaded {} hold_to_aim item patterns from configuration", count); + } + count = + ItemPredicateUtil.addToSet("minecraft", useToAimItemPredicates, use_to_aim_item_patterns); + if (count > 0) { + ThirdPerson.LOGGER.info("Loaded {} use_to_aim item patterns from configuration", count); + } + count = + ItemPredicateUtil.addToSet( + "minecraft", useToFirstPersonItemPredicates, use_to_first_person_patterns); + if (count > 0) { + ThirdPerson.LOGGER.info( + "Loaded {} use_to_first_person item patterns from configuration", count); + } + } + + public @NotNull Set getHoldToAimItemPredicates() { + return holdToAimItemPredicates; + } + + public @NotNull Set getUseToAimItemPredicates() { + return useToAimItemPredicates; + } + + public @NotNull Set getUseToFirstPersonItemPredicates() { + return useToFirstPersonItemPredicates; + } + + public @NotNull CameraOffsetScheme getCameraOffsetScheme() { + return cameraOffsetScheme; + } + + public @NotNull MonoList getDistanceMonoList() { + return Objects.requireNonNull(distanceMonoList); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/config/ConfigManager.java b/common/src/main/java/com/github/leawind/thirdperson/config/ConfigManager.java new file mode 100644 index 00000000..4b5351c4 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/config/ConfigManager.java @@ -0,0 +1,136 @@ +package com.github.leawind.thirdperson.config; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.ThirdPersonConstants; +import com.github.leawind.thirdperson.screen.ConfigScreenBuilder; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Timer; +import java.util.TimerTask; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import org.apache.commons.io.FileUtils; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ConfigManager { + private final Gson GSON = + new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .setPrettyPrinting() + .disableHtmlEscaping() + .create(); + private final Timer lazySaveTimer = new Timer(); + private @NotNull Config config = new Config(); + private boolean isLazySaveScheduled = false; + + public ConfigManager() {} + + /** + * 加载配置 + * + *

如果找不到文件,则保存一份。 + * + *

如果失败,则记录错误到日志 + */ + public void tryLoad() { + ThirdPerson.LOGGER.debug("Trying loading config from {}", ThirdPersonConstants.CONFIG_FILE); + try { + ThirdPersonConstants.CONFIG_FILE.getParentFile().mkdirs(); + if (ThirdPersonConstants.CONFIG_FILE.exists()) { + load(); + ThirdPerson.LOGGER.info("Config is loaded from {}", ThirdPersonConstants.CONFIG_FILE); + } else { + ThirdPerson.LOGGER.info("Config not found, creating one."); + trySave(); + } + } catch (IOException e) { + ThirdPerson.LOGGER.error("Failed to load config.", e); + } catch (JsonSyntaxException e) { + ThirdPerson.LOGGER.error("Config file is broken.", e); + } + config.update(); + } + + /** + * 惰性保存 + * + *

两次保存时间间隔至少为 {@link ThirdPersonConstants#CONFIG_LAZY_SAVE_DELAY} + */ + public void lazySave() { + if (!isLazySaveScheduled) { + isLazySaveScheduled = true; + lazySaveTimer.schedule( + new TimerTask() { + @Override + public void run() { + trySave(); + isLazySaveScheduled = false; + } + }, + ThirdPersonConstants.CONFIG_LAZY_SAVE_DELAY); + } + } + + /** + * 尝试保存配置文件 + * + *

如果失败,则记录错误到日志 + */ + public void trySave() { + ThirdPerson.LOGGER.debug("Trying saving config to {}", ThirdPersonConstants.CONFIG_FILE); + try { + save(); + ThirdPerson.LOGGER.info("Config is saved."); + } catch (IOException e) { + ThirdPerson.LOGGER.error("Failed to save config.", e); + } + config.update(); + } + + /** 直接读取配置文件 */ + public void load() throws IOException { + config = + GSON.fromJson( + Files.readString(ThirdPersonConstants.CONFIG_FILE.toPath(), StandardCharsets.UTF_8), + Config.class); + } + + /** 直接保存配置文件 */ + public void save() throws IOException { + FileUtils.writeStringToFile( + ThirdPersonConstants.CONFIG_FILE, GSON.toJson(this.config), StandardCharsets.UTF_8); + } + + /** 获取配置屏幕 */ + public @Nullable Screen getConfigScreen(@Nullable Screen parent) { + var builder = ConfigScreenBuilder.getBuilder(); + if (builder == null) { + ThirdPerson.LOGGER.warn("No config screen builder available."); + return null; + } + ThirdPerson.LOGGER.debug("Building config screen"); + return builder.build(config, parent); + } + + /** 获取配置对象 */ + public @NotNull Config getConfig() { + return this.config; + } + + /** + * 在可翻译文本的键前加上mod_id前缀 + * + * @param name 键名 + * @return ${mod_id}.${id} + */ + @Contract(value = "_ -> new", pure = true) + public static @NotNull Component getText(@NotNull String name) { + return Component.translatable(ThirdPersonConstants.MOD_ID + "." + name); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/core/AimingTargetComparator.java b/common/src/main/java/com/github/leawind/thirdperson/core/AimingTargetComparator.java new file mode 100644 index 00000000..6924da5d --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/core/AimingTargetComparator.java @@ -0,0 +1,35 @@ +package com.github.leawind.thirdperson.core; + +import com.github.leawind.thirdperson.util.math.LMath; +import java.util.Comparator; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector3d; + +/** + * 在预测目标实体时,判断两个实体的优先级 + * + * @see CameraAgent#predictTargetEntity(float) + */ +public record AimingTargetComparator(Vec3 pos, Vector3d viewVector) implements Comparator { + @Override + public int compare(Entity e1, Entity e2) { + return (int) Math.signum(getCost(e1) - getCost(e2)); + } + + /** 计算一个目标实体的代价,值越低越优先 */ + public double getCost(@NotNull Entity entity) { + var entityPos = entity.getPosition(1); + var vectorToTarget = LMath.toVector3d(entityPos.subtract(pos)); + if (vectorToTarget.length() < 1e-5) { + return 0; + } + vectorToTarget.normalize(); + double dist = pos.distanceTo(entityPos); + double angrad = Math.acos(viewVector.dot(vectorToTarget)); + double angdeg = Math.toDegrees(angrad); + return Math.pow(dist, 2) * Math.pow(angdeg, 2.5); + // return dist * 2 + angdeg * 5; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/core/CameraAgent.java b/common/src/main/java/com/github/leawind/thirdperson/core/CameraAgent.java new file mode 100644 index 00000000..270ac289 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/core/CameraAgent.java @@ -0,0 +1,687 @@ +package com.github.leawind.thirdperson.core; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.ThirdPersonConstants; +import com.github.leawind.thirdperson.ThirdPersonStatus; +import com.github.leawind.thirdperson.api.client.event.ThirdPersonCameraSetupEvent; +import com.github.leawind.thirdperson.config.AbstractConfig; +import com.github.leawind.thirdperson.mixin.CameraInvoker; +import com.github.leawind.thirdperson.mixin.ClientLevelInvoker; +import com.github.leawind.thirdperson.mixin.GameRendererInvoker; +import com.github.leawind.thirdperson.util.FiniteChecker; +import com.github.leawind.thirdperson.util.annotation.VersionSensitive; +import com.github.leawind.thirdperson.util.math.LMath; +import com.github.leawind.thirdperson.util.math.Zone; +import com.github.leawind.thirdperson.util.math.smoothvalue.ExpSmoothDouble; +import com.github.leawind.thirdperson.util.math.smoothvalue.ExpSmoothVector2d; +import com.github.leawind.thirdperson.util.math.smoothvalue.ExpSmoothVector3d; +import com.google.common.collect.Lists; +import java.util.List; +import java.util.Objects; +import net.minecraft.client.Camera; +import net.minecraft.client.CameraType; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.projectile.ProjectileUtil; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector2d; +import org.joml.Vector3d; + +public class CameraAgent { + public final FiniteChecker FINITE_CHECKER = + new FiniteChecker(err -> ThirdPerson.LOGGER.error(err.toString())); + + private final @NotNull Minecraft minecraft; + private final @NotNull ExpSmoothVector3d smoothRotateCenter; + private final @NotNull Camera tempCamera = new Camera(); + private final @NotNull Vector2d relativeRotation = new Vector2d(0); + + /** 相机偏移量 */ + private final @NotNull ExpSmoothVector2d smoothOffsetRatio; + + /** + * 虚相机到平滑眼睛外壳的距离系数 + * + *

外壳的半径是 {@link EntityAgent#getBodyRadius()} + */ + private final @NotNull ExpSmoothDouble smoothDistance; + + /** + * 平滑变化的视野大小乘数 + * + * @see AbstractConfig#aiming_fov_divisor + * @see CameraAgent#getSmoothFovDivisor() + */ + private final @NotNull ExpSmoothDouble smoothFovDivisor; + + /** 在 {@link CameraAgent#onRenderTickStart} 中更新 */ + private @NotNull HitResult hitResult = + BlockHitResult.miss(Vec3.ZERO, Direction.EAST, BlockPos.ZERO); + + public CameraAgent(@NotNull Minecraft minecraft) { + this.minecraft = minecraft; + smoothRotateCenter = new ExpSmoothVector3d(); + smoothOffsetRatio = new ExpSmoothVector2d(); + smoothDistance = new ExpSmoothDouble(); + smoothFovDivisor = new ExpSmoothDouble(); + smoothFovDivisor.set(1D); + } + + /** 重置各种属性 */ + public void reset() { + ThirdPerson.LOGGER.debug("Reset CameraAgent"); + + smoothOffsetRatio.setValue(0, 0); + smoothDistance.set(0D); + smoothFovDivisor.set(1D); + + if (ThirdPerson.ENTITY_AGENT.isCameraEntityExist()) { + smoothRotateCenter.set(getRotateCenterTarget(1)); + var entity = ThirdPerson.ENTITY_AGENT.getRawCameraEntity(); + relativeRotation.set(-entity.getXRot(), entity.getYRot() - 180); + } + } + + public void checkGameStatus() { + if (minecraft.options.getCameraType() == CameraType.FIRST_PERSON) { + ThirdPersonStatus.isPerspectiveInverted = + smoothDistance.get() > ThirdPersonConstants.FIRST_PERSON_TRANSITION_END_THRESHOLD; + } + } + + /** 渲染前 */ + public void onRenderTickStart(double now, double period, float partialTick) { + if (!minecraft.isPaused() && ThirdPersonStatus.isRenderingInThirdPerson()) { + // mc 没有暂停,且正在以第三人称渲染 + + // 更新探测结果 + hitResult = pick(getPickRange()); + + updateSmoothVirtualDistance(period); + updateSmoothOffsetRatio(period); + updateSmoothFovMultiplier(period); + + if (ThirdPersonStatus.shouldCameraTurnWithEntity()) { + // 将相机朝向与相机实体朝向同步 + var rot = ThirdPerson.ENTITY_AGENT.getRawRotation(partialTick); + FINITE_CHECKER.checkOnce(rot.x, rot.y); + relativeRotation.set(-rot.x, rot.y - 180); + } + } + } + + public double getSmoothFovDivisor() { + return smoothFovDivisor.get(); + } + + /** 渲染过程中放置相机 */ + public void onCameraSetup(@NotNull ThirdPersonCameraSetupEvent event) { + updateTempCameraRotationPosition(event.partialTick); + event.setPosition(tempCamera.getPosition()); + + float yRot = tempCamera.getYRot(); + float xRot = tempCamera.getXRot(); + FINITE_CHECKER.checkOnce(xRot, yRot); + + event.setRotation(xRot, yRot); + } + + /** 不平滑的旋转中心 */ + public Vector3d getRotateCenterTarget(float partialTick) { + var config = ThirdPerson.getConfig(); + var entity = ThirdPerson.ENTITY_AGENT.getRawCameraEntity(); + var eyePosition = entity.getEyePosition(partialTick); + return LMath.toVector3d( + eyePosition.with(Direction.Axis.Y, eyePosition.y + config.rotate_center_height_offset)); + } + + /** 防止旋转中心穿墙 */ + public boolean limitRotateCenter(Vector3d rotateCenter, float partialTick) { + var entity = ThirdPerson.ENTITY_AGENT.getRawCameraEntity(); + var smoothEyePosition = LMath.toVec3(smoothRotateCenter.get(partialTick)); + var eyePosition = + new Vec3(smoothEyePosition.x, entity.getEyePosition(partialTick).y, smoothEyePosition.z); + var limit = + Zone.ofAuto(eyePosition.y, rotateCenter.y) + .expendRadius(ThirdPersonConstants.ROTATE_CENTER_RADIUS); + + BlockHitResult hitResult; + Vec3 pickEnd; + + pickEnd = new Vec3(eyePosition.x, limit.max, eyePosition.z); + hitResult = + entity + .getLevel() + .clip( + new ClipContext( + eyePosition, + pickEnd, + ThirdPersonConstants.CAMERA_OBSTACLE_BLOCK_SHAPE_GETTER, + ClipContext.Fluid.NONE, + entity)); + if (hitResult.getType() == HitResult.Type.BLOCK) { + limit = limit.withMax(hitResult.getLocation().y); + } + + pickEnd = new Vec3(eyePosition.x, limit.min, eyePosition.z); + hitResult = + entity + .getLevel() + .clip( + new ClipContext( + eyePosition, + pickEnd, + ThirdPersonConstants.CAMERA_OBSTACLE_BLOCK_SHAPE_GETTER, + ClipContext.Fluid.NONE, + entity)); + if (hitResult.getType() == HitResult.Type.BLOCK) { + limit = limit.withMin(hitResult.getLocation().y); + } + + for (int i = 0; i < 4; i++) { + final double offsetX = 0.3 * ((i & 1) * 2 - 1); + final double offsetZ = 0.3 * ((i >> 1 & 1) * 2 - 1); + + pickEnd = new Vec3(eyePosition.x + offsetX, limit.max, eyePosition.z + offsetZ); + hitResult = + entity + .getLevel() + .clip( + new ClipContext( + eyePosition, + pickEnd, + ThirdPersonConstants.CAMERA_OBSTACLE_BLOCK_SHAPE_GETTER, + ClipContext.Fluid.NONE, + entity)); + if (hitResult.getType() == HitResult.Type.BLOCK) { + limit = limit.withMax(hitResult.getLocation().y); + } + + pickEnd = new Vec3(eyePosition.x + offsetX, limit.min, eyePosition.z + offsetZ); + hitResult = + entity + .getLevel() + .clip( + new ClipContext( + eyePosition, + pickEnd, + ThirdPersonConstants.CAMERA_OBSTACLE_BLOCK_SHAPE_GETTER, + ClipContext.Fluid.NONE, + entity)); + if (hitResult.getType() == HitResult.Type.BLOCK) { + limit = limit.withMin(hitResult.getLocation().y); + } + } + + limit = limit.squeezeSafely(ThirdPersonConstants.ROTATE_CENTER_RADIUS); + double newY = limit.nearest(rotateCenter.y); + boolean result = newY != rotateCenter.y; + rotateCenter.y = newY; + return result; + } + + /** 获取平滑的相机旋转中心 */ + public @NotNull Vector3d getRotateCenterFinally(float partialTick) { + var rotateCenter = smoothRotateCenter.get(partialTick); + var smoothFactor = smoothRotateCenter.smoothFactor; + + boolean isHorizontalZero = smoothFactor.x * smoothFactor.z == 0; + boolean isVerticalZero = smoothFactor.y == 0; + if (isHorizontalZero || isVerticalZero) { + var rotateCenterTarget = getRotateCenterTarget(partialTick); + rotateCenter = + new Vector3d( + isHorizontalZero ? rotateCenterTarget.x : rotateCenter.x, + isVerticalZero ? rotateCenterTarget.y : rotateCenter.y, + isHorizontalZero ? rotateCenterTarget.z : rotateCenter.z); + } + + if (limitRotateCenter(rotateCenter, partialTick)) { + smoothRotateCenter.target.y = rotateCenter.y; + } + return rotateCenter; + } + + public void onClientTickStart() { + var config = ThirdPerson.getConfig(); + + final Vector3d halflife; + if (minecraft.options.getCameraType() == CameraType.FIRST_PERSON) { + halflife = new Vector3d(0); + } else if (ThirdPerson.ENTITY_AGENT.isFallFlying()) { + halflife = new Vector3d(config.flying_smooth_halflife); + } else { + halflife = config.getCameraOffsetScheme().getMode().getEyeSmoothHalflife(); + } + final double dist = + getRotateCenterFinally(1).distance(ThirdPerson.CAMERA_AGENT.getRawCameraPosition()); + halflife.mul(Math.pow(dist, 0.5) * ThirdPersonConstants.EYE_HALFLIFE_MULTIPLIER); + smoothRotateCenter.setHalflife(halflife); + + smoothRotateCenter.setTarget(getRotateCenterTarget(1)); + + smoothRotateCenter.update(ThirdPersonConstants.VANILLA_CLIENT_TICK_TIME); + } + + /** 获取原版相机对象 */ + public @NotNull Camera getRawCamera() { + return Objects.requireNonNull(Minecraft.getInstance().gameRenderer.getMainCamera()); + } + + /** 获取原始相机位置 */ + public @NotNull Vector3d getRawCameraPosition() { + return LMath.toVector3d(getRawCamera().getPosition()); + } + + /** 第三人称相机朝向 */ + public @NotNull Vector2d getRotation() { + return new Vector2d(-relativeRotation.x, (relativeRotation.y % 360 - 180)); + } + + public void setRotation(Vector2d rot) { + relativeRotation.x = -rot.x; + relativeRotation.y = rot.y % 360 - 180; + } + + public Vector2d getRawRotation() { + var camera = ThirdPerson.CAMERA_AGENT.getRawCamera(); + return new Vector2d(camera.getXRot(), camera.getYRot()); + } + + /** + * 玩家控制的相机旋转 + * + * @param dYRot 方向角变化量 + * @param dXRot 俯仰角变化量 + */ + public void turnCamera(double dYRot, double dXRot) { + FINITE_CHECKER.checkOnce(dYRot, dXRot); + var config = ThirdPerson.getConfig(); + if (config.is_mod_enabled && !ThirdPersonStatus.isAdjustingCameraOffset()) { + if (dYRot != 0 || dXRot != 0) { + double yRot = getRelativeRotation().y + dYRot; + yRot %= 360f; + double xRot; + if (config.lock_camera_pitch_angle) { + xRot = getRelativeRotation().x; + } else { + xRot = getRelativeRotation().x - dXRot; + xRot = + LMath.clamp( + xRot, + -ThirdPersonConstants.CAMERA_PITCH_DEGREE_LIMIT, + ThirdPersonConstants.CAMERA_PITCH_DEGREE_LIMIT); + } + relativeRotation.set(xRot, yRot); + } + } + } + + /** 获取相对旋转角度 */ + public @NotNull Vector2d getRelativeRotation() { + return relativeRotation; + } + + /** render tick 开始时,相机的 hitResult */ + public @NotNull HitResult getHitResult() { + return hitResult; + } + + public double getPickRange() { + return ThirdPerson.ENTITY_AGENT.getBodyRadius() + + smoothDistance.get() + + ThirdPerson.getConfig().camera_ray_trace_length; + } + + /** + * 从相机出发探测所选方块或实体。 + * + *

当探测不到时,返回的是{@link HitResult.Type#MISS}类型。坐标将为探测终点 + * + * @param pickRange 探测距离限制 + */ + @VersionSensitive + public @NotNull HitResult pick(double pickRange) { + var cameraPos = getRawCamera().getPosition(); + + var blockHitResult = pickBlock(pickRange); + double blockDistance = cameraPos.distanceTo(blockHitResult.getLocation()); + + pickRange = Math.min(pickRange, blockDistance + 1); + + var entityHitResult = pickEntity(pickRange); + + if (entityHitResult != null) { + double entityDistance = cameraPos.distanceTo(entityHitResult.getLocation()); + if (entityDistance < blockDistance) { + return entityHitResult; + } + } + return blockHitResult; + } + + /** + * 根据相机的视线确定所选实体 + * + *

如果探测不到就返回空值 + * + * @param pickRange 探测距离 + */ + @VersionSensitive + public @Nullable EntityHitResult pickEntity(double pickRange) { + if (!ThirdPerson.ENTITY_AGENT.isCameraEntityExist()) { + return null; + } + + var cameraEntity = ThirdPerson.ENTITY_AGENT.getRawCameraEntity(); + var camera = getRawCamera(); + var viewVector = new Vec3(camera.getLookVector()); + var pickFrom = camera.getPosition(); + var pickTo = viewVector.scale(pickRange).add(pickFrom); + var aabb = new AABB(pickFrom, pickTo); + + return ProjectileUtil.getEntityHitResult( + cameraEntity, + pickFrom, + pickTo, + aabb, + target -> !target.isSpectator() && target.isPickable(), + pickRange); + } + + /** + * 根据相机的视线探测方块 + * + * @param pickRange 从相机出发的探测距离 + * @param blockShape 方块形状获取器 + * @param fluidShape 液体形状获取器 + */ + public @NotNull BlockHitResult pickBlock( + double pickRange, + @NotNull ClipContext.Block blockShape, + @NotNull ClipContext.Fluid fluidShape) { + var camera = getRawCamera(); + + var pickFrom = camera.getPosition(); + var viewVector = new Vec3(camera.getLookVector()); + var pickTo = pickFrom.add(viewVector.scale(pickRange)); + + var cameraEntity = ThirdPerson.ENTITY_AGENT.getRawCameraEntity(); + return cameraEntity + .getLevel() + .clip(new ClipContext(pickFrom, pickTo, blockShape, fluidShape, cameraEntity)); + } + + /** + * 瞄准时使用的方块形状获取器是 {@link ClipContext.Block#COLLIDER},不包含草 + * + *

非瞄准时使用的方块形状获取器是 {@link ClipContext.Block#OUTLINE} + * + *

当探测不到方块时,返回的是 {@link HitResult.Type#MISS} 类型,坐标将为探测终点,即 相机位置 + 视线向量.normalize(探测距离) + * + * @param pickRange 从相机出发的探测距离 + */ + @VersionSensitive + public @NotNull BlockHitResult pickBlock(double pickRange) { + return pickBlock( + pickRange, + ThirdPerson.ENTITY_AGENT.wasAiming() + ? ClipContext.Block.COLLIDER + : ClipContext.Block.OUTLINE, + ClipContext.Fluid.NONE); + } + + /** 相机是否正在注视某个实体(无视其他实体或方块) */ + @VersionSensitive + public boolean isLookingAt(@NotNull Entity entity) { + var from = getRawCamera().getPosition(); + var to = from.add(new Vec3(getRawCamera().getLookVector()).scale(getPickRange())); + var aabb = entity.getBoundingBox(); + return aabb.contains(from) || aabb.clip(from, to).isPresent(); + } + + /** + * 预测玩家可能想要射击的目标实体 + * + * @return 玩家当前想要使用弓箭射击的实体是哪个 + */ + public @Nullable Entity predictTargetEntity(float partialTick) { + if (hitResult.getType() == HitResult.Type.BLOCK) { + return null; + } + + var config = ThirdPerson.getConfig(); + + // 候选目标实体 + List candidateTargets = Lists.newArrayList(); + + var cameraPos = getRawCamera().getPosition(); + var cameraRot = getRotation(); + var cameraViewVector = LMath.directionFromRotationDegree(cameraRot).normalize(); + + if (ThirdPerson.ENTITY_AGENT.isControlled()) { + var playerEntity = ThirdPerson.ENTITY_AGENT.getRawPlayerEntity(); + var clientLevel = (ClientLevel) playerEntity.getLevel(); + + var entityGetter = ((ClientLevelInvoker) clientLevel).invokeGetEntityGetter(); + for (var target : entityGetter.getAll()) { + if (!(target instanceof LivingEntity)) { + continue; + } + + // 排除距离太近和太远的 + double distance = target.distanceTo(playerEntity); + if (distance < 2 || distance > config.camera_ray_trace_length) { + continue; + } + + if (!target.is(playerEntity)) { + var targetPos = target.getPosition(partialTick); + var bottomY = + LMath.toVector3d(targetPos.with(Direction.Axis.Y, target.getBoundingBox().minY)); + var vectorToBottom = + new Vector3d(bottomY).sub(ThirdPerson.ENTITY_AGENT.getRawEyePosition(partialTick)); + if (LMath.rotationDegreeFromDirection(vectorToBottom).x < cameraRot.x) { + continue; + } + var vectorToTarget = LMath.toVector3d(targetPos.subtract(cameraPos)).normalize(); + FiniteChecker.assertFinite(vectorToTarget.x, vectorToTarget.y, vectorToTarget.z); + double angrad = Math.acos(cameraViewVector.dot(vectorToTarget)); + if (Math.toDegrees(angrad) < ThirdPersonConstants.TARGET_PREDICTION_DEGREES_LIMIT) { + candidateTargets.add(target); + } + } + } + } + if (!candidateTargets.isEmpty()) { + candidateTargets.sort(new AimingTargetComparator(cameraPos, cameraViewVector)); + return candidateTargets.get(0); + } + return null; + } + + /** + * 计算临时相机实际朝向和位置 + * + *

关于防止穿墙,参考 net.minecraft.client.Camera#getMaxZoom(double) + */ + private void updateTempCameraRotationPosition(float partialTick) { + ((CameraInvoker) tempCamera) + .invokeSetRotation((float) (relativeRotation.y + 180), (float) -relativeRotation.x); + + var minecraft = Minecraft.getInstance(); + var cameraDistanceMode = ThirdPerson.getConfig().camera_distance_mode; + + // 垂直视野角度一半(弧度制) + double aspectRatio = + (double) minecraft.getWindow().getWidth() / minecraft.getWindow().getHeight(); + double fov = + ((GameRendererInvoker) minecraft.gameRenderer) + .invokeGetFov(getRawCamera(), partialTick, true); + double verticalRadianHalf = Math.toRadians(fov) / 2; + + double heightHalf = + Math.tan(verticalRadianHalf) * ThirdPersonConstants.VANILLA_NEAR_PLANE_DISTANCE; + double widthHalf = aspectRatio * heightHalf; + + // 从旋转中心到相机的方向 + Vector3d direction; + { + var forward = LMath.toVector3d(tempCamera.getLookVector()); + var left = LMath.toVector3d(tempCamera.getLeftVector()); + var up = LMath.toVector3d(tempCamera.getUpVector()); + + double verticalFovHalf = Math.toRadians(fov); + double horizontalFovHalf = + 2 * Math.atan(widthHalf / ThirdPersonConstants.VANILLA_NEAR_PLANE_DISTANCE); + + var offsetRatio = smoothOffsetRatio.get(partialTick); + { + var absPitchDegree = Math.abs(relativeRotation.x); + var multiplier = + (absPitchDegree > ThirdPersonConstants.CAMERA_OFFSET_SQUEEZE_PITCH_THRESHOLD) + ? (90 - absPitchDegree) + / (90 - ThirdPersonConstants.CAMERA_OFFSET_SQUEEZE_PITCH_THRESHOLD) + : 1; + offsetRatio.mul(multiplier); + } + + double offsetX = offsetRatio.x; + double offsetY = offsetRatio.y; + + direction = + forward.sub( + up.mul(offsetY * Math.tan(verticalFovHalf / 2)) + .add(left.mul(offsetX * Math.tan(horizontalFovHalf / 2)))); + if (cameraDistanceMode == AbstractConfig.CameraDistanceMode.STRAIGHT) { + direction.normalize(); + FiniteChecker.assertFinite(direction.x, direction.y, direction.z); + } + } + + var rotateCenterVector3d = getRotateCenterFinally(partialTick); + double bodyRadius = ThirdPerson.ENTITY_AGENT.getBodyRadius(); + var cameraPosition = + LMath.toVec3(rotateCenterVector3d.sub(direction.mul(bodyRadius + smoothDistance.get()))); + ((CameraInvoker) tempCamera).invokeSetPosition(cameraPosition); + // 防止穿墙 + { + var rotateCenter = LMath.toVec3(getRotateCenterFinally(partialTick)); + var entity = ThirdPerson.ENTITY_AGENT.getRawCameraEntity(); + if (entity.isSpectator() && ThirdPerson.ENTITY_AGENT.isEyeInWall(ClipContext.Block.VISUAL)) { + return; + } + var rotateCenterToCamera = rotateCenter.vectorTo(cameraPosition); + double initDistance = rotateCenterToCamera.length(); + if (initDistance < 1e-5) { + return; + } + + double limit = initDistance; + for (int i = 0; i < 8; ++i) { + double offsetX = ThirdPersonConstants.CAMERA_THROUGH_WALL_DETECTION * ((i & 1) * 2 - 1); + double offsetY = + ThirdPersonConstants.CAMERA_THROUGH_WALL_DETECTION * ((i >> 1 & 1) * 2 - 1); + double offsetZ = + ThirdPersonConstants.CAMERA_THROUGH_WALL_DETECTION * ((i >> 2 & 1) * 2 - 1); + + var pickFrom = rotateCenter.add(offsetX, offsetY, offsetZ); + var pickTo = pickFrom.add(rotateCenterToCamera); + + var hitResult = + entity + .getLevel() + .clip( + new ClipContext( + pickFrom, + pickTo, + ThirdPersonConstants.CAMERA_OBSTACLE_BLOCK_SHAPE_GETTER, + ClipContext.Fluid.NONE, + ThirdPerson.ENTITY_AGENT.getRawCameraEntity())); + if (hitResult.getType() != HitResult.Type.MISS) { + limit = Math.min(limit, hitResult.getLocation().distanceTo(pickFrom)); + } + } + if (limit < initDistance) { + switch (cameraDistanceMode) { + case PLANE -> + smoothDistance.setValue(Math.max(0, smoothDistance.get() + limit - initDistance)); + case STRAIGHT -> smoothDistance.setValue(Math.max(0, limit - bodyRadius)); + default -> + throw new IllegalStateException( + "Invalid camera distance mode: " + cameraDistanceMode); + } + var limitedPosition = rotateCenter.add(rotateCenterToCamera.scale(limit / initDistance)); + ((CameraInvoker) tempCamera).invokeSetPosition(limitedPosition); + } + } + } + + /** 平滑更新距离 */ + private void updateSmoothVirtualDistance(double period) { + var config = ThirdPerson.getConfig(); + var mode = config.getCameraOffsetScheme().getMode(); + + boolean isAdjusting = ThirdPersonStatus.isAdjustingCameraDistance(); + var BODY_RADIUS = ThirdPerson.ENTITY_AGENT.getBodyRadius(); + + if (minecraft.options.getCameraType() == CameraType.FIRST_PERSON) { + // 当前的目标是第一人称 + smoothDistance.setHalflife(config.t2f_transition_halflife); + smoothDistance.setTarget(-BODY_RADIUS * 0.5); + } else { + // 当前的目标不是第一人称 + smoothDistance.setHalflife( + isAdjusting + ? config.adjusting_distance_smooth_halflife + : mode.getDistanceSmoothHalflife()); + smoothDistance.setTarget( + (mode.getDistanceLimit() * ThirdPerson.ENTITY_AGENT.vehicleTotalSizeCached + BODY_RADIUS) + * getSmoothFovDivisor() + - BODY_RADIUS); + } + smoothDistance.update(period); + FINITE_CHECKER.checkOnce(smoothDistance.get()); + } + + /** 平滑更新相机偏移量 */ + private void updateSmoothOffsetRatio(double period) { + var config = ThirdPerson.getConfig(); + var mode = config.getCameraOffsetScheme().getMode(); + + if (ThirdPersonStatus.isAdjustingCameraOffset()) { + smoothOffsetRatio.setHalflife(config.adjusting_camera_offset_smooth_halflife); + } else { + smoothOffsetRatio.setHalflife(mode.getOffsetSmoothHalflife()); + } + + if (config.center_offset_when_flying && ThirdPerson.ENTITY_AGENT.isFallFlying()) { + smoothOffsetRatio.setTarget(0, 0); + } else { + smoothOffsetRatio.setTarget(mode.getOffsetRatio()); + } + smoothOffsetRatio.update(period); + } + + /** 平滑更新 FOV 乘数 */ + private void updateSmoothFovMultiplier(double period) { + var config = ThirdPerson.getConfig(); + + smoothFovDivisor.setHalflife( + config.getCameraOffsetScheme().getMode().getDistanceSmoothHalflife()); + smoothFovDivisor.setTarget( + ThirdPerson.ENTITY_AGENT.wasAiming() ? config.aiming_fov_divisor : 1); + smoothFovDivisor.update(period); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/core/EntityAgent.java b/common/src/main/java/com/github/leawind/thirdperson/core/EntityAgent.java new file mode 100644 index 00000000..f0c560fb --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/core/EntityAgent.java @@ -0,0 +1,435 @@ +package com.github.leawind.thirdperson.core; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.ThirdPersonConstants; +import com.github.leawind.thirdperson.ThirdPersonResources; +import com.github.leawind.thirdperson.ThirdPersonStatus; +import com.github.leawind.thirdperson.core.rotation.RotateStrategy; +import com.github.leawind.thirdperson.core.rotation.RotateTargetEnum; +import com.github.leawind.thirdperson.core.rotation.SmoothTypeEnum; +import com.github.leawind.thirdperson.util.FiniteChecker; +import com.github.leawind.thirdperson.util.ItemPredicateUtil; +import com.github.leawind.thirdperson.util.annotation.VersionSensitive; +import com.github.leawind.thirdperson.util.math.LMath; +import com.github.leawind.thirdperson.util.math.decisionmap.DecisionMap; +import com.github.leawind.thirdperson.util.math.smoothvalue.ExpSmoothDouble; +import com.github.leawind.thirdperson.util.math.smoothvalue.ExpSmoothRotation; +import java.util.Objects; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.HumanoidArm; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.UseAnim; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import org.apache.logging.log4j.util.PerformanceSensitive; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector2d; +import org.joml.Vector3d; + +public class EntityAgent { + public final FiniteChecker FINITE_CHECKER = + new FiniteChecker(err -> ThirdPerson.LOGGER.error(err.toString())); + + private final Minecraft minecraft; + + private final ExpSmoothRotation smoothRotation = ExpSmoothRotation.createWithHalflife(0.5); + // private final ExpRotSmoothDouble smoothYBodyRotation = + // ExpRotSmoothDouble.createWithHalflife(360, 0.5); + + private final ExpSmoothDouble smoothOpacity; + + /** 在 clientTick 中更新 */ + public double vehicleTotalSizeCached = 1D; + + private @NotNull RotateTargetEnum rotateTarget = RotateTargetEnum.NONE; + private @NotNull SmoothTypeEnum smoothRotationType = SmoothTypeEnum.EXP_LINEAR; + private final DecisionMap rotateDecisionMap = RotateStrategy.build(); + + /** 在上一个 client tick 中的 isAiming() 的值 */ + private boolean wasAiming = false; + + public EntityAgent(@NotNull Minecraft minecraft) { + this.minecraft = minecraft; + + smoothOpacity = new ExpSmoothDouble(); + smoothOpacity.set(1d); + + ThirdPerson.LOGGER.debug(rotateDecisionMap.toDescription()); + } + + /** 相机实体 {@link Minecraft#cameraEntity} 是否已经存在 */ + public boolean isCameraEntityExist() { + return minecraft.cameraEntity != null; + } + + /** + * 重置各种属性 + * + *

当初始化或进入第三人称时调用 + */ + public void reset() { + ThirdPerson.LOGGER.debug("Reset EntityAgent"); + smoothOpacity.set(0d); + wasAiming = false; + } + + @NotNull + public RotateTargetEnum getRotateTarget() { + return rotateTarget; + } + + /** 设置旋转目标 */ + public void setRotateTarget(@NotNull RotateTargetEnum rotateTarget) { + this.rotateTarget = rotateTarget; + } + + public @NotNull SmoothTypeEnum getRotationSmoothType() { + return smoothRotationType; + } + + /** + * 设置平滑类型 + * + *

在 clientTick 和 renderTick 中要根据平滑类型采用不同的处理方式 + */ + public void setRotationSmoothType(@NotNull SmoothTypeEnum smoothType) { + smoothRotationType = smoothType; + } + + /** 设置平滑转向的半衰期 */ + @SuppressWarnings("unused") + public void setSmoothRotationHalflife(double halflife) { + smoothRotation.setHalflife(halflife); + } + + /** 获取相机实体不透明度 */ + public float getSmoothOpacity(float partialTick) { + return smoothOpacity.get(partialTick).floatValue(); + } + + public ExpSmoothRotation getSmoothRotation() { + return smoothRotation; + } + + /** + * @param period 相邻两次 render tick 的时间差,单位:s + */ + @SuppressWarnings("unused") + @PerformanceSensitive + public void onRenderTickStart(double now, double period, float partialTick) { + if (ThirdPersonStatus.isRenderingInThirdPerson() + && isControlled() + && !ThirdPersonStatus.shouldCameraTurnWithEntity()) { + var targetRotation = getRotateTarget().getRotation(partialTick); + + smoothRotation.setTarget(targetRotation); + + switch (smoothRotationType) { + case HARD -> setRawRotation(targetRotation); + case LINEAR, EXP_LINEAR -> setRawRotation(smoothRotation.get(partialTick)); + case EXP -> { + smoothRotation.update(period); + setRawRotation(smoothRotation.get()); + } + default -> + throw new IllegalStateException("Invalid smooth rotation type: " + smoothRotationType); + } + } + } + + public void onClientTickStart() { + if (ThirdPersonStatus.clientTicks % 2 == 0) { + vehicleTotalSizeCached = getVehicleTotalSize(); + } + ThirdPersonStatus.clientTicks++; + + wasAiming = isAiming(); + ThirdPerson.getConfig().getCameraOffsetScheme().setAiming(wasAiming()); + + updateRotateStrategy(); + updateBodyRotation(); + updateSmoothOpacity(ThirdPersonConstants.VANILLA_CLIENT_TICK_TIME, 1); + + switch (smoothRotationType) { + case HARD, EXP -> {} + case LINEAR, EXP_LINEAR -> { + smoothRotation.setTarget(getRotateTarget().getRotation(1)); + smoothRotation.update(ThirdPersonConstants.VANILLA_CLIENT_TICK_TIME); + } + } + } + + /** 设置实体朝向 */ + public void setRawRotation(@NotNull Vector2d rot) { + FINITE_CHECKER.checkOnce(rot.x, rot.y); + var entity = getRawPlayerEntity(); + + entity.setYRot(entity.yRotO = (float) rot.y); + entity.setXRot(entity.xRotO = (float) rot.x); + } + + /** 玩家当前是否在操控这个实体 */ + public boolean isControlled() { + return getRawPlayerEntity() == minecraft.cameraEntity; + } + + /** + * 获取相机附着的实体 + * + * @see EntityAgent#isCameraEntityExist + */ + public @NotNull Entity getRawCameraEntity() { + return Objects.requireNonNull(minecraft.cameraEntity); + } + + /** 获取玩家实体 */ + public @NotNull LocalPlayer getRawPlayerEntity() { + return Objects.requireNonNull(minecraft.player); + } + + /** 直接从相机实体获取眼睛坐标 */ + public @NotNull Vector3d getRawEyePosition(float partialTick) { + return LMath.toVector3d(getRawCameraEntity().getEyePosition(partialTick)); + } + + /** 直接从实体获取朝向 */ + @VersionSensitive + public @NotNull Vector2d getRawRotation(float partialTick) { + var entity = getRawCameraEntity(); + return new Vector2d(entity.getViewXRot(partialTick), entity.getViewYRot(partialTick)); + } + + /** + * 实体的眼睛是否在墙里 + * + *

与{@link Entity#isInWall()}不同的是,旁观者模式下此方法仍然可以返回true + */ + public boolean isEyeInWall(@NotNull ClipContext.ShapeGetter shapeGetter) { + var cameraEntity = getRawCameraEntity(); + var eyePos = cameraEntity.getEyePosition(); + var blockPos = new BlockPos(eyePos.x, eyePos.y, eyePos.z); + var blockState = cameraEntity.getLevel().getBlockState(blockPos); + var eyeAabb = AABB.ofSize(eyePos, 0.8, 1e-6, 0.8); + return shapeGetter + .get(blockState, cameraEntity.getLevel(), blockPos, CollisionContext.empty()) + .toAabbs() + .stream() + .anyMatch(a -> a.move(blockPos).intersects(eyeAabb)); + } + + /** + * 实体是否在交互 + * + *

当控制玩家时,相当于是否按下了 使用|攻击|选取 键 + * + *

当附身其他实体时,另做判断 + */ + @VersionSensitive + public boolean isInteracting() { + if (!isControlled()) { + return getRawCameraEntity() instanceof LivingEntity livingEntity + && livingEntity.isUsingItem(); + } + var options = minecraft.options; + return options.keyUse.isDown() || options.keyAttack.isDown() || options.keyPickItem.isDown(); + } + + /** 实体是否在飞行 */ + @VersionSensitive + public boolean isFallFlying() { + return getRawCameraEntity() instanceof LivingEntity livingEntity && livingEntity.isFallFlying(); + } + + /** 实体是否在奔跑 */ + public boolean isSprinting() { + return getRawCameraEntity().isSprinting(); + } + + /** + * 正在吃食物 + * + *

使用 {@link ItemStack#isEdible()} 判断是否是食物 + */ + public boolean isEating() { + if (getRawCameraEntity() instanceof LivingEntity livingEntity) { + return livingEntity.getUseItem().isEdible(); + } + return false; + } + + /** + * 根据以下因素判断是否在瞄准 + *

  • 是否在使用物品 + *
  • 实体拿着的物品 + *
  • 按键 + *
  • 使用物品时正在播放的动画 + */ + public boolean isAiming() { + var config = ThirdPerson.getConfig(); + if (ThirdPersonStatus.doesPlayerWantToAim()) { + return true; + } + if (getRawCameraEntity() instanceof LivingEntity livingEntity) { + if (config.determine_aim_mode_by_animation && livingEntity.isUsingItem()) { + var anim = livingEntity.getUseItem().getUseAnimation(); + if (anim == UseAnim.BOW || anim == UseAnim.SPEAR) { + return true; + } + } + + boolean shouldBeAiming = + ItemPredicateUtil.anyMatches( + livingEntity.getMainHandItem(), + config.getHoldToAimItemPredicates(), + ThirdPersonResources.itemPredicateManager.holdToAimItemPredicates) + || ItemPredicateUtil.anyMatches( + livingEntity.getOffhandItem(), + config.getHoldToAimItemPredicates(), + ThirdPersonResources.itemPredicateManager.holdToAimItemPredicates); + + if (livingEntity.isUsingItem()) { + shouldBeAiming |= + ItemPredicateUtil.anyMatches( + livingEntity.getUseItem(), + config.getUseToAimItemPredicates(), + ThirdPersonResources.itemPredicateManager.useToAimItemPredicates); + } + return shouldBeAiming; + } + return false; + } + + /** 在上一个 clientTick 中是否在瞄准 */ + public boolean wasAiming() { + return wasAiming; + } + + public AABB getBoundingBox(float partialTick) { + var entity = this.getRawCameraEntity(); + return entity.getDimensions(entity.getPose()).makeBoundingBox(entity.getPosition(partialTick)); + } + + /** 计算点到碰撞箱的距离。如果点在碰撞箱内,则返回0 */ + public double boxDistanceTo(@NotNull Vector3d target, float partialTick) { + var aabb = getBoundingBox(partialTick); + var c = + new Vector3d( + LMath.clamp(target.x, aabb.minX, aabb.maxX), + LMath.clamp(target.y, aabb.minY, aabb.maxY), + LMath.clamp(target.z, aabb.minZ, aabb.maxZ)); + return c.distance(target); + } + + public double getBodyRadius() { + return getRawCameraEntity().getBbWidth() * 0.8660254037844386; // 0.5 * sqrt(3) + } + + public double columnDistanceTo(@NotNull Vector3d target, float partialTick) { + var entity = getRawCameraEntity(); + + var c = LMath.toVector3d(entity.getPosition(partialTick)); + double maxY = c.y + entity.getEyeHeight(); + c.y = LMath.clamp(target.y, c.y, maxY); + double dist = c.distance(target); + if (maxY > target.y && target.y > c.y) { + dist = Math.max(0, dist - getBodyRadius()); + } + return dist; + } + + /** + * 更新旋转策略、平滑类型、平滑系数 + * + *

    根据配置、游泳、飞行、瞄准等状态判断。 + */ + private void updateRotateStrategy() { + setSmoothRotationHalflife(rotateDecisionMap.updateAll().make()); + } + + /** + * TODO 接管身体的转动 + * + *

    脖子最多左右转85度 + * + * @see net.minecraft.client.renderer.entity.LivingEntityRenderer#render + */ + private void updateBodyRotation() { + + // if (getRotateTarget() == RotateTargetEnum.INTEREST_POINT) { + // if (ThirdPersonStatus.impulseHorizon.length() >= 1e-5) { + // var rot = RotateTargetEnum.HORIZONTAL_IMPULSE_DIRECTION.getRotation(1); + // } + // } + + // net.minecraft.client.renderer.entity.LivingEntityRenderer.render + var config = ThirdPerson.getConfig(); + if (config.auto_turn_body_drawing_a_bow && ThirdPerson.ENTITY_AGENT.isControlled()) { + var player = getRawPlayerEntity(); + if (player.isUsingItem() && player.getUseItem().is(Items.BOW)) { + double k = player.getUsedItemHand() == InteractionHand.MAIN_HAND ? 1 : -1; + if (minecraft.options.mainHand().get() == HumanoidArm.LEFT) { + k = -k; + } + player.yBodyRot = (float) (k * 45 + player.getYRot()); + } + } + } + + /** 更新平滑的透明度 */ + private void updateSmoothOpacity(double period, float partialTick) { + double targetOpacity = 1.0; + var config = ThirdPerson.getConfig(); + if (config.player_fade_out_enabled) { + final double C = ThirdPersonConstants.CAMERA_THROUGH_WALL_DETECTION * 2; + var cameraPosition = LMath.toVector3d(ThirdPerson.CAMERA_AGENT.getRawCamera().getPosition()); + double distance = getRawEyePosition(partialTick).distance(cameraPosition); + + targetOpacity = (distance - C) / (1 - C); + FINITE_CHECKER.checkOnce(targetOpacity); + + if (targetOpacity > config.gaze_opacity + && !isFallFlying() + && ThirdPerson.CAMERA_AGENT.isLookingAt(getRawCameraEntity())) { + targetOpacity = config.gaze_opacity; + } + } + + smoothOpacity.setTarget(LMath.clamp(targetOpacity, 0, 1)); + smoothOpacity.setHalflife(ThirdPersonConstants.OPACITY_HALFLIFE * (wasAiming() ? 0.25 : 1)); + smoothOpacity.update(period); + } + + /** + * 当相机在身后时,兴趣点是准星指向的目标点 + * + *

    当相机在面前时,兴趣点是相机 + */ + public @Nullable Vec3 getInterestPoint() { + if (LMath.subtractDegrees( + getRawPlayerEntity().yBodyRot, ThirdPerson.CAMERA_AGENT.getRelativeRotation().y) + > 90) { + return ThirdPerson.CAMERA_AGENT.getHitResult().getLocation(); + } else { + return LMath.toVec3(ThirdPerson.CAMERA_AGENT.getRawCameraPosition()); + } + } + + public double getVehicleTotalSize() { + var root = getRawCameraEntity().getRootVehicle(); + var bb = + root.getPassengersAndSelf() + .map(Entity::getBoundingBox) + .reduce(AABB::minmax) + .orElse(root.getBoundingBox()); + return Math.hypot(Math.hypot(bb.getXsize(), bb.getYsize()), bb.getZsize()); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/AbstractCameraOffsetMode.java b/common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/AbstractCameraOffsetMode.java new file mode 100644 index 00000000..6206f0cc --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/AbstractCameraOffsetMode.java @@ -0,0 +1,92 @@ +package com.github.leawind.thirdperson.core.cameraoffset; + +import com.github.leawind.thirdperson.config.Config; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2d; +import org.joml.Vector3d; + +/** + * 相机偏移模式 + * + *

    描述相机应该如何偏移 + */ +public abstract class AbstractCameraOffsetMode { + protected final @NotNull Config config; + + public AbstractCameraOffsetMode(@NotNull Config config) { + this.config = config; + } + + /** 设置相机在玩家的左边还是右边 */ + public void setSide(boolean isCameraLeftOfPlayer) { + if (isCameraLeftOfPlayer ^ isCameraLeftOfPlayer()) { + toNextSide(); + setCentered(false); + } + } + + /** + * 获取偏移量 + * + *

    根据当前是居中还是在两侧自动计算偏移量 + */ + public void getOffsetRatio(@NotNull Vector2d v) { + if (isCentered()) { + v.set(0, getCenterOffsetRatio()); + } else { + getSideOffsetRatio(v); + } + } + + public Vector2d getOffsetRatio() { + var v = new Vector2d(); + getOffsetRatio(v); + return v; + } + + /** 眼睛平滑半衰期 */ + @NotNull + public abstract Vector3d getEyeSmoothHalflife(); + + /** 距离平滑系数 */ + public abstract double getDistanceSmoothHalflife(); + + /** 相机偏移平滑系数 */ + @NotNull + public abstract Vector2d getOffsetSmoothHalflife(); + + /** 相机到玩家的距离限制 */ + public abstract double getDistanceLimit(); + + /** 设置相机到玩家的距离限制 */ + public abstract void setDistanceLimit(double distance); + + /** 当前是否居中 */ + public abstract boolean isCentered(); + + /** 设置是否居中 */ + public abstract void setCentered(boolean isCentered); + + public abstract boolean isCameraLeftOfPlayer(); + + /** 切换到另一边,如果当前居中,则退出居中 */ + public abstract void toNextSide(); + + /** 设置当相机位于两侧,而非居中时的偏移量。 */ + public abstract void setSideOffsetRatio(@NotNull Vector2d v); + + /** 获取当相机居中时的,垂直偏移量 */ + public abstract double getCenterOffsetRatio(); + + /** 设置当相机居中时的,垂直偏移量 */ + public abstract void setCenterOffsetRatio(double offset); + + /** + * 获取当相机位于两侧,而非居中时的偏移量。 + * + * @param v 将取得的数据存入该向量 + * @return 与传入参数是同一个对象 + */ + @NotNull + public abstract Vector2d getSideOffsetRatio(@NotNull Vector2d v); +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/CameraOffsetModeAiming.java b/common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/CameraOffsetModeAiming.java new file mode 100644 index 00000000..3cff4c4b --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/CameraOffsetModeAiming.java @@ -0,0 +1,88 @@ +package com.github.leawind.thirdperson.core.cameraoffset; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.config.Config; +import com.github.leawind.thirdperson.util.math.LMath; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2d; +import org.joml.Vector3d; + +public class CameraOffsetModeAiming extends AbstractCameraOffsetMode { + public CameraOffsetModeAiming(@NotNull Config config) { + super(config); + } + + @Override + public @NotNull Vector3d getEyeSmoothHalflife() { + return new Vector3d( + config.aiming_smooth_halflife_horizon, + config.aiming_smooth_halflife_vertical, + config.aiming_smooth_halflife_horizon); + } + + @Override + public double getDistanceSmoothHalflife() { + return config.aiming_distance_smooth_halflife; + } + + @Override + public @NotNull Vector2d getOffsetSmoothHalflife() { + return new Vector2d(config.aiming_camera_offset_smooth_halflife); + } + + @Override + public double getDistanceLimit() { + return config.aiming_max_distance; + } + + @Override + public void setDistanceLimit(double distance) { + config.aiming_max_distance = distance; + } + + @Override + public boolean isCentered() { + return config.aiming_is_centered; + } + + @Override + public void setCentered(boolean isCentered) { + config.aiming_is_centered = isCentered; + } + + @Override + public boolean isCameraLeftOfPlayer() { + return config.aiming_offset_x > 0; + } + + @Override + public void toNextSide() { + ThirdPerson.LOGGER.debug("Switching camera to the other side"); + if (isCentered()) { + setCentered(false); + } else { + config.aiming_offset_x = -config.aiming_offset_x; + } + } + + @Override + public void setSideOffsetRatio(@NotNull Vector2d v) { + config.aiming_offset_x = LMath.clamp(v.x, -1, 1); + config.aiming_offset_y = LMath.clamp(v.y, -1, 1); + } + + @Override + public double getCenterOffsetRatio() { + return config.aiming_offset_center; + } + + @Override + public void setCenterOffsetRatio(double offset) { + config.aiming_offset_center = LMath.clamp(offset, -1, 1); + } + + @Override + public @NotNull Vector2d getSideOffsetRatio(@NotNull Vector2d v) { + return v.set(config.aiming_offset_x, config.aiming_offset_y); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/CameraOffsetModeNormal.java b/common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/CameraOffsetModeNormal.java new file mode 100644 index 00000000..41f728a5 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/CameraOffsetModeNormal.java @@ -0,0 +1,85 @@ +package com.github.leawind.thirdperson.core.cameraoffset; + +import com.github.leawind.thirdperson.config.Config; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2d; +import org.joml.Vector3d; + +public class CameraOffsetModeNormal extends AbstractCameraOffsetMode { + public CameraOffsetModeNormal(@NotNull Config config) { + super(config); + } + + @Override + public @NotNull Vector3d getEyeSmoothHalflife() { + return new Vector3d( + config.normal_smooth_halflife_horizon, + config.normal_smooth_halflife_vertical, + config.normal_smooth_halflife_horizon); + } + + @Override + public double getDistanceSmoothHalflife() { + return config.normal_distance_smooth_halflife; + } + + @Override + public @NotNull Vector2d getOffsetSmoothHalflife() { + return new Vector2d(config.normal_camera_offset_smooth_halflife); + } + + @Override + public double getDistanceLimit() { + return config.normal_max_distance; + } + + @Override + public void setDistanceLimit(double distance) { + config.normal_max_distance = distance; + } + + @Override + public boolean isCentered() { + return config.normal_is_centered; + } + + @Override + public void setCentered(boolean isCentered) { + config.normal_is_centered = isCentered; + } + + @Override + public boolean isCameraLeftOfPlayer() { + return config.normal_offset_x > 0; + } + + @Override + public void toNextSide() { + if (isCentered()) { + setCentered(false); + } else { + config.normal_offset_x = -config.normal_offset_x; + } + } + + @Override + public void setSideOffsetRatio(@NotNull Vector2d v) { + config.normal_offset_x = v.x; + config.normal_offset_y = v.y; + } + + @Override + public double getCenterOffsetRatio() { + return config.normal_offset_center; + } + + @Override + public void setCenterOffsetRatio(double offset) { + config.normal_offset_center = offset; + } + + @Override + public @NotNull Vector2d getSideOffsetRatio(@NotNull Vector2d v) { + return v.set(config.normal_offset_x, config.normal_offset_y); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/CameraOffsetScheme.java b/common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/CameraOffsetScheme.java new file mode 100644 index 00000000..cb171ff4 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/core/cameraoffset/CameraOffsetScheme.java @@ -0,0 +1,89 @@ +package com.github.leawind.thirdperson.core.cameraoffset; + +import com.github.leawind.thirdperson.config.Config; +import org.jetbrains.annotations.NotNull; + +/** + * 第三人称相机的偏移方案 + * + *

    第三人称下,相机会根据其当前所处的模式来确定相机的行为。例如如何跟随玩家、如何旋转、与玩家的相对位置如何确定等。 + * + *

    默认有两种模式,按F5在第一人称和两种模式间切换 + */ +public class CameraOffsetScheme { + private final @NotNull AbstractCameraOffsetMode normalMode; + private final @NotNull AbstractCameraOffsetMode aimingMode; + + private boolean isAiming = false; + + public CameraOffsetScheme(@NotNull Config config) { + normalMode = new CameraOffsetModeNormal(config); + aimingMode = new CameraOffsetModeAiming(config); + } + + /** 获取当前模式 */ + public @NotNull AbstractCameraOffsetMode getMode() { + return isAiming() ? aimingMode : normalMode; + } + + public AbstractCameraOffsetMode getNormalMode() { + return normalMode; + } + + public AbstractCameraOffsetMode getAimingMode() { + return aimingMode; + } + + /** 获取当前未启用的模式 */ + public @NotNull AbstractCameraOffsetMode getAnotherMode() { + return isAiming() ? normalMode : aimingMode; + } + + /** + * 设置相机相对于玩家的方向 + * + *

    + * + * @param side 大于0表示相机在玩家左侧 + */ + public void setSide(double side) { + setSide(side > 0); + } + + /** + * 设置相机相对于玩家的方向 + * + * @param isCameraLeftOfPlayer 相机是否在玩家左侧 + */ + public void setSide(boolean isCameraLeftOfPlayer) { + aimingMode.setSide(isCameraLeftOfPlayer); + normalMode.setSide(isCameraLeftOfPlayer); + } + + /** 切换到另一边 */ + public void toNextSide() { + aimingMode.toNextSide(); + normalMode.toNextSide(); + } + + /** 当前是否居中 */ + public boolean isCentered() { + return getMode().isCentered(); + } + + /** 设置当前是否居中 */ + public void setCentered(boolean isCentered) { + getMode().setCentered(isCentered); + getAnotherMode().setCentered(isCentered); + } + + /** 设置当前是否处于瞄准模式 */ + public boolean isAiming() { + return isAiming; + } + + /** 当前是否处于瞄准模式 */ + public void setAiming(boolean aiming) { + isAiming = aiming; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/core/rotation/RotateStrategy.java b/common/src/main/java/com/github/leawind/thirdperson/core/rotation/RotateStrategy.java new file mode 100644 index 00000000..17286355 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/core/rotation/RotateStrategy.java @@ -0,0 +1,153 @@ +package com.github.leawind.thirdperson.core.rotation; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.ThirdPersonStatus; +import com.github.leawind.thirdperson.util.math.decisionmap.DecisionMap; +import java.util.List; +import net.minecraft.client.Minecraft; +import net.minecraft.world.entity.LivingEntity; + +/** 玩家旋转策略 */ +public final class RotateStrategy { + public static DecisionMap build() { + var builder = DecisionMap.builder(); + builder.factor("aiming", Factor::isAiming); + builder.factor("swimming", Factor::isSwimming); + builder.factor("sprint", Factor::wantToSprint); + builder.factor("fall_flying", Factor::isFallFlying); + builder.factor("interacting", Factor::shouldTurnToInteractPoint); + builder.factor("force_rotate", Factor::forceRotate); + builder.factor("is_passenger", Factor::isPassenger); + builder.factor("is_vehicle_living_entity", Factor::isVehicleLivingEntity); + + builder.whenDefault(Do::defaultOperation); + builder.when(List.of("is_passenger", "~is_vehicle_living_entity"), Do::ridingNonLivingEntity); + builder.when(List.of("is_passenger", "is_vehicle_living_entity"), Do::ridingLivingEntity); + builder.when("sprint", Do::sprint); + builder.when("swimming", Do::swimming); + builder.when("interacting", Do::interacting); + builder.when("fall_flying", Do::fallFlying); + builder.when("force_rotate", Do::defaultOperation); + builder.when("aiming", Do::aiming); + return builder.build(); + } + + private static final class Factor { + static boolean isSwimming() { + return ThirdPerson.ENTITY_AGENT.getRawCameraEntity().isSwimming(); + } + + static boolean isAiming() { + return ThirdPerson.ENTITY_AGENT.isAiming(); + } + + static boolean isFallFlying() { + return ThirdPerson.ENTITY_AGENT.isFallFlying(); + } + + static boolean shouldTurnToInteractPoint() { + return ThirdPerson.getConfig().auto_rotate_interacting + && ThirdPerson.ENTITY_AGENT.isInteracting() + && !(ThirdPerson.getConfig().do_not_rotate_when_eating + && ThirdPerson.ENTITY_AGENT.isEating()); + } + + static boolean wantToSprint() { + return Minecraft.getInstance().options.keySprint.isDown() + || ThirdPerson.ENTITY_AGENT.isSprinting(); + } + + static boolean isPassenger() { + return ThirdPerson.ENTITY_AGENT.getRawCameraEntity().isPassenger(); + } + + static boolean isVehicleLivingEntity() { + return ThirdPerson.ENTITY_AGENT.getRawCameraEntity().getVehicle() instanceof LivingEntity; + } + + static boolean forceRotate() { + return switch (ThirdPerson.getConfig().normal_rotate_mode) { + case INTEREST_POINT, MOVING_DIRECTION -> false; + default -> true; + }; + } + } + + private static final class Do { + + static double defaultOperation() { + double rotateHalflife = 0; + switch (ThirdPerson.getConfig().normal_rotate_mode) { + case INTEREST_POINT -> { + if (ThirdPersonStatus.impulseHorizon.length() < 1e-5) { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.INTEREST_POINT); + } else { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.HORIZONTAL_IMPULSE_DIRECTION); + } + ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothTypeEnum.EXP_LINEAR); + rotateHalflife = 0.03; + } + case CAMERA_CROSSHAIR -> { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.CAMERA_HIT_RESULT); + ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothTypeEnum.HARD); + } + case MOVING_DIRECTION -> { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.HORIZONTAL_IMPULSE_DIRECTION); + ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothTypeEnum.LINEAR); + rotateHalflife = 0.06; + } + case PARALLEL_WITH_CAMERA -> { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.CAMERA_ROTATION); + ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothTypeEnum.LINEAR); + } + case NONE -> { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.NONE); + ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothTypeEnum.LINEAR); + } + } + return rotateHalflife; + } + + static double ridingNonLivingEntity() { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.INTEREST_POINT); + ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothTypeEnum.EXP_LINEAR); + return 0.15; + } + + static double ridingLivingEntity() { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.HORIZONTAL_IMPULSE_DIRECTION); + ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothTypeEnum.EXP); + return 0.1; + } + + static double sprint() { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.HORIZONTAL_IMPULSE_DIRECTION); + ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothTypeEnum.LINEAR); + return 0.025; + } + + static double swimming() { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.IMPULSE_DIRECTION); + ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothTypeEnum.LINEAR); + return 0.01; + } + + static double aiming() { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.PREDICTED_TARGET_ENTITY); + ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothTypeEnum.HARD); + return 0D; + } + + static double fallFlying() { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.CAMERA_ROTATION); + ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothTypeEnum.HARD); + return 0D; + } + + static double interacting() { + ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTargetEnum.CAMERA_HIT_RESULT); + ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothTypeEnum.LINEAR); + return 0D; + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/core/rotation/RotateTargetEnum.java b/common/src/main/java/com/github/leawind/thirdperson/core/rotation/RotateTargetEnum.java new file mode 100644 index 00000000..10cbc727 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/core/rotation/RotateTargetEnum.java @@ -0,0 +1,152 @@ +package com.github.leawind.thirdperson.core.rotation; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.ThirdPersonConstants; +import com.github.leawind.thirdperson.ThirdPersonStatus; +import com.github.leawind.thirdperson.core.CameraAgent; +import com.github.leawind.thirdperson.util.math.LMath; +import java.util.function.Function; +import net.minecraft.world.phys.HitResult.Type; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2d; + +/** 旋转目标,即玩家应该转向何处 */ +public enum RotateTargetEnum { + /** 保持当前朝向,不旋转 */ + NONE(partialTick -> ThirdPerson.ENTITY_AGENT.getRawRotation(partialTick)), + /** + * 兴趣点 + * + *

    当相机位于后方时,玩家看向准星处,相机位于前方时,玩家看向相机 + */ + INTEREST_POINT( + partialTick -> { + var point = ThirdPerson.ENTITY_AGENT.getInterestPoint(); + if (point == null) { + return NONE.getRotation(partialTick); + } + + var player = ThirdPerson.ENTITY_AGENT.getRawPlayerEntity(); + + var toInterestedPoint = point.subtract(player.getEyePosition(partialTick)); + if (toInterestedPoint.length() < 1e-5) { + return NONE.getRotation(partialTick); + } + + var playerRot = ThirdPerson.ENTITY_AGENT.getRawRotation(1); + ThirdPerson.FINITE_CHECKER.checkOnce(playerRot.x, playerRot.y); + + var rot = LMath.rotationDegreeFromDirection(LMath.toVector3d(toInterestedPoint)); + ThirdPerson.FINITE_CHECKER.checkOnce(rot.x, rot.y); + + double leftBound = + player.yBodyRot - ThirdPersonConstants.VANILLA_PLAYER_HEAD_ROTATE_LIMIT_DEGREES; + double rightBound = + player.yBodyRot + ThirdPersonConstants.VANILLA_PLAYER_HEAD_ROTATE_LIMIT_DEGREES; + + if (LMath.isWithinDegrees(rot.y, leftBound, rightBound)) { + playerRot.y = rot.y; + } else { + playerRot.y = + LMath.subtractDegrees(rot.y, leftBound) < LMath.subtractDegrees(rot.y, rightBound) + ? leftBound + : rightBound; + } + + playerRot.x = rot.x; + return playerRot; + }), + /** 与相机朝向相同 */ + CAMERA_ROTATION(partialTick -> ThirdPerson.CAMERA_AGENT.getRotation()), + /** 转向相机的视线落点,即准星所指的位置 */ + CAMERA_HIT_RESULT( + partialTick -> { + var cameraHitResult = ThirdPerson.CAMERA_AGENT.getHitResult(); + if (cameraHitResult.getType() == Type.MISS) { + return CAMERA_ROTATION.getRotation(partialTick); + } else { + var cameraHitPosition = LMath.toVector3d(cameraHitResult.getLocation()); + var eyePosition = ThirdPerson.ENTITY_AGENT.getRawEyePosition(partialTick); + return LMath.rotationDegreeFromDirection(cameraHitPosition.sub(eyePosition)); + } + }), + /** + * 预测玩家想射击的目标实体 + * + *

    玩家将朝向的目标点为 相机位置 + 相机射线单位向量*目标实体距离 + * + *

    这样在射击远处的实体时,就不需要考虑玩家视线与相机视线间的偏移量了。 + * + *

    但是问题在于,当周围有许多实体时,对目标实体的预测可能不准确。 + * + * @see CameraAgent#predictTargetEntity(float) + */ + PREDICTED_TARGET_ENTITY( + partialTick -> { + var rotation = CAMERA_HIT_RESULT.getRotation(partialTick); + + if (!ThirdPerson.getConfig().enable_target_entity_predict + || !ThirdPerson.ENTITY_AGENT.isControlled()) { + return rotation; + } + + var predicted = ThirdPerson.CAMERA_AGENT.predictTargetEntity(partialTick); + + if (predicted == null) { + return rotation; + } + + var camera = ThirdPerson.CAMERA_AGENT.getRawCamera(); + var playerEyePos = ThirdPerson.ENTITY_AGENT.getRawEyePosition(partialTick); + var cameraPos = LMath.toVector3d(camera.getPosition()); + var targetPos = LMath.toVector3d(predicted.getPosition(partialTick)); + var end = + LMath.toVector3d(camera.getLookVector()) + .normalize(cameraPos.distance(targetPos)) + .add(cameraPos); + var eyeToEnd = end.sub(playerEyePos); + + if (eyeToEnd.length() < 1e-5) { + return rotation; + } + + return LMath.rotationDegreeFromDirection(eyeToEnd); + }), + /** + * 使用键盘控制的移动方向 + * + *

    当没有使用键盘控制时保持当前朝向 + */ + IMPULSE_DIRECTION( + partialTick -> + ThirdPersonStatus.impulseHorizon.length() < 1e-5 + ? NONE.getRotation(partialTick) + : LMath.rotationDegreeFromDirection(ThirdPersonStatus.impulse)), + /** + * 使用键盘控制的移动方向(仅水平) + * + *

    当没有使用键盘控制时保持当前朝向 + */ + HORIZONTAL_IMPULSE_DIRECTION( + partialTick -> { + if (ThirdPersonStatus.impulseHorizon.length() < 1e-5) { + return NONE.getRotation(partialTick); + } + double absoluteYRotDegree = + LMath.rotationDegreeFromDirection(ThirdPersonStatus.impulseHorizon); + return new Vector2d(0.1, absoluteYRotDegree); + }); + + private final Function rotationGetter; + + RotateTargetEnum(@NotNull Function rotationGetter) { + this.rotationGetter = rotationGetter; + } + + /** 获取玩家当前的目标朝向 */ + public @NotNull Vector2d getRotation(float partialTick) { + var rotation = rotationGetter.apply(partialTick); + ThirdPerson.FINITE_CHECKER.checkOnce(rotation.x, rotation.y); + return rotation; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/core/rotation/SmoothTypeEnum.java b/common/src/main/java/com/github/leawind/thirdperson/core/rotation/SmoothTypeEnum.java new file mode 100644 index 00000000..87200adf --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/core/rotation/SmoothTypeEnum.java @@ -0,0 +1,77 @@ +package com.github.leawind.thirdperson.core.rotation; + +/** + * + * + *

    平滑类型

    + * + *

    在渲染中实现平滑效果可以有多种方式,主要区别在于 render tick 和 client tick 中的处理方式不同。 + * + *

    注意此处的 client tick 可以是mc的 client tick,也可以是自定义的固定间隔的tick,但是需要在 render tick 中计算 partialTick。 + * + *

    partialTick 是一个浮点数,表示自上一次 client tick 以来经过的时间占 client tick 间隔的比例,通常用于线性插值 + */ +public enum SmoothTypeEnum { + /** + * + * + *

    梆硬

    + * + * 不用担心抽搐问题,但完全没有平滑效果 + * + *

    render tick

    + * + * 更新目标值并立即应用。 既然不需要平滑效果,自然不需要更新平滑值了 + * + *

    client tick

    + */ + HARD, + /** + * + * + *

    线性插值

    + * + * 相当于半衰期为0的 {@link SmoothTypeEnum#EXP_LINEAR} + * + *

    render tick

    + * + * 根据 partialTick 获取新值与旧值的线性插值 + * + *

    client tick

    + * + * 更新平滑值并记录旧值 + */ + LINEAR, + /** + * + * + *

    指数衰减

    + * + * 需要指定平滑系数(半衰期),无需记录旧值即可实现平滑效果。可以在任意时刻更新目标值 + * + *

    缺点:对时间精度要求高。当目标值变化速度较快时,平滑值的变化速度可能不平滑 + * + *

    render tick

    + * + * 取值前需要更新平滑值,然后直接取平滑值。 + * + *

    client tick

    + */ + EXP, + /** + * + * + *

    指数衰减+线性插值

    + * + * 解决了 {@link SmoothTypeEnum#EXP} 的不平滑问题。但是响应速度可能略迟钝。 + * + *

    render tick

    + * + * 取值时不直接取平滑值,而是取线性插值 + * + *

    client tick

    + * + * 更新目标值, 更新平滑值并记录旧值 + */ + EXP_LINEAR, +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/CameraInvoker.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/CameraInvoker.java new file mode 100644 index 00000000..c8ef6e51 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/CameraInvoker.java @@ -0,0 +1,27 @@ +package com.github.leawind.thirdperson.mixin; + +import net.minecraft.client.Camera; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(Camera.class) +public interface CameraInvoker { + @Invoker("setPosition") + void invokeSetPosition(Vec3 pos); + + /** + * 设置相机朝向,单位是角度制 + * + *

    该方法除了会设置对象的 yRot和xRot属性外,还会更新 rotation, forwards, up, left 属性 + * + * @param yRot 偏航角,z轴正向是0,顺时针为正向 + * @param xRot 俯仰角,俯正仰负 [-90,90] + */ + @Invoker("setRotation") + void invokeSetRotation(float yRot, float xRot); + + /** 相对于当前位置移动相机 */ + @Invoker("move") + void invokeMove(double forward, double up, double left); +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/CameraTypeMixin.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/CameraTypeMixin.java new file mode 100644 index 00000000..bb26b397 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/CameraTypeMixin.java @@ -0,0 +1,54 @@ +package com.github.leawind.thirdperson.mixin; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.ThirdPersonStatus; +import net.minecraft.client.CameraType; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = CameraType.class, priority = 2000) +public class CameraTypeMixin { + /** + * 这个字段的含义是当前目标是否为第一人称,或者说玩家想要通过按键切换到的是否为第一人称 + * + *

    通过 {@link CameraType#cycle()} 可以更改此字段,其他模组也可以通过直接设置此字段来切换视角。 + * + *

    注意:即使此字段为true,即目标是第一人称,也未必以第一人称渲染。因为从第三人称过渡到第一人称可能是一个平滑连续的过程。 + */ + @Final @Shadow private boolean firstPerson; + + /** + * 应用此 Mixin 后,此方法的语义变为: + * + *

    要么当前目标为第一人称且 {@link ThirdPersonStatus#isPerspectiveInverted} 为 false,要么当前为第三人称且 {@link + * ThirdPersonStatus#isPerspectiveInverted} 为 true。 + * + * @see CameraTypeMixin#firstPerson + * @see ThirdPersonStatus#isPerspectiveInverted + */ + @Inject(method = "isFirstPerson", at = @At("RETURN"), cancellable = true) + private void isFirstPerson(@NotNull CallbackInfoReturnable ci) { + if (ThirdPerson.isAvailable()) { + ci.setReturnValue(firstPerson ^ ThirdPersonStatus.isPerspectiveInverted); + ci.cancel(); + } + } + + /** 根据配置决定是否跳过原版的“盯着镜头”视角,(有人称其为第二人称视角) */ + @Inject(method = "cycle", at = @At("RETURN"), cancellable = true) + private void modifyCycle(CallbackInfoReturnable ci) { + var config = ThirdPerson.getConfig(); + if (config.is_mod_enabled && config.skip_vanilla_second_person_camera) { + var that = (CameraType) (Object) this; + if (that != CameraType.FIRST_PERSON) { + ci.setReturnValue(CameraType.FIRST_PERSON); + ci.cancel(); + } + } + } +} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/ClientLevelInvoker.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/ClientLevelInvoker.java similarity index 71% rename from common/src/main/java/net/leawind/mc/thirdperson/mixin/ClientLevelInvoker.java rename to common/src/main/java/com/github/leawind/thirdperson/mixin/ClientLevelInvoker.java index f8ad233b..b196b61b 100644 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/ClientLevelInvoker.java +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/ClientLevelInvoker.java @@ -1,5 +1,4 @@ -package net.leawind.mc.thirdperson.mixin; - +package com.github.leawind.thirdperson.mixin; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.world.entity.Entity; @@ -9,6 +8,6 @@ @Mixin(ClientLevel.class) public interface ClientLevelInvoker { - @Invoker("getEntities") - LevelEntityGetter invokeGetEntityGetter (); + @Invoker("getEntities") + LevelEntityGetter invokeGetEntityGetter(); } diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/EntityMixin.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/EntityMixin.java new file mode 100644 index 00000000..2f341f70 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/EntityMixin.java @@ -0,0 +1,93 @@ +package com.github.leawind.thirdperson.mixin; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.ThirdPersonStatus; +import com.github.leawind.thirdperson.api.base.GameEvents; +import com.github.leawind.thirdperson.api.client.event.EntityTurnStartEvent; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.client.MouseHandler; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.ClipContext.Block; +import net.minecraft.world.level.ClipContext.Fluid; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** */ +@Mixin(value = Entity.class, priority = 2000) +public class EntityMixin { + /** + * 实体探测方块 + * + *

    原本

    + * + * 从实体眼睛出发。使用实体的{@link Entity#getViewVector}计算方向,从而计算探测终点 + * + *

    Mixin 效果

    + * + * 如果实体是当前相机实体,从实体眼睛或相机出发,取决于配置。终点位于相机准星落点,略微延长一点。 + * + * @param pickFrom 探测起点,原本是玩家眼睛位置 + * @param pickTo 探测终点,原本是玩家眼睛前方距离为 pickRange 的位置 + * @param blockShape 探测的方块类型 + * @param fluidShape 探测的流体类型 + * @param entity 探测者 + * @param pickRange 探测距离,即目标与玩家眼睛间的最大距离 + * @see GameRendererMixin + */ + @WrapOperation( + method = "pick", + at = @At(value = "NEW", target = "Lnet/minecraft/world/level/ClipContext;")) + private ClipContext wrapPick( + Vec3 pickFrom, + Vec3 pickTo, + Block blockShape, + Fluid fluidShape, + Entity entity, + Operation original, + @Local(argsOnly = true) double pickRange, + @Local(argsOnly = true) float partialTick) { + if (ThirdPerson.isAvailable() && ThirdPersonStatus.isRenderingInThirdPerson()) { + if (entity == ThirdPerson.ENTITY_AGENT.getRawCameraEntity()) { + pickTo = ThirdPerson.CAMERA_AGENT.getHitResult().getLocation(); + if (ThirdPersonStatus.shouldPickFromCamera()) { + if (pickFrom.distanceTo(pickTo) > pickRange) { + // 相机实体的眼睛到准星落点距离超过了限制 + pickTo = pickFrom; + } else { + pickFrom = ThirdPerson.CAMERA_AGENT.getRawCamera().getPosition(); + var pickVector = pickFrom.vectorTo(pickTo).normalize(); + pickTo = pickTo.add(pickVector.scale(1e-4)); + } + } else { + var pickVector = pickFrom.vectorTo(pickTo).normalize().scale(pickRange); + pickTo = pickFrom.add(pickVector); + } + } + } + return original.call(pickFrom, pickTo, blockShape, fluidShape, entity); + } + + /** + * 鼠标移动事件处理函数会调用此方法旋转玩家 + * + * @see MouseHandler#turnPlayer() + */ + @Inject(method = "turn", at = @At("HEAD"), cancellable = true) + private void preTurn(double yRot, double xRot, @NotNull CallbackInfo ci) { + if (GameEvents.entityTurnStart != null) { + var entity = (Entity) (Object) this; + var event = new EntityTurnStartEvent(entity, yRot * 0.15, xRot * 0.15); + GameEvents.entityTurnStart.accept(event); + if (event.isDefaultCancelled()) { + ci.cancel(); + } + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/GameRendererInvoker.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/GameRendererInvoker.java new file mode 100644 index 00000000..28d7b01a --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/GameRendererInvoker.java @@ -0,0 +1,12 @@ +package com.github.leawind.thirdperson.mixin; + +import net.minecraft.client.Camera; +import net.minecraft.client.renderer.GameRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(GameRenderer.class) +public interface GameRendererInvoker { + @Invoker("getFov") + double invokeGetFov(Camera camera, float partialTick, boolean considerUserOption); +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/GameRendererMixin.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/GameRendererMixin.java new file mode 100644 index 00000000..ab954bad --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/GameRendererMixin.java @@ -0,0 +1,126 @@ +package com.github.leawind.thirdperson.mixin; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.ThirdPersonStatus; +import com.github.leawind.thirdperson.api.base.GameEvents; +import com.github.leawind.thirdperson.api.client.event.RenderTickStartEvent; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.blaze3d.vertex.PoseStack; +import java.util.function.Predicate; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.projectile.ProjectileUtil; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * {@link GameRenderer#pick(float)} 的作用是更新{@link Minecraft#hitResult}和{@link + * Minecraft#crosshairPickEntity} + * + *

    在原版中它有两处调用: + * + *

    1. 在{@link Minecraft#tick()}开头,tick 完 chatListener 和 gui 之后调用 + * + *

    2. 在{@link GameRenderer#renderLevel}的开头,更新完相机实体后调用 + * + *

    {@link GameRenderer#pick}会先调用{@link Entity#pick}探测方块,再通过{@link + * ProjectileUtil#getEntityHitResult}探测实体,然后计算最终探测结果 + * + *

    当探测结果为空时,它会通过 {@link BlockHitResult#miss(Vec3, Direction, BlockPos)} 创建一个表示结果为空的 + * BlockHitResult 对象,此时会根据玩家的朝向计算 Direction 参数。 + */ +@Mixin(value = GameRenderer.class, priority = 2000) +public abstract class GameRendererMixin { + + @Shadow @Final Minecraft minecraft; + @Final @Shadow private Camera mainCamera; + + /** + * 修改探测实体的起点和终点 + * + * @param pickFrom 探测起点,原本是玩家眼睛位置 + * @param pickTo 探测终点,原本是玩家眼睛前方距离为 pickRange 的位置 + * @param aabb 碰撞箱,只有与它有交点的实体才会被考虑 + * @param predicate 目标实体谓词 + * @param pickRangeSqr 探测距离上限的平方 + */ + @WrapOperation( + method = "pick", + at = + @At( + value = "INVOKE", + target = + "Lnet/minecraft/world/entity/projectile/ProjectileUtil;getEntityHitResult(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/AABB;" + + "Ljava/util/function/Predicate;D)Lnet/minecraft/world/phys/EntityHitResult;")) + private EntityHitResult wrapEntityHit( + Entity receiver, + Vec3 pickFrom, + Vec3 pickTo, + AABB aabb, + Predicate predicate, + double pickRangeSqr, + Operation original) { + if (ThirdPerson.isAvailable() && ThirdPersonStatus.isRenderingInThirdPerson()) { + if (receiver == minecraft.cameraEntity) { + pickTo = ThirdPerson.CAMERA_AGENT.getHitResult().getLocation(); + if (ThirdPersonStatus.shouldPickFromCamera()) { + // 从相机到准星处 + pickFrom = mainCamera.getPosition(); + var pickVector = pickFrom.vectorTo(pickTo).normalize().scale(1e-4); + pickTo = pickTo.add(pickVector); + } else { + // 从玩家眼睛到准星处 + // 限制距离为 pickRange + var pickRange = Math.sqrt(pickRangeSqr); + var pickVector = pickFrom.vectorTo(pickTo).normalize().scale(pickRange); + pickTo = pickFrom.add(pickVector); + } + aabb = new AABB(pickFrom, pickTo).inflate(1.0); + pickRangeSqr = pickFrom.distanceToSqr(pickTo); + } + } + return original.call(receiver, pickFrom, pickTo, aabb, predicate, pickRangeSqr); + } + + @ModifyReturnValue(method = "getFov", at = @At(value = "RETURN", ordinal = 1)) + private double modifyFov(double fov) { + if (!((GameRenderer) (Object) this).isPanoramicMode() + && ThirdPerson.isAvailable() + && ThirdPersonStatus.isRenderingInThirdPerson()) { + fov /= ThirdPerson.CAMERA_AGENT.getSmoothFovDivisor(); + } + return fov; + } + + /** 渲染tick前 */ + @Inject(method = "render", at = @At("HEAD")) + private void preRender(float partialTick, long nanoTime, boolean doRenderLevel, CallbackInfo ci) { + if (GameEvents.renderTickStart != null) { + GameEvents.renderTickStart.accept(new RenderTickStartEvent(partialTick)); + } + } + + /** 禁用第三人称视角摇晃 */ + @Inject(method = "bobView", at = @At("HEAD"), cancellable = true) + private void cancelBobView(PoseStack poseStack, float partialTick, CallbackInfo ci) { + if (ThirdPerson.isAvailable() + && ThirdPersonStatus.isRenderingInThirdPerson() + && ThirdPerson.getConfig().disable_third_person_bob_view) { + ci.cancel(); + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/GuiMixin.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/GuiMixin.java new file mode 100644 index 00000000..6621dbe8 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/GuiMixin.java @@ -0,0 +1,17 @@ +package com.github.leawind.thirdperson.mixin; + +import com.github.leawind.thirdperson.ThirdPersonStatus; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.client.gui.Gui; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(value = Gui.class, priority = 2000) +public class GuiMixin { + @ModifyExpressionValue( + method = "renderCrosshair", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/CameraType;isFirstPerson()Z")) + private boolean isFirstPerson(boolean isFirstPersonReally) { + return isFirstPersonReally || ThirdPersonStatus.forceThirdPersonCrosshair; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/KeyboardInputMixin.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/KeyboardInputMixin.java new file mode 100644 index 00000000..434cdffc --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/KeyboardInputMixin.java @@ -0,0 +1,31 @@ +package com.github.leawind.thirdperson.mixin; + +import com.github.leawind.thirdperson.api.base.GameEvents; +import com.github.leawind.thirdperson.api.client.event.CalculateMoveImpulseEvent; +import net.minecraft.client.player.KeyboardInput; +import org.apache.logging.log4j.util.PerformanceSensitive; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = KeyboardInput.class, priority = 2000) +public class KeyboardInputMixin { + /** 注入到tick的末尾,重新计算 leftImpulse 和 forwardImpulse 的值 */ + @Inject(method = "tick", at = @At(value = "TAIL")) + @PerformanceSensitive + private void postTick(boolean multiplyImpulse, float impulseMultiplier, CallbackInfo ci) { + var that = ((KeyboardInput) (Object) this); + if (GameEvents.calculateMoveImpulse != null) { + var event = new CalculateMoveImpulseEvent(that, multiplyImpulse ? impulseMultiplier : 1); + + event.forwardImpulse = that.forwardImpulse; + event.leftImpulse = that.leftImpulse; + + GameEvents.calculateMoveImpulse.accept(event); + + that.forwardImpulse = event.forwardImpulse; + that.leftImpulse = event.leftImpulse; + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/LevelRendererMixin.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/LevelRendererMixin.java new file mode 100644 index 00000000..1f14b855 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/LevelRendererMixin.java @@ -0,0 +1,58 @@ +package com.github.leawind.thirdperson.mixin; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.ThirdPersonStatus; +import com.github.leawind.thirdperson.api.base.GameEvents; +import com.github.leawind.thirdperson.api.client.event.RenderEntityEvent; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.world.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = LevelRenderer.class, priority = 2000) +public class LevelRendererMixin { + /** 允许取消渲染实体 */ + @Inject(method = "renderEntity", at = @At("HEAD"), cancellable = true) + private void cancelRenderEntity( + Entity entity, + double x, + double y, + double z, + float partialTick, + PoseStack poseStack, + MultiBufferSource multiBufferSource, + CallbackInfo ci) { + if (GameEvents.renderEntity != null) { + var event = new RenderEntityEvent(entity, partialTick); + if (!GameEvents.renderEntity.apply(event)) { + ci.cancel(); + } + } + } + + @Inject(method = "renderEntity", at = @At("TAIL")) + private void postRenderEntity( + Entity entity, + double x, + double y, + double z, + float partialTick, + PoseStack poseStack, + MultiBufferSource multiBufferSource, + CallbackInfo ci) { + if (multiBufferSource instanceof MultiBufferSource.BufferSource) { + if (ThirdPerson.isAvailable() + && ThirdPersonStatus.isRenderingInThirdPerson() + && entity == ThirdPerson.ENTITY_AGENT.getRawCameraEntity()) { + if (ThirdPersonStatus.useCameraEntityOpacity(partialTick) + && ThirdPersonStatus.shouldRenderCameraEntity(partialTick)) { + ((MultiBufferSource.BufferSource) multiBufferSource).endLastBatch(); + } + } + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/LocalPlayerMixin.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/LocalPlayerMixin.java new file mode 100644 index 00000000..080f0619 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/LocalPlayerMixin.java @@ -0,0 +1,25 @@ +package com.github.leawind.thirdperson.mixin; + +import com.github.leawind.thirdperson.ThirdPerson; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = LocalPlayer.class, priority = 2000) +public class LocalPlayerMixin { + @Shadow protected int sprintTriggerTime; + + @Inject(method = "aiStep()V", at = @At("HEAD")) + private void resetSprintTriggerTime(CallbackInfo ci) { + var config = ThirdPerson.getConfig(); + if (config.is_mod_enabled + && !Minecraft.getInstance().options.getCameraType().isFirstPerson() + && !config.allow_double_tap_sprint) { + sprintTriggerTime = 0; + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/MinecraftMixin.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/MinecraftMixin.java new file mode 100644 index 00000000..67537d86 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/MinecraftMixin.java @@ -0,0 +1,20 @@ +package com.github.leawind.thirdperson.mixin; + +import com.github.leawind.thirdperson.api.base.GameEvents; +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** handleKeybinds 方法中会处理各种按键事件, 其中包括鼠标使用、攻击、选取按键 */ +@Mixin(value = Minecraft.class, priority = 2000) +public class MinecraftMixin { + /** 注入到 handleKeybinds 头部,触发相应事件 */ + @Inject(method = "handleKeybinds", at = @At(value = "HEAD")) + private void preHandleKeybinds(CallbackInfo ci) { + if (GameEvents.handleKeybindsStart != null) { + GameEvents.handleKeybindsStart.run(); + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/ModelPartCubeMixin.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/ModelPartCubeMixin.java new file mode 100644 index 00000000..5383ccd1 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/ModelPartCubeMixin.java @@ -0,0 +1,23 @@ +package com.github.leawind.thirdperson.mixin; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.ThirdPersonStatus; +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.geom.ModelPart; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(value = ModelPart.Cube.class, priority = 2000) +public class ModelPartCubeMixin { + @ModifyVariable(at = @At("HEAD"), method = "compile", index = 8, argsOnly = true) + private float compile(float opacity) { + float partialTick = Minecraft.getInstance().getFrameTime(); + if (ThirdPerson.isAvailable() + && ThirdPersonStatus.isRenderingInThirdPerson() + && ThirdPersonStatus.useCameraEntityOpacity(partialTick)) { + return Math.min(opacity, ThirdPerson.ENTITY_AGENT.getSmoothOpacity(partialTick)); + } + return opacity; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/MouseHandlerMixin.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/MouseHandlerMixin.java new file mode 100644 index 00000000..b3833438 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/MouseHandlerMixin.java @@ -0,0 +1,35 @@ +package com.github.leawind.thirdperson.mixin; + +import com.github.leawind.thirdperson.api.base.GameEvents; +import com.github.leawind.thirdperson.api.client.event.MouseTurnPlayerStartEvent; +import net.minecraft.client.MouseHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = MouseHandler.class, priority = 2000) +public class MouseHandlerMixin { + @Shadow private double accumulatedDX; + @Shadow private double accumulatedDY; + + /** + * 在根据鼠标位移转动玩家前触发 + * + *

    如果在事件处理函数中调用了{@link MouseTurnPlayerStartEvent#cancelDefault()},则后续处理将会取消,好像鼠标没有移动一样。 + */ + @Inject(method = "turnPlayer()V", at = @At(value = "HEAD"), cancellable = true) + private void preTurnPlayer(CallbackInfo ci) { + if (GameEvents.mouseTurnPlayerStart != null) { + var event = new MouseTurnPlayerStartEvent(accumulatedDX, accumulatedDY); + GameEvents.mouseTurnPlayerStart.accept(event); + if (event.isDefaultCancelled()) { + // 重置累积变化量 + accumulatedDX = 0; + accumulatedDY = 0; + ci.cancel(); + } + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/RenderTypeInvoker.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/RenderTypeInvoker.java new file mode 100644 index 00000000..666de110 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/RenderTypeInvoker.java @@ -0,0 +1,16 @@ +package com.github.leawind.thirdperson.mixin; + +import com.mojang.blaze3d.vertex.VertexFormat; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.RenderType.CompositeRenderType; +import net.minecraft.client.renderer.RenderType.CompositeState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(RenderType.class) +public interface RenderTypeInvoker { + @Invoker("create") + static CompositeRenderType invokeCreate(String string, VertexFormat vertexFormat, VertexFormat.Mode mode, int i, boolean bl, boolean bl2, CompositeState compositeState) { + throw new AssertionError(); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/mixin/RenderTypeMixin.java b/common/src/main/java/com/github/leawind/thirdperson/mixin/RenderTypeMixin.java new file mode 100644 index 00000000..db548e3f --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/mixin/RenderTypeMixin.java @@ -0,0 +1,87 @@ +package com.github.leawind.thirdperson.mixin; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.ThirdPersonStatus; +import com.github.leawind.thirdperson.core.EntityAgent; +import com.github.leawind.thirdperson.util.annotation.VersionSensitive; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.VertexFormat; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.RenderStateShard; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.function.Function; + +@Mixin(value = RenderType.class, priority = 2000) +public class RenderTypeMixin extends RenderStateShard { + /** + * 修改自 RenderType#ARMOR_CUTOUT_NO_CULL + * + *

    将 NO_TRANSPARENCY 改成了 TRANSLUCENT_TRANSPARENCY + */ + @Unique + private static final Function ARMOR_CUTOUT_NO_CULL_TRANSLUCENT = + Util.memoize( + (resourceLocation) -> { + var compositeState = + RenderType.CompositeState.builder() + .setShaderState(RENDERTYPE_ARMOR_CUTOUT_NO_CULL_SHADER) + .setTextureState( + new RenderStateShard.TextureStateShard(resourceLocation, false, false)) + .setTransparencyState(TRANSLUCENT_TRANSPARENCY) + .setCullState(NO_CULL) + .setLightmapState(LIGHTMAP) + .setOverlayState(OVERLAY) + .setLayeringState(VIEW_OFFSET_Z_LAYERING) + .createCompositeState(true); + return RenderTypeInvoker.invokeCreate( + "armor_cutout_no_cull", + DefaultVertexFormat.NEW_ENTITY, + VertexFormat.Mode.QUADS, + 256, + true, + false, + compositeState); + }); + + @SuppressWarnings("unused") + public RenderTypeMixin(String name, Runnable setupState, Runnable clearState) { + super(name, setupState, clearState); + } + + /** + * 对盔甲和鞘翅使用自定义的 RenderType 提供器,实现半透明效果 + * + *

    see ModelPartCubeMixin#compile(float) + * + *

    + * + * @see EntityAgent#getSmoothOpacity(float) + */ + @VersionSensitive + @Inject( + method = "armorCutoutNoCull", + at = + @At( + value = "HEAD", + target = "Ljava/util/function/Function;apply(Ljava/lang/Object;)Ljava/lang/Object;"), + cancellable = true) + private static void setTransparencyState( + ResourceLocation resourceLocation, @NotNull CallbackInfoReturnable ci) { + if (ThirdPerson.isAvailable() + && ThirdPersonStatus.isRenderingInThirdPerson() + && ThirdPersonStatus.useCameraEntityOpacity(Minecraft.getInstance() + .getFrameTime())) { + ci.setReturnValue(ARMOR_CUTOUT_NO_CULL_TRANSLUCENT.apply(resourceLocation)); + ci.cancel(); + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/resources/ItemPredicateManager.java b/common/src/main/java/com/github/leawind/thirdperson/resources/ItemPredicateManager.java new file mode 100644 index 00000000..6b22e6ab --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/resources/ItemPredicateManager.java @@ -0,0 +1,160 @@ +package com.github.leawind.thirdperson.resources; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.config.Config; +import com.github.leawind.thirdperson.util.ItemPredicateUtil; +import com.github.leawind.thirdperson.util.annotation.VersionSensitive; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import net.minecraft.advancements.critereon.ItemPredicate; +import net.minecraft.client.resources.SplashManager; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.MultiPackResourceManager; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * 物品谓词资源管理器 + * + *

      + *
    • 物品谓词(ItemPredicate)是指 {@link ItemPredicate}对象,它代表一种规则,可以判断一个物品栈({@link ItemStack})是否符合其规则 + *
    • 物品模式(ItemPattern)是字符串,可以用{@link ItemPredicateUtil#parse(String)}将其解析为物品谓词 + *
    + * + * 资源包中的物品谓词集合采用json格式存储 + * + *

    重载资源包时,mc会调用{@link ItemPredicateManager#apply}方法处理读取到的json数据 + * + * @see ItemPredicateUtil + * @see SplashManager + */ +@VersionSensitive("SimpleJsonResourceReloadListener may not exist in other mc version") +public class ItemPredicateManager extends SimpleJsonResourceReloadListener { + private static final Gson GSON = new GsonBuilder().create(); + + private static final String ID = "item_patterns"; + + private static final String SET_HOLD_TO_AIM = "hold_to_aim"; + private static final String SET_USE_TO_AIM = "use_to_aim"; + private static final String SET_USE_TO_FIRST_PERSON = "use_to_first_person"; + + public final Map> holdToAimItemPatterns = new HashMap<>(); + public final Map> useToAimItemPatterns = new HashMap<>(); + public final Map> useToFirstPersonItemPatterns = new HashMap<>(); + + /** 玩家手持(主手或副手)这些物品时,进入瞄准模式 */ + public final Set holdToAimItemPredicates = new HashSet<>(); + + /** 玩家使用这些物品时,进入瞄准模式 */ + public final Set useToAimItemPredicates = new HashSet<>(); + + /** 玩家使用这些物品时,会暂时进入第一人称 */ + public final Set useToFirstPersonItemPredicates = new HashSet<>(); + + public ItemPredicateManager() { + super(GSON, ID); + } + + /** + * 重载资源包时会调用此方法,处理资源包中的json数据 + * + * @param map 资源地址与json数据的映射表 + * @param resourceManager {@link MultiPackResourceManager}的实例 + * @see Config#updateItemPredicates() + */ + @Override + public void apply( + @NotNull Map map, + ResourceManager resourceManager, + ProfilerFiller profile) { + holdToAimItemPatterns.clear(); + useToAimItemPatterns.clear(); + useToFirstPersonItemPatterns.clear(); + + map.forEach( + (resourceLocation, jsonElement) -> { + var obj = jsonElement.getAsJsonArray(); + var resourcePath = resourceLocation.getPath().split("/"); + var namespace = resourceLocation.getNamespace(); + + if (resourcePath.length >= 2) { + var resourceSetName = resourcePath[0]; + switch (resourceSetName) { + case SET_HOLD_TO_AIM -> addToSet(namespace, holdToAimItemPatterns, obj); + case SET_USE_TO_AIM -> addToSet(namespace, useToAimItemPatterns, obj); + case SET_USE_TO_FIRST_PERSON -> + addToSet(namespace, useToFirstPersonItemPatterns, obj); + } + } + }); + reparse(); + } + + private void addToSet( + String defaultNs, @NotNull Map> patternMap, @NotNull JsonArray arr) { + Set patterns; + if (patternMap.containsKey(defaultNs)) { + patterns = patternMap.get(defaultNs); + } else { + patterns = new HashSet<>(); + patternMap.put(defaultNs, patterns); + } + arr.forEach( + ele -> { + try { + var pattern = ele.getAsString(); + patterns.add(pattern); + } catch (Throwable e) { + ThirdPerson.LOGGER.warn(e.getMessage()); + } + }); + arr.size(); + } + + public void reparse() { + holdToAimItemPredicates.clear(); + useToAimItemPredicates.clear(); + useToFirstPersonItemPredicates.clear(); + + int count; + count = parseToSet(holdToAimItemPredicates, holdToAimItemPatterns); + if (count > 0) { + ThirdPerson.LOGGER.info("Loaded {} hold_to_aim item patterns from resource pack", count); + } + count = parseToSet(useToAimItemPredicates, useToAimItemPatterns); + if (count > 0) { + ThirdPerson.LOGGER.info("Loaded {} use_to_aim item patterns from resource pack", count); + } + count = parseToSet(useToFirstPersonItemPredicates, useToFirstPersonItemPatterns); + if (count > 0) { + ThirdPerson.LOGGER.info( + "Loaded {} use_to_first_person item patterns from resource pack", count); + } + } + + private int parseToSet(Set predicates, Map> patternMap) { + int count = 0; + for (var defaultNs : patternMap.keySet()) { + var patterns = patternMap.get(defaultNs); + for (var pattern : patterns) { + try { + predicates.add(ItemPredicateUtil.parse(pattern)); + count++; + } catch (IllegalArgumentException e) { + ThirdPerson.LOGGER.warn( + "Skip invalid item pattern: {} Because {}", pattern, e.getMessage()); + } + } + } + return count; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/screen/ClothConfigScreenBuilder.java b/common/src/main/java/com/github/leawind/thirdperson/screen/ClothConfigScreenBuilder.java new file mode 100644 index 00000000..80ea5e93 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/screen/ClothConfigScreenBuilder.java @@ -0,0 +1,599 @@ +package com.github.leawind.thirdperson.screen; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.config.AbstractConfig; +import com.github.leawind.thirdperson.config.Config; +import com.github.leawind.thirdperson.config.ConfigManager; +import com.github.leawind.thirdperson.util.ItemPredicateUtil; +import java.util.List; +import java.util.function.Consumer; +import me.shedaniel.clothconfig2.api.ConfigBuilder; +import me.shedaniel.clothconfig2.api.ConfigEntryBuilder; +import me.shedaniel.clothconfig2.gui.entries.BooleanListEntry; +import me.shedaniel.clothconfig2.gui.entries.DoubleListEntry; +import me.shedaniel.clothconfig2.gui.entries.IntegerSliderEntry; +import me.shedaniel.clothconfig2.gui.entries.StringListListEntry; +import me.shedaniel.clothconfig2.impl.builders.BooleanToggleBuilder; +import me.shedaniel.clothconfig2.impl.builders.SubCategoryBuilder; +import net.minecraft.client.gui.screens.Screen; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ClothConfigScreenBuilder extends ConfigScreenBuilder { + public @NotNull Screen build(@NotNull Config config, @Nullable Screen parent) { + final var builder = + ConfigBuilder.create() + .setParentScreen(parent) + .setTitle(ConfigManager.getText("text.title")) + .setSavingRunnable(ThirdPerson.CONFIG_MANAGER::trySave); + final var entryBuilder = builder.entryBuilder(); + var defaults = Config.DEFAULTS; + // ===============================================================================================================// + final var CATEGORY_COMMON = + builder.getOrCreateCategory(ConfigManager.getText("option_category.common")); + { + CATEGORY_COMMON.addEntry( + buildBooleanEntry( + "is_mod_enabled", + defaults.is_mod_enabled, + config.is_mod_enabled, + v -> config.is_mod_enabled = v, + entryBuilder)); + CATEGORY_COMMON.addEntry( + buildBooleanEntry( + "center_offset_when_flying", + defaults.center_offset_when_flying, + config.center_offset_when_flying, + v -> config.center_offset_when_flying = v, + entryBuilder)); + CATEGORY_COMMON.addEntry( + buildBooleanEntry( + "temp_first_person_in_narrow_space", + defaults.temp_first_person_in_narrow_space, + config.temp_first_person_in_narrow_space, + v -> config.temp_first_person_in_narrow_space = v, + entryBuilder)); + + // SubCategory: Player Rotation + final var SUBCATEGORY_PLAYER_ROTATION = + buildSubCategory("player_rotation", entryBuilder).setExpanded(false); + SUBCATEGORY_PLAYER_ROTATION.add( + entryBuilder + .startEnumSelector( + ConfigManager.getText(AbstractConfig.PlayerRotateMode.KEY), + AbstractConfig.PlayerRotateMode.class, + config.normal_rotate_mode) + .setEnumNameProvider(AbstractConfig.PlayerRotateMode::formatter) + .setSaveConsumer(v -> config.normal_rotate_mode = v) + .build()); + SUBCATEGORY_PLAYER_ROTATION.add( + buildBooleanEntry( + "auto_rotate_interacting", + defaults.auto_rotate_interacting, + config.auto_rotate_interacting, + v -> config.auto_rotate_interacting = v, + entryBuilder)); + SUBCATEGORY_PLAYER_ROTATION.add( + buildBooleanEntry( + "do_not_rotate_when_eating", + defaults.do_not_rotate_when_eating, + config.do_not_rotate_when_eating, + v -> config.do_not_rotate_when_eating = v, + entryBuilder)); + SUBCATEGORY_PLAYER_ROTATION.add( + buildBooleanEntry( + "auto_turn_body_drawing_a_bow", + defaults.auto_turn_body_drawing_a_bow, + config.auto_turn_body_drawing_a_bow, + v -> config.auto_turn_body_drawing_a_bow = v, + entryBuilder)); + CATEGORY_COMMON.addEntry(SUBCATEGORY_PLAYER_ROTATION.build()); + + // SubCategory: Player Fade out + final var Subcategory_Player_Fade_Out = + buildSubCategory("player_fade_out", entryBuilder).setExpanded(false); + Subcategory_Player_Fade_Out.add( + buildBooleanEntry( + "player_fade_out_enabled", + defaults.player_fade_out_enabled, + config.player_fade_out_enabled, + v -> config.player_fade_out_enabled = v, + entryBuilder)); + Subcategory_Player_Fade_Out.add( + buildDoubleEntry( + "gaze_opacity", + 0D, + 1D, + defaults.gaze_opacity, + config.gaze_opacity, + v -> config.gaze_opacity = v, + entryBuilder)); + Subcategory_Player_Fade_Out.add( + buildDoubleEntry( + "player_invisible_threshold", + 0D, + 1D, + defaults.player_invisible_threshold, + config.player_invisible_threshold, + v -> config.player_invisible_threshold = v, + entryBuilder)); + CATEGORY_COMMON.addEntry(Subcategory_Player_Fade_Out.build()); + + // SubCategory: Camera Distance Adjustment + final var Subcategory_Camera_Distance_Adjustment = + buildSubCategory("camera_distance_adjustment", entryBuilder).setExpanded(false); + Subcategory_Camera_Distance_Adjustment.add( + buildIntSliderEntry( + "available_distance_count", + 2, + 64, + defaults.available_distance_count, + config.available_distance_count, + v -> config.available_distance_count = v, + entryBuilder)); + Subcategory_Camera_Distance_Adjustment.add( + buildDoubleEntry( + "camera_distance_min", + 0, + 6D, + defaults.camera_distance_min, + config.camera_distance_min, + v -> config.camera_distance_min = Math.min(v, config.camera_distance_max), + entryBuilder)); + Subcategory_Camera_Distance_Adjustment.add( + buildDoubleEntry( + "camera_distance_max", + 0, + 6D, + defaults.camera_distance_max, + config.camera_distance_max, + v -> config.camera_distance_max = Math.max(v, config.camera_distance_min), + entryBuilder)); + CATEGORY_COMMON.addEntry(Subcategory_Camera_Distance_Adjustment.build()); + } + // ===============================================================================================================// + // Category: smooth factors + final var CATEGORY_SMOOTH_FACTORS = + builder.getOrCreateCategory(ConfigManager.getText("option_category.smoothness")); + { + CATEGORY_SMOOTH_FACTORS.addEntry( + buildSmoothHalflifeEntry( + "flying_smooth_halflife", + defaults.flying_smooth_halflife, + config.flying_smooth_halflife, + v -> config.flying_smooth_halflife = v, + entryBuilder)); + CATEGORY_SMOOTH_FACTORS.addEntry( + buildSmoothHalflifeEntry( + "t2f_transition_halflife", + defaults.t2f_transition_halflife, + config.t2f_transition_halflife, + v -> config.t2f_transition_halflife = v, + entryBuilder)); + + // SubCategory: Adjusting Camera + final var Subcategory_Adjusting_Camera = buildSubCategory("adjusting_camera", entryBuilder); + Subcategory_Adjusting_Camera.add( + buildSmoothHalflifeEntry( + "adjusting_camera_offset_smooth_halflife", + defaults.adjusting_camera_offset_smooth_halflife, + config.adjusting_camera_offset_smooth_halflife, + v -> config.adjusting_camera_offset_smooth_halflife = v, + entryBuilder)); + Subcategory_Adjusting_Camera.add( + buildSmoothHalflifeEntry( + "adjusting_distance_smooth_halflife", + defaults.adjusting_distance_smooth_halflife, + config.adjusting_distance_smooth_halflife, + v -> config.adjusting_distance_smooth_halflife = v, + entryBuilder)); + CATEGORY_SMOOTH_FACTORS.addEntry(Subcategory_Adjusting_Camera.build()); + + // SubCategory: Normal Mode + final var SubCategory_Normal_Mode = buildSubCategory("normal_mode", entryBuilder); + SubCategory_Normal_Mode.add( + buildSmoothHalflifeEntry( + "smooth_halflife_horizon", + defaults.normal_smooth_halflife_horizon, + config.normal_smooth_halflife_horizon, + v -> config.normal_smooth_halflife_horizon = v, + entryBuilder)); + SubCategory_Normal_Mode.add( + buildSmoothHalflifeEntry( + "smooth_halflife_vertical", + defaults.normal_smooth_halflife_vertical, + config.normal_smooth_halflife_vertical, + v -> config.normal_smooth_halflife_vertical = v, + entryBuilder)); + SubCategory_Normal_Mode.add( + buildSmoothHalflifeEntry( + "camera_offset_smooth_halflife", + defaults.normal_camera_offset_smooth_halflife, + config.normal_camera_offset_smooth_halflife, + v -> config.normal_camera_offset_smooth_halflife = v, + entryBuilder)); + SubCategory_Normal_Mode.add( + buildSmoothHalflifeEntry( + "distance_smooth_halflife", + defaults.normal_distance_smooth_halflife, + config.normal_distance_smooth_halflife, + v -> config.normal_distance_smooth_halflife = v, + entryBuilder)); + CATEGORY_SMOOTH_FACTORS.addEntry(SubCategory_Normal_Mode.build()); + + // SubCategory: Aiming Mode + final var Subcategory_Aiming_Mode = buildSubCategory("aiming_mode", entryBuilder); + Subcategory_Aiming_Mode.add( + buildSmoothHalflifeEntry( + "smooth_halflife_horizon", + defaults.aiming_smooth_halflife_horizon, + config.aiming_smooth_halflife_horizon, + v -> config.aiming_smooth_halflife_horizon = v, + entryBuilder)); + Subcategory_Aiming_Mode.add( + buildSmoothHalflifeEntry( + "smooth_halflife_vertical", + defaults.aiming_smooth_halflife_vertical, + config.aiming_smooth_halflife_vertical, + v -> config.aiming_smooth_halflife_vertical = v, + entryBuilder)); + Subcategory_Aiming_Mode.add( + buildSmoothHalflifeEntry( + "camera_offset_smooth_halflife", + defaults.aiming_camera_offset_smooth_halflife, + config.aiming_camera_offset_smooth_halflife, + v -> config.aiming_camera_offset_smooth_halflife = v, + entryBuilder)); + Subcategory_Aiming_Mode.add( + buildSmoothHalflifeEntry( + "distance_smooth_halflife", + defaults.aiming_distance_smooth_halflife, + config.aiming_distance_smooth_halflife, + v -> config.aiming_distance_smooth_halflife = v, + entryBuilder)); + CATEGORY_SMOOTH_FACTORS.addEntry(Subcategory_Aiming_Mode.build()); + } + // ===============================================================================================================// + // Category: Camera Offset + final var CATEGORY_CAMERA_OFFSET = + builder.getOrCreateCategory(ConfigManager.getText("option_category.camera_offset")); + { + CATEGORY_CAMERA_OFFSET.addEntry( + buildDoubleEntry( + "aiming_fov_divisor", + 1D, + 2D, + defaults.aiming_fov_divisor, + config.aiming_fov_divisor, + v -> config.aiming_fov_divisor = v, + entryBuilder)); + + // SubCategory: Normal Mode + final var SubCategory_Normal_Mode = buildSubCategory("normal_mode", entryBuilder); + SubCategory_Normal_Mode.add( + buildDoubleEntry( + "max_distance", + 0D, + 6D, + defaults.normal_max_distance, + config.normal_max_distance, + v -> config.normal_max_distance = v, + entryBuilder)); + SubCategory_Normal_Mode.add( + buildDoubleEntry( + "offset_x", + -1, + +1, + defaults.normal_offset_x, + config.normal_offset_x, + v -> config.normal_offset_x = v, + entryBuilder)); + SubCategory_Normal_Mode.add( + buildDoubleEntry( + "offset_y", + -1, + +1, + defaults.normal_offset_y, + config.normal_offset_y, + v -> config.normal_offset_y = v, + entryBuilder)); + SubCategory_Normal_Mode.add( + buildBooleanEntry( + "is_centered", + defaults.normal_is_centered, + config.normal_is_centered, + v -> config.normal_is_centered = v, + entryBuilder)); + SubCategory_Normal_Mode.add( + buildDoubleEntry( + "offset_center", + -1, + +1, + defaults.normal_offset_center, + config.normal_offset_center, + v -> config.normal_offset_center = v, + entryBuilder)); + CATEGORY_CAMERA_OFFSET.addEntry(SubCategory_Normal_Mode.build()); + + // SubCategory: Aiming Mode + final var Subcategory_Aiming_Mode = buildSubCategory("aiming_mode", entryBuilder); + Subcategory_Aiming_Mode.add( + buildDoubleEntry( + "max_distance", + 0D, + 6D, + defaults.aiming_max_distance, + config.aiming_max_distance, + v -> config.aiming_max_distance = v, + entryBuilder)); + Subcategory_Aiming_Mode.add( + buildDoubleEntry( + "offset_x", + -1, + +1, + defaults.aiming_offset_x, + config.aiming_offset_x, + v -> config.aiming_offset_x = v, + entryBuilder)); + Subcategory_Aiming_Mode.add( + buildDoubleEntry( + "offset_y", + -1, + +1, + defaults.aiming_offset_y, + config.aiming_offset_y, + v -> config.aiming_offset_y = v, + entryBuilder)); + Subcategory_Aiming_Mode.add( + buildBooleanEntry( + "is_centered", + defaults.aiming_is_centered, + config.aiming_is_centered, + v -> config.aiming_is_centered = v, + entryBuilder)); + Subcategory_Aiming_Mode.add( + buildDoubleEntry( + "offset_center", + -1, + +1, + defaults.aiming_offset_center, + config.aiming_offset_center, + v -> config.aiming_offset_center = v, + entryBuilder)); + CATEGORY_CAMERA_OFFSET.addEntry(Subcategory_Aiming_Mode.build()); + } + // ===============================================================================================================// + // Category: Aiming Check + final var CATEGORY_AIMING_CHECK = + builder.getOrCreateCategory(ConfigManager.getText("option_category.item_predicates")); + { + CATEGORY_AIMING_CHECK.addEntry( + buildBooleanEntry( + "determine_aim_mode_by_animation", + defaults.determine_aim_mode_by_animation, + config.determine_aim_mode_by_animation, + v -> config.determine_aim_mode_by_animation = v, + entryBuilder)); + CATEGORY_AIMING_CHECK.addEntry( + buildStringListEntry( + "hold_to_aim_item_pattern_expressions", + defaults.hold_to_aim_item_patterns, + config.hold_to_aim_item_patterns, + v -> config.hold_to_aim_item_patterns = v, + entryBuilder)); + CATEGORY_AIMING_CHECK.addEntry( + buildStringListEntry( + "use_to_aim_item_pattern_expressions", + defaults.use_to_aim_item_patterns, + config.use_to_aim_item_patterns, + v -> config.use_to_aim_item_patterns = v, + entryBuilder)); + CATEGORY_AIMING_CHECK.addEntry( + buildStringListEntry( + "use_to_first_person_pattern_expressions", + defaults.use_to_first_person_patterns, + config.use_to_first_person_patterns, + v -> config.use_to_first_person_patterns = v, + entryBuilder)); + } + // ===============================================================================================================// + final var CATEGORY_OTHER = + builder.getOrCreateCategory(ConfigManager.getText("option_category.other")); + { + if (getAvailableBuidlers().size() > 1) { + CATEGORY_OTHER.addEntry( + entryBuilder + .startDropdownMenu( + ConfigManager.getText("option.config_screen_api"), + config.config_screen_api, + v -> config.config_screen_api = v) + .setSelections(getAvailableBuidlers().keySet()) + .build()); + } + CATEGORY_OTHER.addEntry( + entryBuilder + .startEnumSelector( + ConfigManager.getText(AbstractConfig.CameraDistanceMode.KEY), + AbstractConfig.CameraDistanceMode.class, + config.camera_distance_mode) + .setEnumNameProvider(AbstractConfig.CameraDistanceMode::formatter) + .setSaveConsumer(v -> config.camera_distance_mode = v) + .build()); + CATEGORY_OTHER.addEntry( + buildDoubleEntry( + "rotate_center_height_offset", + -0.5, + 0.5, + defaults.rotate_center_height_offset, + config.rotate_center_height_offset, + v -> config.rotate_center_height_offset = v, + entryBuilder)); + CATEGORY_OTHER.addEntry( + buildBooleanEntry( + "enable_target_entity_predict", + defaults.enable_target_entity_predict, + config.enable_target_entity_predict, + v -> config.enable_target_entity_predict = v, + entryBuilder)); + CATEGORY_OTHER.addEntry( + buildBooleanEntry( + "skip_vanilla_second_person_camera", + defaults.skip_vanilla_second_person_camera, + config.skip_vanilla_second_person_camera, + v -> config.skip_vanilla_second_person_camera = v, + entryBuilder)); + CATEGORY_OTHER.addEntry( + buildBooleanEntry( + "disable_third_person_bob_view", + defaults.disable_third_person_bob_view, + config.disable_third_person_bob_view, + v -> config.disable_third_person_bob_view = v, + entryBuilder)); + CATEGORY_OTHER.addEntry( + buildBooleanEntry( + "allow_double_tap_sprint", + defaults.allow_double_tap_sprint, + config.allow_double_tap_sprint, + v -> config.allow_double_tap_sprint = v, + entryBuilder)); + CATEGORY_OTHER.addEntry( + buildBooleanEntry( + "lock_camera_pitch_angle", + defaults.lock_camera_pitch_angle, + config.lock_camera_pitch_angle, + v -> config.lock_camera_pitch_angle = v, + entryBuilder)); + CATEGORY_OTHER.addEntry( + buildBooleanEntry( + "use_camera_pick_in_creative", + defaults.use_camera_pick_in_creative, + config.use_camera_pick_in_creative, + v -> config.use_camera_pick_in_creative = v, + entryBuilder)); + CATEGORY_OTHER.addEntry( + buildDoubleEntry( + "camera_ray_trace_length", + 32D, + 2048D, + defaults.camera_ray_trace_length, + config.camera_ray_trace_length, + v -> config.camera_ray_trace_length = v, + entryBuilder)); + + // SubCategory: Crosshair + final var Subcategory_Crosshair = buildSubCategory("crosshair", entryBuilder); + Subcategory_Crosshair.add( + buildBooleanEntry( + "render_crosshair_when_not_aiming", + defaults.render_crosshair_when_not_aiming, + config.render_crosshair_when_not_aiming, + v -> config.render_crosshair_when_not_aiming = v, + entryBuilder)); + Subcategory_Crosshair.add( + buildBooleanEntry( + "render_crosshair_when_aiming", + defaults.render_crosshair_when_aiming, + config.render_crosshair_when_aiming, + v -> config.render_crosshair_when_aiming = v, + entryBuilder)); + Subcategory_Crosshair.add( + buildBooleanEntry( + "hide_crosshair_when_flying", + defaults.hide_crosshair_when_flying, + config.hide_crosshair_when_flying, + v -> config.hide_crosshair_when_flying = v, + entryBuilder)); + CATEGORY_OTHER.addEntry(Subcategory_Crosshair.build()); + } + return builder.build(); + } + + private BooleanToggleBuilder booleanEntry( + String name, + boolean defaultValue, + boolean currentValue, + Consumer setter, + ConfigEntryBuilder entryBuilder) { + return entryBuilder + .startBooleanToggle(ConfigManager.getText("option." + name), currentValue) + .setTooltip(ConfigManager.getText("option." + name + ".desc")) + .setDefaultValue(defaultValue) + .setSaveConsumer(setter); + } + + private BooleanListEntry buildBooleanEntry( + String name, + boolean defaultValue, + boolean currentValue, + Consumer setter, + ConfigEntryBuilder entryBuilder) { + return booleanEntry(name, defaultValue, currentValue, setter, entryBuilder).build(); + } + + private SubCategoryBuilder buildSubCategory(String name, ConfigEntryBuilder entryBuilder) { + return entryBuilder + .startSubCategory(ConfigManager.getText("option_group." + name)) + .setExpanded(true) + .setTooltip(ConfigManager.getText("option_group." + name + ".desc")); + } + + private IntegerSliderEntry buildIntSliderEntry( + String name, + int min, + int max, + int defaultValue, + int currentValue, + Consumer setter, + ConfigEntryBuilder entryBuilder) { + return entryBuilder + .startIntSlider(ConfigManager.getText("option." + name), currentValue, min, max) + .setTooltip(ConfigManager.getText("option." + name + ".desc")) + .setDefaultValue(defaultValue) + .setSaveConsumer(setter) + .build(); + } + + private DoubleListEntry buildDoubleEntry( + String name, + double min, + double max, + double defaultValue, + double currentValue, + Consumer setter, + ConfigEntryBuilder entryBuilder) { + return entryBuilder + .startDoubleField(ConfigManager.getText("option." + name), currentValue) + .setTooltip(ConfigManager.getText("option." + name + ".desc")) + .setDefaultValue(defaultValue) + .setSaveConsumer(setter) + .setMin(min) + .setMax(max) + .build(); + } + + private DoubleListEntry buildSmoothHalflifeEntry( + String name, + double defaultValue, + double currentValue, + Consumer setter, + ConfigEntryBuilder entryBuilder) { + return buildDoubleEntry(name, 0, 1, defaultValue, currentValue, setter, entryBuilder); + } + + private StringListListEntry buildStringListEntry( + String name, + List defaultValue, + List currentValue, + Consumer> setter, + ConfigEntryBuilder entryBuilder) { + return entryBuilder + .startStrList(ConfigManager.getText("option." + name), currentValue) + .setTooltip(ConfigManager.getText("option." + name + ".desc")) + .setSaveConsumer(setter) + .setDefaultValue(defaultValue) + .setDeleteButtonEnabled(true) + .setCellErrorSupplier(ItemPredicateUtil::supplyError) + .setExpanded(true) + .build(); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/screen/ConfigScreenBuilder.java b/common/src/main/java/com/github/leawind/thirdperson/screen/ConfigScreenBuilder.java new file mode 100644 index 00000000..f67cd92e --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/screen/ConfigScreenBuilder.java @@ -0,0 +1,86 @@ +package com.github.leawind.thirdperson.screen; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.config.Config; +import com.github.leawind.thirdperson.config.ConfigManager; +import com.github.leawind.thirdperson.util.PossibleSupplier; +import com.github.leawind.thirdperson.util.annotation.VersionSensitive; +import dev.architectury.platform.Platform; +import java.util.HashMap; +import java.util.Map; +import net.minecraft.client.gui.screens.Screen; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * 配置屏幕构建器 + * + * @see ConfigManager#getConfigScreen(Screen) + */ +@SuppressWarnings("all") +@VersionSensitive("YACL version check") +public abstract class ConfigScreenBuilder { + /** + * 构建配置屏幕 + * + * @param config 配置实例 + * @param parent 父屏幕 + * @return 配置屏幕 + */ + @NotNull + public abstract Screen build(@NotNull Config config, @Nullable Screen parent); + + /** 已经实现或将来可能实现的构建器们 */ + private static Map> builders = new HashMap<>(); + + static { + builders.put( + "Cloth Config", + PossibleSupplier.of( + () -> new ClothConfigScreenBuilder(), + () -> Platform.isModLoaded("cloth-config") || Platform.isModLoaded("cloth_config"))); + // builders.put( + // "YACL", + // PossibleSupplier.of( + // () -> new YaclConfigScreenBuilder(), + // () -> + // Platform.isModLoaded("yet_another_config_lib_v3") + // && !(Platform.isForge() + // && !Platform.getMod("yet_another_config_lib_v3") + // .getVersion() + // .startsWith("3.2.")))); + + var availables = ConfigScreenBuilder.getAvailableBuidlers().keySet(); + availables.forEach( + name -> { + ThirdPerson.LOGGER.debug("Found available config screen builder: {}", name); + }); + if (availables.isEmpty()) { + ThirdPerson.LOGGER.warn("No config screen API available."); + } + } + + /** 根据配置获取屏幕构建器 */ + public static @Nullable ConfigScreenBuilder getBuilder() { + final var availables = getAvailableBuidlers(); + if (availables.isEmpty()) { + return null; + } + return availables + .getOrDefault( + ThirdPerson.getConfig().config_screen_api, availables.values().iterator().next()) + .get(); + } + + /** 获取全部可用的构建器 */ + public static @NotNull Map> getAvailableBuidlers() { + final Map> availableBuilders = new HashMap<>(); + builders.forEach( + (name, builder) -> { + if (builder.available()) { + availableBuilders.put(name, builder); + } + }); + return availableBuilders; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/FiniteChecker.java b/common/src/main/java/com/github/leawind/thirdperson/util/FiniteChecker.java new file mode 100644 index 00000000..aba90ef5 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/FiniteChecker.java @@ -0,0 +1,94 @@ +package com.github.leawind.thirdperson.util; + +import java.util.function.Consumer; + +public final class FiniteChecker { + private final Consumer printer; + private boolean failedOnce = false; + + public FiniteChecker(Consumer printer) { + this.printer = printer; + } + + public void reset() { + failedOnce = false; + } + + /** + * 检查数值是否为有限值,若不是则调用 printer 打印异常信息,并返回 true + * + *

    此后若再调用此方法将不再调用 printer,除非通过{@link FiniteChecker#reset()}重置标志 + */ + public boolean checkOnce(Object... objects) { + for (int i = 0; i < objects.length; i++) { + var obj = objects[i]; + if (notFinite(obj)) { + if (!failedOnce) { + var err = new InfiniteException(obj, String.format("objects[%d] is not finite", i)); + printer.accept(err); + failedOnce = true; + } + return true; + } + } + return false; + } + + public boolean check(Object... objects) { + for (int i = 0; i < objects.length; i++) { + var obj = objects[i]; + if (notFinite(obj)) { + var err = new InfiniteException(obj, String.format("objects[%d] is not finite", i)); + printer.accept(err); + failedOnce = true; + return true; + } + } + return false; + } + + public static void assertFinite(Object... objects) { + for (int i = 0; i < objects.length; i++) { + var obj = objects[i]; + if (notFinite(obj)) { + throw new InfiniteException(obj, String.format("objects[%d] is not finite", i)); + } + } + } + + private static boolean isFinite(Object obj) { + return (obj instanceof Float && Float.isFinite((Float) obj)) + || (obj instanceof Double && Double.isFinite((Double) obj)); + } + + private static boolean notFinite(Object obj) { + return !isFinite(obj); + } + + public static class InfiniteException extends RuntimeException { + public final Object object; + public final String message; + + public InfiniteException(Object object) { + this(object, "Object is not finite"); + } + + public InfiniteException(Object object, String message) { + this.object = object; + this.message = message; + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append(String.format("InfiniteException: %s\n", message)); + s.append(String.format("Object: %s\n", object)); + s.append("Stacktrace:\n"); + var trace = Thread.currentThread().getStackTrace(); + for (StackTraceElement traceElement : trace) { + s.append("\tat ").append(traceElement).append("\n"); + } + return s.toString(); + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/ItemPredicateUtil.java b/common/src/main/java/com/github/leawind/thirdperson/util/ItemPredicateUtil.java new file mode 100644 index 00000000..ae6acffe --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/ItemPredicateUtil.java @@ -0,0 +1,224 @@ +package com.github.leawind.thirdperson.util; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.google.common.collect.ImmutableSet; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.regex.Pattern; +import me.shedaniel.clothconfig2.impl.builders.StringListBuilder; +import net.minecraft.ResourceLocationException; +import net.minecraft.advancements.critereon.EnchantmentPredicate; +import net.minecraft.advancements.critereon.ItemPredicate; +import net.minecraft.advancements.critereon.MinMaxBounds; +import net.minecraft.advancements.critereon.NbtPredicate; +import net.minecraft.commands.arguments.item.ItemParser; +import net.minecraft.core.Registry; +import net.minecraft.nbt.TagParser; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * + * + *

    {@code
    + * minecraft:bow
    + * minecraft:crossbow{Charged:1b}
    + * #minecraft:boats
    + * #minecraft:banners{x:-32}
    + * bow
    + * crossbow{Charged:1b}
    + * #boats
    + * #banners{x:-32}
    + * }
    + * + * @see ItemParser + * @see ItemPredicate + * @see NbtPredicate + */ +public final class ItemPredicateUtil { + private static final Pattern RGX_NBT = Pattern.compile("^(\\{.*})$"); + + /** "#<namespace>:<id>{<nbt>}" */ + private static final Pattern RGX_TAG_NBT = Pattern.compile("^#([a-z0-9.:_]+)(\\{.*})?$"); + + /** "<namespace>:<id>{<nbt>}" */ + private static final Pattern RGX_KEY_NBT = Pattern.compile("^([a-z0-9.:_]+)(\\{.*})?$"); + + /** + * @see StringListBuilder#setCellErrorSupplier(Function) + */ + public static @NotNull Optional supplyError(String pattern) { + try { + parse("minecraft", pattern); + return Optional.empty(); + } catch (IllegalArgumentException e) { + return Optional.of(Component.literal(e.getMessage())); + } catch (IllegalStateException e) { + return Optional.empty(); + } + } + + @SafeVarargs + public static boolean anyMatches( + @NotNull ItemStack itemStack, Iterable @NotNull ... predicatesList) { + if (itemStack.isEmpty()) { + return false; + } + for (var predicates : predicatesList) { + for (var predicate : predicates) { + if (predicate.matches(itemStack)) { + return true; + } + } + } + return false; + } + + public static int addToSet( + @NotNull String defaultNamespace, + @NotNull Set predicates, + @Nullable Iterable patterns) { + int count = 0; + if (patterns != null) { + for (var pattern : patterns) { + try { + predicates.add(parse(defaultNamespace, pattern)); + count++; + } catch (IllegalArgumentException e) { + ThirdPerson.LOGGER.error( + "Skip invalid item pattern: {}, because {}", pattern, e.getMessage()); + } catch (IllegalStateException e) { + ThirdPerson.LOGGER.warn( + "Skip invalid item pattern: {}, because {}", pattern, e.getMessage()); + } + } + } + return count; + } + + public static ItemPredicate parse(String pattern) + throws IllegalArgumentException, IllegalStateException { + return parse("minecraft", pattern); + } + + /** + * @param defaultNs 默认命名空间 + * @param pattern 源字符串 + * @throws IllegalArgumentException 格式不正确或存在语法错误 + * @throws IllegalStateException 物品ID不存在或为 minecraft:air + */ + public static ItemPredicate parse(String defaultNs, String pattern) + throws IllegalArgumentException, IllegalStateException { + if (pattern.isEmpty()) { + throw new IllegalArgumentException("Empty item pattern"); + } else if (pattern.startsWith("{")) { + return parseNbtPredicate(defaultNs, pattern); + } else if (pattern.startsWith("#")) { + return parseTagPredicate(defaultNs, pattern); + } else { + return parseKeyPredicate(defaultNs, pattern); + } + } + + private static ItemPredicate parseNbtPredicate(String defaultNs, String pattern) + throws IllegalArgumentException { + if (!RGX_NBT.matcher(pattern).matches()) { + throw new IllegalArgumentException(String.format("Invalid NBT: %s", pattern)); + } + try { + return of(defaultNs, pattern, null, null); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException( + String.format("Invalid NBT: %s, %s", pattern, e.getMessage())); + } + } + + private static ItemPredicate parseTagPredicate(String defaultNs, String pattern) + throws IllegalArgumentException { + var m = RGX_TAG_NBT.matcher(pattern); + if (m.matches()) { + return of(defaultNs, m.group(2), m.group(1), null); + } + throw new IllegalArgumentException(String.format("Invalid item tag: %s", pattern)); + } + + private static ItemPredicate parseKeyPredicate(String defaultNs, String pattern) + throws IllegalArgumentException, IllegalStateException { + var m = RGX_KEY_NBT.matcher(pattern); + if (m.matches()) { + return of(defaultNs, m.group(2), null, m.group(1)); + } else { + throw new IllegalArgumentException(String.format("Invalid item pattern: %s", pattern)); + } + } + + /** + * @param defaultNs 默认命名空间 + * @param nbtPattern NBT模板,null 表示任意,例如 "{Charged:1b}" + * @param tagKeyPattern 物品标签,例如 "minecraft:boats" + * @param itemKeyPattern 物品键,例如 "minecraft:bow"。null表示任意,空字符串表示不匹配任何物品 + * @return {@link ItemPredicate} + * @throws IllegalArgumentException 格式错误 + * @throws IllegalStateException 物品不存在或为 minecraft:air + */ + private static ItemPredicate of( + String defaultNs, + @Nullable String nbtPattern, + @Nullable String tagKeyPattern, + @Nullable String itemKeyPattern) + throws IllegalArgumentException, IllegalStateException { + NbtPredicate nbt; + TagKey tagKey = null; + Set items; + try { + nbt = + nbtPattern == null ? NbtPredicate.ANY : new NbtPredicate(TagParser.parseTag(nbtPattern)); + } catch (CommandSyntaxException e) { + throw new IllegalArgumentException(e.getMessage()); + } + if (tagKeyPattern != null) { + tagKey = TagKey.create(Registry.ITEM.key(), parseResourceLocation(defaultNs, tagKeyPattern)); + } + if (itemKeyPattern == null) { + items = null; + } else if (itemKeyPattern.isEmpty()) { + items = ImmutableSet.of(); + } else { + var resourceLocation = parseResourceLocation(defaultNs, itemKeyPattern); + var item = Registry.ITEM.get(resourceLocation); + if (item == Items.AIR) { + throw new IllegalStateException( + String.format("Item %s does not exist or it's minecraft:air", resourceLocation)); + } + items = ImmutableSet.of(item); + } + return new ItemPredicate( + tagKey, + items, + MinMaxBounds.Ints.ANY, + MinMaxBounds.Ints.ANY, + EnchantmentPredicate.NONE, + EnchantmentPredicate.NONE, + null, + nbt); + } + + private static ResourceLocation parseResourceLocation(String defaultNamespace, String pattern) + throws IllegalArgumentException { + try { + return pattern.indexOf(':') < 0 + ? new ResourceLocation(defaultNamespace, pattern) + : new ResourceLocation(pattern); + } catch (ResourceLocationException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/PossibleSupplier.java b/common/src/main/java/com/github/leawind/thirdperson/util/PossibleSupplier.java new file mode 100644 index 00000000..8aefb702 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/PossibleSupplier.java @@ -0,0 +1,54 @@ +package com.github.leawind.thirdperson.util; + +import java.util.function.Supplier; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * PossibleSupplier 接口为可能不可用的供应操作提供了一种表示方法。 + * + *

    它允许在调用时检查供应操作是否可用。 + * + * @param 供应操作返回的类型 + */ +public interface PossibleSupplier { + /** + * 执行供应操作并获取其结果。 + * + * @return 供应操作的结果 + */ + T get(); + + /** + * 检查此供应操作是否可用。 + * + * @return 如果供应操作可用,则返回true;否则返回false + */ + boolean available(); + + /** + * 创建并返回一个新的PossibleSupplier实例。 + * + *

    该方法通过合并一个供应商和一个可用性谓词,提供了一个方便的方式来处理可能不可用的供应操作。 + * + * @param supplier 提供值的供应操作 + * @param availablePredicate 用于判断供应操作是否可用的供应操作 + * @param 供应操作返回的类型 + * @return 一个新的PossibleSupplier实例,封装了提供的供应操作和可用性谓词 + */ + @Contract(value = "_, _ -> new", pure = true) + static @NotNull PossibleSupplier of( + Supplier supplier, Supplier availablePredicate) { + return new PossibleSupplier<>() { + @Override + public E get() { + return supplier.get(); + } + + @Override + public boolean available() { + return availablePredicate.get(); + } + }; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/Surroundings.java b/common/src/main/java/com/github/leawind/thirdperson/util/Surroundings.java new file mode 100644 index 00000000..25409b26 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/Surroundings.java @@ -0,0 +1,221 @@ +package com.github.leawind.thirdperson.util; + +import com.github.leawind.thirdperson.util.math.LMath; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; +import org.joml.Vector3i; + +@SuppressWarnings("unused") +public class Surroundings { + private static final Pattern BLANK_PATTERN = Pattern.compile("[\\s\\n|]"); + private static final Pattern SEPARATOR_PATTERN = Pattern.compile("[\\s\\n|]+"); + + /** Identifier -> offset[] */ + private final Map> identifierMap = new HashMap<>(); + + /** Identifier -> Matches[] */ + private final Map matchesMap = new HashMap<>(); + + /** + * 解析提供的多行字符串表达式,该表达式定义一个3x3x3区域的方块布局。 + * + *

    表达式中的每个字符代表一个标识符,用于区分不同的方块类型或位置。 + * + *

    表达式可以由多个部分组成,每个部分之间通过空格、换行符或竖线(|)分隔,每个部分内部也使用相同的分隔符来组织字符。 + * + *

    示例: + * + *

    +   * new Surroundings("""
    +   *     B B B  M M M  T T T
    +   *     B B B  M M M  T T T
    +   *     B B B  M M M  T T T
    +   * """);
    +   * 
    + * + * 在此示例中,'B', 'M', 'T' 都是不同的标识符,它们分别代表了不同类型的方块或位置。 + * + *

    其中 B 表示下层 3x1x3 的方块,M 表示中间 3x1x3 的一层方块,T 表示上层 3x1x3 的方块 + */ + public Surroundings(String expr) { + var tokens = SEPARATOR_PATTERN.split(BLANK_PATTERN.matcher(expr).replaceAll(" ").trim()); + for (int i = 0; i < tokens.length; i++) { + var word = tokens[i]; + List offsets; + if (identifierMap.containsKey(word)) { + offsets = identifierMap.get(word); + } else { + offsets = new ArrayList<>(27); + identifierMap.put(word, offsets); + } + + int ix = i % 3; + int iy = (i / 3) % 3; + int iz = i / 9; + offsets.add(new Vector3i(ix - 1, iy - 1, iz - 1)); + } + } + + public void clear() { + this.matchesMap.clear(); + } + + public void rematch(BlockPos center, BlockGetter level, Predicate predicate) { + clear(); + match(center, level, predicate); + } + + public void match(BlockPos center, BlockGetter level, Predicate predicate) { + for (var identifier : getIdentifiers()) { + var seq = new Matches(getOffsets(identifier), center, level); + matchesMap.put(identifier, seq); + } + for (var seq : matchesMap.values()) { + seq.apply(predicate); + } + } + + public Matches getMatches(String identifier) { + if (!matchesMap.containsKey(identifier)) { + throw new IllegalArgumentException("Unknown identifier: " + identifier); + } + return matchesMap.get(identifier); + } + + public Set getIdentifiers() { + return identifierMap.keySet(); + } + + public List getOffsets(String identifier) { + if (!identifierMap.containsKey(identifier)) { + throw new IllegalStateException("Unknown identifier: " + identifier); + } + return identifierMap.get(identifier); + } + + public static class Matches { + private final BlockState[] blockStates; + private final Vector3i[] offsets; + private final boolean[] states; + private boolean isApplied = false; + + private Matches(List offsets, BlockPos center, BlockGetter level) { + blockStates = new BlockState[offsets.size()]; + states = new boolean[offsets.size()]; + this.offsets = new Vector3i[offsets.size()]; + offsets.toArray(this.offsets); + for (int i = 0; i < blockStates.length; i++) { + var offset = offsets.get(i); + var pos = center.offset(LMath.toVec3i(offset)); + blockStates[i] = level.getBlockState(pos); + } + } + + public void apply(Predicate predicate) { + for (int i = 0; i < blockStates.length; i++) { + states[i] = predicate.test(blockStates[i]); + } + isApplied = true; + } + + public boolean all() { + if (!isApplied) { + throw new IllegalStateException("No predicate applied yet"); + } + for (var s : states) { + if (!s) { + return false; + } + } + return true; + } + + public boolean all(Predicate predicate) { + for (var blockState : blockStates) { + if (!predicate.test(blockState)) { + return false; + } + } + return true; + } + + public boolean any() { + if (!isApplied) { + throw new IllegalStateException("No predicate applied yet"); + } + for (var s : states) { + if (!s) { + return true; + } + } + return false; + } + + public boolean any(Predicate predicate) { + for (var blockState : blockStates) { + if (predicate.test(blockState)) { + return true; + } + } + return false; + } + + public int count() { + int sum = 0; + for (var s : states) { + if (s) { + sum++; + } + } + return sum; + } + + public int count(Predicate predicate) { + int sum = 0; + for (var state : blockStates) { + if (predicate.test(state)) { + sum++; + } + } + return sum; + } + + public boolean has(Vector3i v) { + for (var offset : offsets) { + if (offset.equals(v)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + var s = new StringBuilder("SurroundingPattern:\n"); + for (int z = 0; z < 3; z++) { + s.append(" | "); + for (int y = 0; y < 3; y++) { + for (int x = 0; x < 3; x++) { + Vector3i v = new Vector3i(x, y, z); + if (has(v)) { + s.append('#').append(' '); + } else { + s.append(" "); + } + } + s.append("| "); + } + s.append("\n"); + } + return s.toString(); + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/annotation/VersionSensitive.java b/common/src/main/java/com/github/leawind/thirdperson/util/annotation/VersionSensitive.java new file mode 100644 index 00000000..cc376c3d --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/annotation/VersionSensitive.java @@ -0,0 +1,23 @@ +package com.github.leawind.thirdperson.util.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * 用于标记版本敏感内容 + * + *

    有些内容很可能需要随着Minecraft版本更新而更新。 + * + *

    对这类内容使用此注解,以便在将此模组移植到其他MC版本时检查相关内容。 + * + *

    通常应当对实现方法,而非接口方法使用此注解。 + */ +@SuppressWarnings("unused") +@Retention(RetentionPolicy.SOURCE) +public @interface VersionSensitive { + String value() default ""; + + String since() default ""; + + String until() default ""; +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/LMath.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/LMath.java new file mode 100644 index 00000000..a349fd5e --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/LMath.java @@ -0,0 +1,262 @@ +package com.github.leawind.thirdperson.util.math; + +import com.github.leawind.thirdperson.util.annotation.VersionSensitive; +import net.minecraft.core.Vec3i; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2d; +import org.joml.Vector3d; +import org.joml.Vector3f; +import org.joml.Vector3i; + +public interface LMath { + + /** + * 将方向转换成角度(弧度制) + * + * @param r 方向 + * @return [x=俯仰角, y=偏航角] 当 d 的模为 0 时,返回值将包含 NaN + */ + @Contract(pure = true) + static Vector3d directionFromRotationDegree(Vector2d r) { + return directionFromRotationDegree(r.x, r.y); + } + + /** + * 将方向转换成角度(弧度制) + * + * @param d 方向 + * @return [x=俯仰角, y=偏航角] 当 d 的模为 0 时,返回值将包含 NaN + */ + @Contract(pure = true) + static Vector2d rotationRadianFromDirection(Vector3d d) { + d.normalize(); + return new Vector2d(-Math.asin(d.y), Math.atan2(-d.x, d.z)); + } + + @Contract(pure = true) + static Vector3d directionFromRotationDegree(double x, double y) { + double h = Math.cos(-y * 0.017453292519943295 - Math.PI); + double i = Math.sin(-y * 0.017453292519943295 - Math.PI); + + double j = -Math.cos(-x * 0.017453292519943295); + double k = Math.sin(-x * 0.017453292519943295); + + return new Vector3d(i * j, k, h * j); + } + + /** + * 将一个向量相对原本方向旋转一定弧度 + * + * @param vec 原向量 + * @param dy 偏航角变化量(弧度制) + * @param dx 俯仰角变化量(弧度制) + */ + @Contract(pure = true) + static Vector3d rotateRadian(Vector3d vec, float dy, float dx) { + return directionFromRotationDegree(rotationRadianFromDirection(vec).add(new Vector2d(dx, dy))) + .mul(vec.length()); + } + + /** + * 将一个向量相对原本方向旋转一定弧度 + * + * @param vec 原向量 + * @param rotation 弧度变化量(弧度制) + */ + @Contract(pure = true) + static Vector3d rotateRadian(Vector3d vec, Vector2d rotation) { + return directionFromRotationDegree(rotationRadianFromDirection(vec).add(rotation)) + .mul(vec.length()); + } + + /** + * 将一个向量相对原本方向旋转一定角度 + * + * @param vec 原向量 + * @param dy 偏航角变化量(角度制) + * @param dx 俯仰角变化量(角度制) + */ + @Contract(pure = true) + static Vector3d rotateDegree(Vector3d vec, double dy, double dx) { + return directionFromRotationDegree(rotationDegreeFromDirection(vec).add(new Vector2d(dx, dy))) + .mul(vec.length()); + } + + /** + * 将方向转换成角度(角度制) + * + * @param d 方向 + * @return [x=俯仰角, y=偏航角] 当 d 的模为 0 时,返回值将包含 NaN + */ + @Contract(pure = true) + static Vector2d rotationDegreeFromDirection(Vector3d d) { + var nd = new Vector3d(d).normalize(); + return new Vector2d( + (-Math.toDegrees(Math.asin(nd.y))), Math.toDegrees(Math.atan2(-nd.x, nd.z))); + } + + /** + * 将一个向量相对原本方向旋转一定角度 + * + * @param vec 原向量 + * @param rotationAngle 角度变化量(角度制) + */ + @Contract(pure = true) + static Vector3d rotateDegree(Vector3d vec, Vector2d rotationAngle) { + return directionFromRotationDegree(rotationDegreeFromDirection(vec).add(rotationAngle)) + .mul(vec.length()); + } + + /** + * 将方向转换成角度(角度制) + * + * @param d 方向 + * @return [x=俯仰角, y=偏航角] 当 d 的模为 0 时,返回值将包含 NaN + */ + @Contract(pure = true) + static double rotationDegreeFromDirection(Vector2d d) { + return -Math.toDegrees(Math.atan2(d.x, d.y)); + } + + @Contract(pure = true) + static Vector2d directionFromRotationDegree(double yRot) { + double x = Math.sin(yRot * 0.017453292519943295 + Math.PI); + double z = -Math.cos(yRot * 0.017453292519943295 + Math.PI); + return new Vector2d(x, z); + } + + @Contract(pure = true) + static Vector3d toVector3d(Vec3 v) { + return new Vector3d(v.x, v.y, v.z); + } + + @VersionSensitive(until = "1.19.3") + @Contract(pure = true) + static Vector3d toVector3d(com.mojang.math.Vector3f v) { + return new Vector3d(v.x(), v.y(), v.z()); + } + + @Contract(pure = true) + static Vector3d toVector3d(Vector3f v) { + return new Vector3d(v.x, v.y, v.z); + } + + @Contract(pure = true) + static Vec3i toVec3i(Vec3 v) { + return new Vec3i((int) v.x, (int) v.y, (int) v.z); + } + + @Contract(pure = true) + static Vec3i toVec3i(Vector3i v) { + return new Vec3i(v.x, v.y, v.z); + } + + @Contract(pure = true) + static Vec3 toVec3(Vector3d v) { + return new Vec3(v.x, v.y, v.z); + } + + @Contract("_, _ -> param1") + static @NotNull Vector2d pow(Vector2d v, Vector2d p) { + v.x = Math.pow(v.x, p.x); + v.y = Math.pow(v.y, p.y); + return v; + } + + @Contract("_, _ -> param1") + static @NotNull Vector3d pow(Vector3d v, Vector3d p) { + v.x = Math.pow(v.x, p.x); + v.y = Math.pow(v.y, p.y); + v.z = Math.pow(v.z, p.z); + return v; + } + + @Contract(pure = true) + static int clamp(int d, int min, int max) { + return d < min ? min : Math.min(d, max); + } + + @Contract(pure = true) + static long clamp(long d, long min, long max) { + return d < min ? min : Math.min(d, max); + } + + @Contract(pure = true) + static float clamp(float d, float min, float max) { + return d < min ? min : Math.min(d, max); + } + + @Contract(pure = true) + static double clamp(double d, double min, double max) { + return d < min ? min : Math.min(d, max); + } + + @Contract(pure = true) + static void clamp(Vector2d v, double min, double max) { + v.x = clamp(v.x, min, max); + v.y = clamp(v.y, min, max); + } + + /** start = start + (end - start) * t */ + @Contract(pure = true) + static double lerp(double start, double end, double t) { + return start + t * (end - start); + } + + /** start = start + (end - start) * t */ + static void lerp(Vector2d start, Vector2d end, Vector2d t) { + start.x = lerp(start.x, end.x, t.x); + start.y = lerp(start.y, end.y, t.y); + } + + /** start = start + (end - start) * t */ + static void lerp(Vector3d start, Vector3d end, Vector3d t) { + start.x = lerp(start.x, end.x, t.x); + start.y = lerp(start.y, end.y, t.y); + start.z = lerp(start.z, end.z, t.z); + } + + @Contract(pure = true) + static double floorMod(double x, double y) { + return ((x % y) + y) % y; + } + + @Contract(pure = true) + static float floorMod(float x, float y) { + return ((x % y) + y) % y; + } + + @Contract(pure = true) + static int floorMod(int x, int y) { + return Math.floorMod(x, y); + } + + @Contract(pure = true) + static long floorMod(long x, long y) { + return Math.floorMod(x, y); + } + + /** 角度a是否在角度x与y的夹角中 */ + static boolean isWithinRadian(double x, double a, double b) { + double tp = Math.sin(b - a); + return Math.sin(b - x) * tp > 0 && Math.sin(x - a) * tp > 0; + } + + /** 两角度的夹角大小 */ + static double subtractRadian(double a, double b) { + double x = Math.abs(Math.IEEEremainder(a - b, 6.283185307179586)); + return x > 3.141592653589793 ? 6.283185307179586 - x : x; + } + + static boolean isWithinDegrees(double x, double a, double b) { + return isWithinRadian( + x * 0.017453292519943295, a * 0.017453292519943295, b * 0.017453292519943295); + } + + static double subtractDegrees(double a, double b) { + double x = Math.abs(Math.IEEEremainder(a - b, 360)); + return x > 180 ? 360 - x : x; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/Zone.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/Zone.java new file mode 100644 index 00000000..0e63bd18 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/Zone.java @@ -0,0 +1,284 @@ +package com.github.leawind.thirdperson.util.math; + +import org.jetbrains.annotations.NotNull; + +/** + * 区间 + * + *

    + *   min    center    max
    + *    |________|_______|
    + * 
    + */ +@SuppressWarnings("unused") +public class Zone { + public final double min; + public final double max; + + /** + * @throws IllegalArgumentException min > max + */ + public Zone(double min, double max) throws IllegalArgumentException { + if (min > max) { + throw new IllegalArgumentException( + "Minimum cannot be greater than maximum: " + min + " > " + max); + } + this.min = min; + this.max = max; + } + + /** 区间长度的一半 */ + public double radius() { + return (max - min) / 2; + } + + @Override + public String toString() { + return "Zone[" + min + ", " + max + "]"; + } + + /** + * 保持区间中点不变,放缩长度 + * + * @param scale 比例 + */ + public @NotNull Zone scale(double scale) { + return Zone.ofLength(center(), length() * scale); + } + + /** + * @return 区间中点 + */ + public double center() { + return (min + max) / 2; + } + + /** 区间长度 */ + public double length() { + return max - min; + } + + /** + * 求两个区间的相交区域 + * + *
    +   * 	this:   |--------|
    +   * 	that:         |---------|
    +   * 	union:        |--|
    +   * 
    + * + * @throws IllegalArgumentException 没有相交区域 + */ + public @NotNull Zone intersection(@NotNull Zone zone) throws IllegalArgumentException { + return new Zone(Math.max(min, zone.min), Math.min(max, zone.max)); + } + + /** + * 求两个区间的并集 + * + *
    +   * 	this:   |--------|
    +   * 	that:         |---------|
    +   * 	union:  |---------------|
    +   * 
    + * + * @throws IllegalArgumentException 没有相交区域 + */ + public @NotNull Zone union(@NotNull Zone zone) { + if (!hasIntersection(zone)) { + throw new IllegalArgumentException("Zones do not intersect"); + } + return new Zone(Math.min(min, zone.min), Math.max(max, zone.max)); + } + + /** + * 增加半径 + * + * @throws IllegalArgumentException d < 0 + */ + public @NotNull Zone expendRadius(double d) throws IllegalArgumentException { + if (d < 0) { + throw new IllegalArgumentException("Expected non-negative, got " + d); + } + return new Zone(min - d, max + d); + } + + /** + * 两端分别延长 + * + * @throws IllegalArgumentException a < 0 || b < 0 + */ + public @NotNull Zone expend(double a, double b) { + if (a < 0 || b < 0) { + throw new IllegalArgumentException("Expected non-negative, got (" + a + ", " + b + ")"); + } + return new Zone(min - a, max + b); + } + + /** + * 减小半径 + * + * @throws IllegalArgumentException d > radius + */ + public @NotNull Zone squeeze(double d) throws IllegalArgumentException { + if (d > radius()) { + throw new IllegalArgumentException("Squeeze too much!"); + } + return new Zone(min + d, max - d); + } + + /** + * 将半径减小 + * + *

    当减小量超过当前半径时,将视为将半径设为 0 + */ + public @NotNull Zone squeezeSafely(double d) { + double r = Math.min(d, radius()); + return Zone.ofAuto(min + r, max - r); + } + + /** 检查当前区间是否与给定的区间有交集。 */ + public boolean hasIntersection(@NotNull Zone zone) { + return min <= zone.max && zone.min <= max; + } + + /** + * 将当前区间沿着数轴平移指定的距离。 + * + * @param offset 偏移量 + */ + public @NotNull Zone move(double offset) { + return new Zone(min + offset, max + offset); + } + + /** 检查当前区间是否完全位于给定区间的左侧。 */ + public boolean lessThan(@NotNull Zone zone) { + return max <= zone.min; + } + + /** 检查当前区间是否完全位于给定区间的右侧。 */ + public boolean greaterThan(@NotNull Zone zone) { + return min >= zone.max; + } + + /** + * 在左侧创建一个紧邻当前区间的新区间 + * + *

    +   * this:          |----|
    +   * neighbor:  |---|
    +   * 
    + * + * @param length 新区间的长度 + * @throws IllegalArgumentException length < 0 + */ + public @NotNull Zone lessNeighbor(double length) throws IllegalArgumentException { + if (length < 0) { + throw new IllegalArgumentException("Length must be non-negative, not " + length); + } + return new Zone(min - length, min); + } + + /** + * 在右侧创建一个紧邻当前区间的新区间 + * + *
    +   * this:     |----|
    +   * neighbor:      |---|
    +   * 
    + * + * @param length 新区间的长度 + * @throws IllegalArgumentException length < 0 + */ + public @NotNull Zone greaterNeighbor(double length) throws IllegalArgumentException { + if (length < 0) { + throw new IllegalArgumentException("Length must be non-negative, not " + length); + } + return new Zone(max, max + length); + } + + /** 返回区间中与给定数值最近的值 */ + public double nearest(double value) { + return Math.min(Math.max(value, min), max); + } + + /** 返回区间中与给定数值最远的值 */ + public double furthest(double value) { + return value <= center() ? max : min; + } + + /** 返回与给定数值的距离 */ + public double distance(double value) { + return (value < min) ? (min - value) : (value > max) ? (value - max) : 0; + } + + /** 检查当前区间是否完全包含于给定区间中。 */ + public boolean in(@NotNull Zone zone) { + return zone.min <= min && max <= zone.max; + } + + /** 检查当前区间是否完全包含给定区间。 */ + public boolean contains(@NotNull Zone zone) { + return zone.min >= min && zone.max <= max; + } + + /** 检查当前区间是否包含给定数值。 */ + public boolean contains(double value) { + return min <= value && value <= max; + } + + /** + * 创建一个新的区间,具有相同的最小值和新的最大值。 + * + * @param max 新的最大值 + * @return 具有相同最小值和新最大值的新区间 + * @throws IllegalArgumentException min < max + */ + public @NotNull Zone withMax(double max) throws IllegalArgumentException { + return new Zone(min, max); + } + + /** + * 创建一个新的区间,具有新的最小值和相同的最大值。 + * + * @param min 新的最小值 + * @return 具有新最小值和相同最大值的新区间 + * @throws IllegalArgumentException min < max + */ + public @NotNull Zone withMin(double min) throws IllegalArgumentException { + return new Zone(min, max); + } + + /** + * @throws IllegalArgumentException min > max + */ + public static @NotNull Zone of(double min, double max) throws IllegalArgumentException { + return new Zone(min, max); + } + + /** + * 由两个端点创建区间,允许 a > b + * + * @param a 端点 + * @param b 另一个端点 + */ + public static @NotNull Zone ofAuto(double a, double b) { + return a < b ? new Zone(a, b) : new Zone(b, a); + } + + /** + * @throws IllegalArgumentException radius < 0 + */ + public static @NotNull Zone ofRadius(double center, double radius) + throws IllegalArgumentException { + return new Zone(center - radius, center + radius); + } + + /** + * @throws IllegalArgumentException length < 0 + */ + public static @NotNull Zone ofLength(double center, double length) + throws IllegalArgumentException { + return new Zone(center - length / 2, center + length / 2); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/decisionmap/DecisionFactor.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/decisionmap/DecisionFactor.java new file mode 100644 index 00000000..7974fd08 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/decisionmap/DecisionFactor.java @@ -0,0 +1,47 @@ +package com.github.leawind.thirdperson.util.math.decisionmap; + +import java.util.function.BooleanSupplier; + +public class DecisionFactor { + private final String name; + private final BooleanSupplier supplier; + + int index; + + private boolean value = false; + + /** + * @param name 因素的名称 + * @param supplier 用于计算因素的值的函数 + */ + public DecisionFactor(String name, BooleanSupplier supplier) { + this.name = name; + this.supplier = supplier; + } + + int getMask() { + return 1 << index; + } + + public String getName() { + return name; + } + + /** 重新计算因素的值 */ + public boolean update() { + return value = supplier.getAsBoolean(); + } + + public boolean get() { + return value; + } + + public int getInt() { + return value ? 1 : 0; + } + + @Override + public String toString() { + return String.format("DecisionFactor(%s)[%d]{%b}", name, index, value); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/decisionmap/DecisionMap.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/decisionmap/DecisionMap.java new file mode 100644 index 00000000..6c007407 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/decisionmap/DecisionMap.java @@ -0,0 +1,181 @@ +package com.github.leawind.thirdperson.util.math.decisionmap; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import org.jetbrains.annotations.Nullable; + +/** + * 决策表 + * + *

    事先将给定因素的组合与具体决策间的映射关系存储在表中,随时可以更新因素的值并根据它们做出新的决策 + * + *

    和直接使用条件判断语句进行判断相比,具有以下缺点: + * + *

      + *
    • 构造麻烦 + *
    • 需要时间阅读文档 + *
    • 最好为规则编写清晰的注释 + *
    + * + *

    优点: + * + *

      + *
    • 便于精细控制。你可以为某一种具体情况指定相应的规则,而几乎不会影响运行时的性能 + *
    + */ +public class DecisionMap { + public static final int MAX_FACTOR_COUNT = 32; + + /** index -> {@link DecisionFactor} */ + private final List factors; + + /** name -> {@link DecisionFactor} */ + private final Map factorMap = new HashMap<>(); + + /** flags -> strategy */ + private final Map> strategies; + + private final Supplier defaultOperation; + private int factorValues = 0; + + public DecisionMap( + List factorList, + Map> strategyMap, + Supplier defaultOperation) { + if (factorList.size() > MAX_FACTOR_COUNT) { + throw new IllegalArgumentException( + "Too many factors. Max is " + MAX_FACTOR_COUNT + ", got " + factorList.size()); + } + + factors = new ArrayList<>(factorList); + + int i = 0; + for (var factor : factors) { + factorMap.put(factor.getName(), factor); + factor.index = i; + i++; + } + + strategies = new HashMap<>(strategyMap); + + this.defaultOperation = defaultOperation; + } + + public int getFactorCount() { + return factors.size(); + } + + public int getFactorValues() { + return factorValues; + } + + private @Nullable DecisionFactor getFactor(String name) { + return factorMap.get(name); + } + + /** + * @throws IndexOutOfBoundsException if the index is out of range ({@code index < 0 || index >= + * size()}) + */ + private DecisionFactor getFactor(int index) throws IndexOutOfBoundsException { + return factors.get(index); + } + + /** + * @throws IllegalArgumentException if the given name does not exist + */ + public void update(String name) { + var factor = getFactor(name); + if (factor == null) { + throw new IllegalArgumentException("No such factor: " + name); + } + factor.update(); + factorValues &= ~(1 << factor.index); + factorValues |= factor.getInt() << factor.index; + } + + /** + * @throws IndexOutOfBoundsException if the index is out of range ({@code index < 0 || index >= + * size()}) + */ + public void update(int index) throws IndexOutOfBoundsException { + var factor = getFactor(index); + factor.update(); + factorValues &= ~(1 << index); + factorValues |= factor.getInt() << index; + } + + /** 更新所有因素 */ + public DecisionMap updateAll() { + for (var i = 0; i < factors.size(); i++) { + update(i); + } + return this; + } + + /** + * 根据当前因素值执行对应的操作 + * + *

    如果策略表中没有当前因素值的组合,则执行默认操作 + */ + public T make() { + return strategies.getOrDefault(factorValues, defaultOperation).get(); + } + + public String toDescription() { + var strategySet = new HashSet<>(strategies.values()); + StringBuilder s = new StringBuilder("DecisionMap\n"); + s.append( + String.format("\tTotally %d factors, %d strategies\n", factors.size(), strategySet.size())); + s.append( + String.format( + "\tSpecified cases: %d/%d\n", strategies.size(), (int) Math.pow(2, factors.size()))); + for (int i = factors.size() - 1; i >= 0; i--) { + s.append(String.format("\t\t%2d. %s\n", i, factors.get(i).getName())); + } + return s.toString(); + } + + public String toDescriptionWithCases(boolean allCases) { + var strategySet = new HashSet<>(strategies.values()); + + StringBuilder s = new StringBuilder("DecisionMap\n"); + s.append( + String.format("\tTotally %d factors, %d strategies\n", factors.size(), strategySet.size())); + s.append( + String.format( + "\tSpecified cases: %d/%d\n", strategies.size(), (int) Math.pow(2, factors.size()))); + for (int i = factors.size() - 1; i >= 0; i--) { + s.append(String.format("\t\t%2d. %s\n", i, factors.get(i).getName())); + } + int flagsMax = (1 << factors.size()) - 1; + for (int flags = 0; flags <= flagsMax; flags++) { + if (allCases || strategies.containsKey(flags)) { + s.append("\t"); + var flagsStr = Integer.toBinaryString(flags); + s.append("0".repeat(factors.size() - flagsStr.length())).append(flagsStr); + var strategy = strategies.get(flags); + if (strategy != null) { + s.append(String.format(" %s\n", strategy.getClass().getSimpleName())); + } else { + s.append(" (Default)\n"); + } + } + } + + return s.toString(); + } + + /** + * 创建构造器 + * + * @param 决策结果类型 + */ + public static DecisionMapBuilder builder() { + return new DecisionMapBuilder<>(); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/decisionmap/DecisionMapBuilder.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/decisionmap/DecisionMapBuilder.java new file mode 100644 index 00000000..9a7d025f --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/decisionmap/DecisionMapBuilder.java @@ -0,0 +1,243 @@ +package com.github.leawind.thirdperson.util.math.decisionmap; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; +import org.jetbrains.annotations.Nullable; + +public class DecisionMapBuilder { + private final String REVERSE_MARKER = "!~"; + private final Supplier EMPTY_OPERATION = () -> null; + + private final List<@Nullable DecisionFactor> factors = + new ArrayList<>(DecisionMap.MAX_FACTOR_COUNT); + private final List<@Nullable Rule> rules = new LinkedList<>(); + private Supplier defaultOperation = EMPTY_OPERATION; + + private int lastFactorIndex = -1; + + DecisionMapBuilder() {} + + /** 清空所有规则并重置默认值 */ + public DecisionMapBuilder clearRules() { + rules.clear(); + defaultOperation = EMPTY_OPERATION; + return this; + } + + /** + * 根据名称获取因素 + * + * @param name 名称 + * @throws IllegalArgumentException 不存在给定名称的因素 + */ + public DecisionFactor factor(String name) { + for (var factor : factors) { + if (factor != null && factor.getName().equals(name)) { + return factor; + } + } + throw new IllegalArgumentException("No such factor: " + name); + } + + /** + * 不指定索引,则索引将是上一个定义的因素的索引+1 + * + * @param name 名称 + * @param supplier 计算因素的函数 + */ + public DecisionMapBuilder factor(String name, BooleanSupplier supplier) { + return factor(lastFactorIndex + 1, name, supplier); + } + + /** + * 定义一个因素 + * + * @param index 因素的索引 + * @param name 名称,不能以 {@link DecisionMapBuilder#REVERSE_MARKER} 中的字符开头 + * @param supplier 计算因素的函数 + * @throws IndexOutOfBoundsException 索引值超出范围 + * @throws IllegalArgumentException 索引或名称重复,或名称不合法 + * @throws IllegalStateException 因素的数量已达到上限 + */ + public DecisionMapBuilder factor(int index, String name, BooleanSupplier supplier) { + if (index < 0 || index >= DecisionMap.MAX_FACTOR_COUNT) { + throw new IndexOutOfBoundsException( + String.format("Index %d out of bounds [%d, %d)", index, 0, DecisionMap.MAX_FACTOR_COUNT)); + } + if (REVERSE_MARKER.indexOf(name.charAt(0)) != -1) { + throw new IllegalArgumentException(String.format("Invalid factor name: %s", name)); + } + for (var factor : factors) { + if (factor != null) { + if (factor.index == index) { + throw new IllegalArgumentException("Duplicated factor index: " + index); + } + if (factor.getName().equals(name)) { + throw new IllegalArgumentException("Duplicated factor name: " + name); + } + } + } + while (factors.size() <= index) { + factors.add(null); + } + var factor = new DecisionFactor(name, supplier); + factor.index = index; + factors.set(index, factor); + lastFactorIndex = index; + return this; + } + + public DecisionMapBuilder clearFactors() { + factors.clear(); + return this; + } + + /** + * 定义默认操作 + * + * @param operation null 表示什么也不做 + */ + public DecisionMapBuilder whenDefault(@Nullable Supplier operation) { + defaultOperation = Objects.requireNonNullElse(operation, EMPTY_OPERATION); + return this; + } + + /** + * 添加规则:当特定索引的因素为 true 时 + * + * @see DecisionMapBuilder#when(String, boolean, Supplier) + */ + public DecisionMapBuilder when(String name, Supplier operation) { + return when(name, true, operation); + } + + /** + * 添加规则:当特定索引的因素等于指定值时 + * + * @param name 因素的名称 + * @param value 当索引等于何值时 + * @param operation 操作 + * @see DecisionMapBuilder#when(int, int, Supplier) + */ + public DecisionMapBuilder when(String name, boolean value, Supplier operation) { + var index = factor(name).index; + return when(1 << index, (value ? 1 : 0) << index, operation); + } + + /** 当给定的名称列表对应的因素全都为给定值时 */ + public DecisionMapBuilder when(List names, boolean value, Supplier operation) { + int mask = 0; + for (var name : names) { + mask |= factor(name).getMask(); + } + int flags = value ? mask & ~0 : 0; + when(mask, flags, operation); + return this; + } + + public DecisionMapBuilder when(List expressions, Supplier operation) { + int mask = 0; + int flags = 0; + for (var expr : expressions) { + String name = expr; + boolean value = true; + if (REVERSE_MARKER.indexOf(expr.charAt(0)) != -1) { + name = expr.substring(1); + value = false; + } + var factorMask = factor(name).getMask(); + mask |= factorMask; + flags |= value ? factorMask : 0; + } + when(mask, flags, operation); + return this; + } + + /** + * 添加规则:当特定索引的因素等于指定值时 + * + * @param index 因素的索引 + * @param value 当索引等于何值时 + * @param operation 操作 + */ + public DecisionMapBuilder when(int index, boolean value, Supplier operation) { + return when(1 << index, (value ? 1 : 0) << index, operation); + } + + /** + * 当所有因素都等于给定的值时 + * + * @param flags 所有因素的值 + * @param operation 操作 + */ + public DecisionMapBuilder whenAll(int flags, Supplier operation) { + return when(~0, flags, operation); + } + + /** + * 添加规则:当指定的因素为指定的值的时候,执行特定的操作 + * + * @param mask 掩码,表示在这个规则中要考虑哪些因素 + * @param flags 因素的值,当被考虑的这些因素恰好为指定的这些值的时候,执行 operation。 + *

    注意:可能被之后定义的规则覆盖。 + * @param operation 当满足上述因素时执行的操作 + */ + public DecisionMapBuilder when(int mask, int flags, Supplier operation) { + rules.add(new Rule<>(mask, flags, operation)); + return this; + } + + /** + * 构建决策表 + * + *

    对于每条规则,计算该规则适用的所有情况,将这些情况下的因素组合与相应的操作加入到决策表中 + * + * @throws IllegalStateException 因素列表中存在null。因素的索引必须从0开始,必须连续 + */ + public DecisionMap build() { + for (int i = 0; i < factors.size(); i++) { + if (factors.get(i) == null) { + throw new IllegalStateException(String.format("Factor[%d] has not been specified", i)); + } + } + + var strategyMap = new HashMap>(); + // 有效位的掩码 + // 例如:若共有3个因素,则 filter = 0b111 + int filter = (int) ((1L << factors.size()) - 1); + + for (var rule : rules) { + if (rule != null) { + rule.filter(filter); + for (int flags = 0; flags <= filter; flags++) { + if ((rule.mask & flags) == rule.flags) { + strategyMap.put(flags, rule.operation); + } + } + } + } + return new DecisionMap<>(factors, strategyMap, defaultOperation); + } + + private static class Rule { + final Supplier operation; + int mask; + int flags; + + Rule(int mask, int flags, Supplier operation) { + this.mask = mask; + this.flags = flags; + this.operation = operation; + } + + void filter(int filter) { + mask &= filter; + flags &= mask; + } + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/monolist/DeferedMonoList.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/monolist/DeferedMonoList.java new file mode 100644 index 00000000..0a287dfd --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/monolist/DeferedMonoList.java @@ -0,0 +1,106 @@ +package com.github.leawind.thirdperson.util.math.monolist; + +import com.github.leawind.thirdperson.util.math.LMath; +import java.util.function.Function; +import org.jetbrains.annotations.NotNull; + +/** + * 延迟计算的单调列表 + * + *

    列表项是下标的单调函数 + * + *

    数列中每一项的值只有在被访问时才计算 + */ +@SuppressWarnings("unused") +public class DeferedMonoList implements MonoList { + private final int length; + private final @NotNull Function getter; + private final int sgn; + + /** + * @param length 列表长度 + * @param getter 值与下标的对应关系 + */ + protected DeferedMonoList(int length, @NotNull Function getter) { + this.length = length; + this.getter = getter; + this.sgn = getter.apply(1) > getter.apply(0) ? 1 : -1; + } + + @Override + public double get(int i) { + return getter.apply(i); + } + + @Override + public double offset(double value, int offset) { + int i = iadsorption(value) + offset; + i = LMath.clamp(i, 0, length() - 1); + return get(i); + } + + @Override + public int iadsorption(double value) { + int iLeft = 0; + int iRight = length() - 1; + int iCenter = length() / 2; + while (true) { + double vi = get(iCenter); + if (vi < value) { + iLeft = iCenter; + } else if (vi > value) { + iRight = iCenter; + } else { + return iCenter; + } + if (iRight - iLeft == 1) { + break; + } + iCenter = (iLeft + iRight) / 2; + } + double min = value - get(iLeft); + double max = get(iRight) - value; + if (min <= max) { + return iLeft; + } else { + return iRight; + } + } + + @Override + public double adsorption(double value) { + return get(iadsorption(value)); + } + + @Override + public double getNext(double value) { + return offset(value, 1); + } + + @Override + public double getLast(double value) { + return offset(value, -1); + } + + @Override + public int sgn() { + return sgn; + } + + @Override + public int length() { + return length; + } + + public static @NotNull DeferedMonoList exp(int length) { + return new DeferedMonoList(length, Math::exp); + } + + public static @NotNull DeferedMonoList squared(int length) { + return new DeferedMonoList(length, i -> (double) (i * i)); + } + + public static @NotNull DeferedMonoList of(int length, @NotNull Function getter) { + return new DeferedMonoList(length, getter); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/monolist/MonoList.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/monolist/MonoList.java new file mode 100644 index 00000000..23547254 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/monolist/MonoList.java @@ -0,0 +1,93 @@ +package com.github.leawind.thirdperson.util.math.monolist; + +/** + * 单调列表 + * + *

    列表中的数据是单调递增或单调递减的 + */ +@SuppressWarnings("unused") +public interface MonoList { + /** 获取下标对应的值 */ + double get(int i); + + /** + * 计算当前值对应的下标,将下标偏移后获取其在列表中对应的值 + * + *

    如果偏移后的下标超出范围,则取边缘的值(第一个或最后一个值) + * + *

    例: + * + *

    {@code
    +   * value ≈ B
    +   * offset = 2
    +   *
    +   * 下标: | 0 | 1 | 2 | 3 | 4 |
    +   * 数值: | A | B | C | D | E |
    +   *             ↑       ↑
    +   *           value     |
    +   *                     |
    +   *            index(value)+offset
    +   * }
    + * + * @param value 值 + * @param offset 偏移量 + */ + double offset(double value, int offset); + + /** + * 取最接近的一个值的下标 + * + *

    例: + * + *

    {@code
    +   * value ≈ 2.4
    +   *             result
    +   *               ↓
    +   * 下标: | 0   | 1   | 2   | 3   | 4   |
    +   * 数值: | 1.0 | 2.0 | 3.0 | 4.0 | 5.0 |
    +   *                  ↑
    +   *              value=2.4
    +   * }
    + */ + int iadsorption(double value); + + /** + * 找最近的一个值 + * + *

    例: + * + *

    {@code
    +   * value ≈ 2.4
    +   * 下标: | 0   | 1   | 2   | 3   | 4   |
    +   * 数值: | 1.0 | 2.0 | 3.0 | 4.0 | 5.0 |
    +   *               ↑  ↑
    +   *               | value=2.4
    +   *             result
    +   * }
    + */ + double adsorption(double value); + + /** + * 获取指定值的下一个值 + * + *

    如果该值已经位于最后一个区间,那么直接返回最后一个值 + */ + double getNext(double value); + + /** + * 获取指定值的上一个值 + * + *

    如果该值已经位于第一个区间,那么直接返回第一个值 + */ + double getLast(double value); + + /** + * @return 如果单调递增则为1,单调递减则为-1 + */ + int sgn(); + + /** + * @return 列表长度 + */ + int length(); +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/monolist/StaticMonoList.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/monolist/StaticMonoList.java new file mode 100644 index 00000000..c6ab6761 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/monolist/StaticMonoList.java @@ -0,0 +1,139 @@ +package com.github.leawind.thirdperson.util.math.monolist; + +import com.github.leawind.thirdperson.util.math.LMath; +import java.util.function.Function; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * 静态单调列表 + * + *

    列表中每一项的值会在列表被实例化时创建,不可更改 + */ +@SuppressWarnings("unused") +public class StaticMonoList implements MonoList { + private final int sgn; + private final double[] list; + + /** + * 根据列表直接创建 + * + * @param list 列表 + */ + public StaticMonoList(double @NotNull [] list) { + this.list = list; + sgn = (int) Math.signum(list[1] - list[0]); + if (!isMono()) { + throw new IllegalArgumentException("Invalid list"); + } + } + + private boolean isMono() { + double lastValue = list[0]; + for (double value : list) { + if (value != lastValue && Math.signum(value - lastValue) != sgn) { + return false; + } + } + return true; + } + + @Override + public double get(int i) { + return list[i]; + } + + @Override + public double offset(double value, int offset) { + int i = iadsorption(value) + offset * sgn(); + i = LMath.clamp(i, 0, length() - 1); + return list[i]; + } + + @Override + public int iadsorption(double value) { + int ileft = 0; + int iright = length() - 1; + int icenter = length() / 2; + + while (true) { + if (list[icenter] < value) { + ileft = icenter; + } else if (list[icenter] > value) { + iright = icenter; + } else { + return icenter; + } + if (iright - ileft == 1) { + break; + } + icenter = (ileft + iright) / 2; + } + if (value - list[ileft] <= list[iright] - value) { + return ileft; + } else { + return iright; + } + } + + @Override + public double adsorption(double value) { + return list[iadsorption(value)]; + } + + @Override + public double getNext(double value) { + return offset(value, 1); + } + + @Override + public double getLast(double value) { + return offset(value, -1); + } + + @Override + public int sgn() { + return sgn; + } + + @Override + public int length() { + return list.length; + } + + public static @NotNull StaticMonoList linear(int length) { + return of(length, d -> (double) d); + } + + public static @NotNull StaticMonoList of(int length, @NotNull Function getter) { + double[] list = new double[length]; + for (int i = 0; i < length; i++) { + list[i] = getter.apply(i); + } + return of(list); + } + + @Contract("_ -> new") + public static @NotNull StaticMonoList of(double[] list) { + return new StaticMonoList(list); + } + + public static @NotNull StaticMonoList exp(int length) { + return of(length, Math::exp); + } + + public static @NotNull StaticMonoList squared(int length) { + return of(length, i -> (double) (i * i)); + } + + public static @NotNull StaticMonoList of( + int length, + double min, + double max, + @NotNull Function f, + @NotNull Function fInv) { + double xmin = fInv.apply(min); + double xrange = fInv.apply(max) - xmin; + return of(length, i -> f.apply(i * xrange / length + xmin)); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpRotSmoothDouble.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpRotSmoothDouble.java new file mode 100644 index 00000000..4772bc81 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpRotSmoothDouble.java @@ -0,0 +1,77 @@ +package com.github.leawind.thirdperson.util.math.smoothvalue; + +import com.github.leawind.thirdperson.util.math.LMath; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public class ExpRotSmoothDouble extends ExpSmoothDouble { + private double cycle; + + /** + * @param cycle 周期 + */ + public ExpRotSmoothDouble(double cycle) { + super(); + setCycle(cycle); + } + + public double getCycle() { + return cycle; + } + + public void setCycle(double cycle) { + this.cycle = cycle; + } + + @Override + public void setTarget(double d) { + super.setTarget(LMath.floorMod(d, cycle)); + } + + @Override + public @NotNull Double get(double t) { + lastValue = LMath.floorMod(lastValue, cycle); + value = LMath.floorMod(value, cycle); + double delta = LMath.floorMod(value - lastValue, cycle); + if (delta > cycle / 2) { + delta -= cycle; + } + value = lastValue + delta; + return LMath.lerp(lastValue, value, t); + } + + @Override + protected void updateWithOutSavingLastValue(double period) { + value = LMath.floorMod(value, cycle); + target = LMath.floorMod(target, cycle); + double delta = LMath.floorMod(target - value, cycle); + if (delta > cycle / 2) { + delta -= cycle; + } + target = value + delta; + value = LMath.lerp(value, target, 1 - Math.pow(smoothFactor, smoothFactorWeight * period)); + } + + @Override + public void set(@NotNull Double d) { + d = LMath.floorMod(d, cycle); + super.set(d); + } + + @Override + public void setHalflife(double halflife) { + super.setHalflife(halflife); + } + + @Override + public void setValue(double d) { + d = LMath.floorMod(d, cycle); + super.setValue(d); + } + + public static @NotNull ExpRotSmoothDouble createWithHalflife(double cycle, double halflife) { + var v = new ExpRotSmoothDouble(cycle); + v.setHalflife(halflife); + return v; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothDouble.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothDouble.java new file mode 100644 index 00000000..37ef67ca --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothDouble.java @@ -0,0 +1,89 @@ +package com.github.leawind.thirdperson.util.math.smoothvalue; + +import com.github.leawind.thirdperson.util.math.LMath; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public class ExpSmoothDouble extends ExpSmoothValue { + public ExpSmoothDouble() { + super(0D, 1D, 0D, 0D, 0D); + } + + public void setTarget(double target) { + this.target = target; + } + + @Override + public void setTarget(@NotNull Double target) { + this.target = target; + } + + @Override + public @NotNull Double get(double t) { + return LMath.lerp(lastValue, value, t); + } + + @Override + protected void saveLastValue() { + lastValue = value; + } + + @Override + protected void updateWithOutSavingLastValue(double period) { + value = LMath.lerp(value, target, 1 - Math.pow(smoothFactor, smoothFactorWeight * period)); + } + + @Override + public void setValue(@NotNull Double d) { + value = d; + } + + @Override + public void set(@NotNull Double d) { + value = target = d; + } + + @Override + public void setSmoothFactor(@NotNull Double smoothFactor) { + this.smoothFactor = smoothFactor; + } + + @Override + public void setSmoothFactor(double smoothFactor) { + this.smoothFactor = smoothFactor; + } + + @Override + public void setMT(@NotNull Double multiplier, @NotNull Double time) { + if (multiplier < 0 || multiplier > 1) { + throw new IllegalArgumentException("Multiplier should in [0,1]: " + multiplier); + } else if (time < 0) { + throw new IllegalArgumentException("Invalid time, non-negative required, but got " + time); + } + setSmoothFactor(time == 0 ? 0 : Math.pow(multiplier, 1 / time)); + } + + @Override + public void setHalflife(@NotNull Double halflife) { + setMT(0.5, halflife); + } + + @Override + public void setHalflife(double halflife) { + setMT(0.5, halflife); + } + + public void setSmoothFactorWeight(double weight) { + this.smoothFactorWeight = weight; + } + + public void setValue(double d) { + value = d; + } + + public static @NotNull ExpSmoothDouble createWithHalflife(double halflife) { + var v = new ExpSmoothDouble(); + v.setHalflife(halflife); + return v; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothRotation.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothRotation.java new file mode 100644 index 00000000..b54f1ff6 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothRotation.java @@ -0,0 +1,113 @@ +package com.github.leawind.thirdperson.util.math.smoothvalue; + +import com.github.leawind.thirdperson.util.math.LMath; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2d; + +/** + * 平滑旋转 + * + *

    实体朝向通常用二维向量来描述,但要实现其朝向的平滑变化, 不能直接使用 {@link ExpSmoothVector2d} + * + *

    因为其存在一些约束条件和特殊性质 + * + *

    + *

  • y:偏航角,范围:[0, 360] + *
  • x:俯仰角,范围:[-90, 90] + */ +@SuppressWarnings("unused") +public class ExpSmoothRotation { + private final ExpRotSmoothDouble y; + private final ExpSmoothDouble x; + + private ExpSmoothRotation() { + y = new ExpRotSmoothDouble(360); + x = new ExpSmoothDouble(); + } + + public void setHalflife(double halflife) { + y.setHalflife(halflife); + x.setHalflife(halflife); + } + + public void setMT(double multiplier, double time) { + y.setMT(multiplier, time); + x.setMT(multiplier, time); + } + + public void setSmoothFactor(double smoothFactor) { + y.setSmoothFactor(smoothFactor); + x.setSmoothFactor(smoothFactor); + } + + public void setTarget(@NotNull Vector2d rot) { + y.setTarget(rot.y); + x.setTarget(LMath.clamp(rot.x, -90, 90)); + } + + /** + * 记录旧的平滑值,然后更新平滑值 + * + *

    对于 {@link ExpSmoothValue},理论上任何时候都可以更新, + * + *

    但考虑到时间的精度有限,更新间隔不应过小。 + * + * @param period 经过的时间(s) + */ + public void update(double period) { + y.update(period); + x.update(period); + } + + /** + * 获取当前的平滑值 + * + *

    如果使用 {@link ISmoothValue#update(double)} 进行更新的频率小于渲染频率, + * + *

    则不建议在渲染中直接采用此方法获取平滑值,因为这可能造成不平滑的效果。 + * + *

    应当使用 {@link ISmoothValue#get(double)} + */ + public @NotNull Vector2d get() { + return new Vector2d(x.get(), y.get()); + } + + /** + * 旧值与当前平滑值的线性插值 + * + *

    每次更新时,都会记录下当时的平滑值作为旧值,然后再计算新的平滑值。 + * + *

    当更新频率小于渲染频率时,此方法十分有效。 + * + * @param t 自上次更新以来经过的时间占更新间隔的比例,用于线性插值。 + */ + public @NotNull Vector2d get(double t) { + return new Vector2d(x.get(t), y.get(t)); + } + + public void set(@NotNull Vector2d v) { + y.set(v.y); + x.set(LMath.clamp(v.x, -90, 90)); + } + + public void setSmoothFactorWeight(double weight) { + y.setSmoothFactorWeight(weight); + x.setSmoothFactorWeight(weight); + } + + public void setValue(@NotNull Vector2d v) { + y.setValue(v.y); + x.setValue(LMath.clamp(v.x, -90, 90)); + } + + /** 获取上次更新前的平滑值(旧值) */ + public @NotNull Vector2d getLast() { + return new Vector2d(x.getLast(), y.getLast()); + } + + public static @NotNull ExpSmoothRotation createWithHalflife(double halflife) { + var v = new ExpSmoothRotation(); + v.setHalflife(halflife); + return v; + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothValue.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothValue.java new file mode 100644 index 00000000..d3b81d2f --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothValue.java @@ -0,0 +1,100 @@ +package com.github.leawind.thirdperson.util.math.smoothvalue; + +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public abstract class ExpSmoothValue implements ISmoothValue { + /** 平滑系数,越大越平滑 */ + public @NotNull T smoothFactor; + + /** 平滑系数乘数,默认应为1 */ + public @NotNull T smoothFactorWeight; // factor ^ weight + + /** 目标值 */ + public @NotNull T target; + + /** 当前平滑的值 */ + protected @NotNull T value; + + /** 上次更新时的目标值 */ + protected @NotNull T lastValue; + + protected ExpSmoothValue( + @NotNull T smoothFactor, + @NotNull T smoothFactorWeight, + @NotNull T value, + @NotNull T lastValue, + @NotNull T target) { + this.smoothFactor = smoothFactor; + this.smoothFactorWeight = smoothFactorWeight; + + this.value = value; + this.lastValue = lastValue; + this.target = target; + } + + public T getRawValue() { + return value; + } + + public T getRawTarget() { + return target; + } + + public T getRawLastValue() { + return lastValue; + } + + public final void update(double period) { + saveLastValue(); + updateWithOutSavingLastValue(period); + } + + @Override + public final @NotNull T get() { + return value; + } + + @Override + public abstract @NotNull T get(double t); + + @Override + public final @NotNull T getLast() { + return lastValue; + } + + /** + * 将当前的平滑值 value 存储在 lastValue 中 + * + *

    在 update 方法中写入新值前被调用 + */ + protected abstract void saveLastValue(); + + protected abstract void updateWithOutSavingLastValue(double period); + + public abstract void setValue(@NotNull T value); + + /** + * 同时设置目标值和当前平滑值 + * + *

    不改变旧值 + */ + public abstract void set(@NotNull T value); + + public abstract void setSmoothFactor(@NotNull T smoothFactor); + + abstract void setSmoothFactor(double smoothFactor); + + /** + * 根据以下规则设置平滑系数: + * + *

    每隔 time 秒,value 变为原来的 multiplier 倍。 + */ + abstract void setMT(@NotNull T multiplier, @NotNull T time); + + /** 根据半衰期设置平滑系数 */ + abstract void setHalflife(@NotNull T halflife); + + /** 根据半衰期设置平滑系数 */ + abstract void setHalflife(double halflife); +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothVector2d.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothVector2d.java new file mode 100644 index 00000000..120e1847 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothVector2d.java @@ -0,0 +1,97 @@ +package com.github.leawind.thirdperson.util.math.smoothvalue; + +import com.github.leawind.thirdperson.util.math.LMath; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2d; + +@SuppressWarnings("unused") +public class ExpSmoothVector2d extends ExpSmoothValue { + public ExpSmoothVector2d() { + super(new Vector2d(0), new Vector2d(1), new Vector2d(0), new Vector2d(0), new Vector2d(0)); + } + + public void setTarget(double x, double y) { + this.target.set(x, y); + } + + @Override + public void setTarget(@NotNull Vector2d target) { + this.target.set(target); + } + + @Override + public @NotNull Vector2d get(double t) { + return new Vector2d(lastValue).lerp(value, t); + } + + @Override + protected void saveLastValue() { + lastValue.set(value); + } + + @Override + protected void updateWithOutSavingLastValue(double period) { + var t = + LMath.pow(new Vector2d(smoothFactor), new Vector2d(smoothFactorWeight).mul(period)) + .negate() + .add(1, 1); + LMath.lerp(value, target, t); + } + + @Override + public void setValue(@NotNull Vector2d v) { + value.set(v); + } + + @Override + public void set(@NotNull Vector2d v) { + value.set(v); + target.set(v); + } + + @Override + public void setSmoothFactor(@NotNull Vector2d s) { + this.smoothFactor.set(s); + } + + @Override + public void setSmoothFactor(double smoothFactor) { + setSmoothFactor(smoothFactor, smoothFactor); + } + + @Override + public void setMT(@NotNull Vector2d multiplier, @NotNull Vector2d time) { + if (multiplier.x < 0 || multiplier.x > 1) { + throw new IllegalArgumentException("Multiplier.x should in [0,1]: " + multiplier.x); + } else if (multiplier.y < 0 || multiplier.y > 1) { + throw new IllegalArgumentException("Multiplier.y should in [0,1]: " + multiplier.y); + } else if (time.x < 0 || time.y < 0) { + throw new IllegalArgumentException("Invalid time, non-negative required, but got " + time); + } + this.smoothFactor.set( + time.x == 0 ? 0 : Math.pow(multiplier.x, 1 / time.x), + time.y == 0 ? 0 : Math.pow(multiplier.y, 1 / time.y)); + } + + @Override + public void setHalflife(@NotNull Vector2d halflife) { + setMT(new Vector2d(0.5), halflife); + } + + @Override + public void setHalflife(double halflife) { + setMT(new Vector2d(0.5), new Vector2d(halflife)); + } + + public void setSmoothFactor(double x, double y) { + this.smoothFactor.set(x, y); + } + + public void setSmoothFactorWeight(double x, double y) { + this.smoothFactorWeight.set(x, y); + } + + public void setValue(double x, double y) { + this.value.set(x, y); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothVector3d.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothVector3d.java new file mode 100644 index 00000000..d04951b4 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ExpSmoothVector3d.java @@ -0,0 +1,100 @@ +package com.github.leawind.thirdperson.util.math.smoothvalue; + +import com.github.leawind.thirdperson.util.math.LMath; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector3d; + +@SuppressWarnings("unused") +public class ExpSmoothVector3d extends ExpSmoothValue { + public ExpSmoothVector3d() { + super(new Vector3d(0), new Vector3d(1), new Vector3d(0), new Vector3d(0), new Vector3d(0)); + } + + public void setTarget(double x, double y, double z) { + this.target.set(x, y, z); + } + + public void setValue(double x, double y, double z) { + this.value.set(x, y, z); + } + + @Override + public void setTarget(@NotNull Vector3d target) { + this.target.set(target); + } + + @Override + public @NotNull Vector3d get(double t) { + return new Vector3d(lastValue).lerp(value, t); + } + + @Override + protected void saveLastValue() { + lastValue.set(value); + } + + @Override + protected void updateWithOutSavingLastValue(double period) { + var t = + LMath.pow(new Vector3d(smoothFactor), new Vector3d(smoothFactorWeight).mul(period)) + .negate() + .add(1, 1, 1); + LMath.lerp(value, target, t); + } + + @Override + public void setValue(@NotNull Vector3d v) { + value.set(v); + } + + @Override + public void set(@NotNull Vector3d v) { + value.set(v); + target.set(v); + } + + @Override + public void setSmoothFactor(@NotNull Vector3d smoothFactor) { + this.smoothFactor.set(smoothFactor); + } + + @Override + public void setSmoothFactor(double d) { + setSmoothFactor(d, d, d); + } + + @Override + public void setMT(@NotNull Vector3d multiplier, @NotNull Vector3d time) { + if (multiplier.x < 0 || multiplier.x > 1) { + throw new IllegalArgumentException("Multiplier.x should in [0,1]: " + multiplier.x); + } else if (multiplier.y < 0 || multiplier.y > 1) { + throw new IllegalArgumentException("Multiplier.y should in [0,1]: " + multiplier.y); + } else if (multiplier.z < 0 || multiplier.z > 1) { + throw new IllegalArgumentException("Multiplier.z should in [0,1]: " + multiplier.z); + } else if (time.x < 0 || time.y < 0 || time.z < 0) { + throw new IllegalArgumentException("Invalid time, non-negative required, but got " + time); + } + this.smoothFactor.set( + time.x == 0 ? 0 : Math.pow(multiplier.x, 1 / time.x), + time.y == 0 ? 0 : Math.pow(multiplier.y, 1 / time.y), + time.z == 0 ? 0 : Math.pow(multiplier.z, 1 / time.z)); + } + + @Override + public void setHalflife(@NotNull Vector3d halflife) { + setMT(new Vector3d(0.5), halflife); + } + + @Override + public void setHalflife(double halflife) { + setMT(new Vector3d(0.5), new Vector3d(halflife)); + } + + private void setSmoothFactor(double x, double y, double z) { + this.smoothFactor.set(x, y, z); + } + + public void setSmoothFactorWeight(double x, double y, double z) { + this.smoothFactorWeight.set(x, y, z); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ISmoothValue.java b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ISmoothValue.java new file mode 100644 index 00000000..b57995a1 --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/math/smoothvalue/ISmoothValue.java @@ -0,0 +1,52 @@ +package com.github.leawind.thirdperson.util.math.smoothvalue; + +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public interface ISmoothValue { + /** + * 设置目标值 + * + *

    任何时候都可能设置此值 + */ + void setTarget(@NotNull T endValue); + + /** + * 记录旧的平滑值,然后更新平滑值 + * + *

    对于 {@link ExpSmoothValue},理论上任何时候都可以更新, + * + *

    但考虑到时间的精度有限,更新间隔不应过小。 + * + * @param period 经过的时间(s) + */ + void update(double period); + + /** + * 获取当前的平滑值 + * + *

    如果使用 {@link ISmoothValue#update(double)} 进行更新的频率小于渲染频率, + * + *

    则不建议在渲染中直接采用此方法获取平滑值,因为这可能造成不平滑的效果。 + * + *

    应当使用 {@link ISmoothValue#get(double)} + */ + @NotNull + T get(); + + /** + * 旧值与当前平滑值的线性插值 + * + *

    每次更新时,都会记录下当时的平滑值作为旧值,然后再计算新的平滑值。 + * + *

    当更新频率小于渲染频率时,此方法十分有效。 + * + * @param t 自上次更新以来经过的时间占更新间隔的比例,用于线性插值。 + */ + @NotNull + T get(double t); + + /** 获取上次更新前的平滑值(旧值) */ + @NotNull + T getLast(); +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/modkeymapping/ModKeyMapping.java b/common/src/main/java/com/github/leawind/thirdperson/util/modkeymapping/ModKeyMapping.java new file mode 100644 index 00000000..876ea3da --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/modkeymapping/ModKeyMapping.java @@ -0,0 +1,125 @@ +package com.github.leawind.thirdperson.util.modkeymapping; + +import com.mojang.blaze3d.platform.InputConstants; +import dev.architectury.registry.client.keymappings.KeyMappingRegistry; +import java.util.HashMap; +import java.util.function.Supplier; +import net.minecraft.client.KeyMapping; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * 按键映射 + * + *

    使用方法: + * + *

    在模组初始化时,首先使用{@link ModKeyMapping#of}实例化所有按键映射,并绑定需要的的事件处理函数。 + * + *

    最后调用 {@link ModKeyMapping#registerAll()}方法使用 Architectury API 注册按键。 + * + *

    示例: + * + *

    + * public class ModKeys{
    + *     public static final KEY_OPEN_CONFIG = ModKeyMapping.of("key.examplemod.open_config", InputConstants.UNKNOWN.getValue(), "key.categories.examplemod")
    + *     public static void init(){
    + *         ModKeyMapping.registerAll();
    + *     }
    + * }
    + * 
    + */ +public interface ModKeyMapping extends Comparable { + HashMap mappings = new HashMap<>(); + + /** 按键是否已按下 */ + boolean isDown(); + + /** + * 长按时长 + * + *

    按住一个按键足够长时间后触发长按事件 + * + * @param holdLength 长按时长,单位是 ms + */ + @Contract("_ -> this") + ModKeyMapping holdMs(long holdLength); + + /** + * 短按时长 + * + *

    按下一个按键,并在足够短的时间内松开,就会触发短按事件。 + * + * @param pressLength 短按时长,单位是 ms + */ + @Contract("_ -> this") + ModKeyMapping pressMs(long pressLength); + + /** 当按键被按下时立即触发 */ + @Contract("_ -> this") + ModKeyMapping onDown(@NotNull Runnable handler); + + /** + * 当按键被按下时立即触发 + * + * @param handler 事件处理函数。若其返回true,则不会触发后续的 onPress 或 onHold 事件 + */ + @Contract("_ -> this") + ModKeyMapping onDown(@NotNull Supplier handler); + + /** 当按键松开时立即触发,位于 onPress 之前 */ + @Contract("_ -> this") + ModKeyMapping onUp(@NotNull Runnable handler); + + /** + * 当按键松开时立即触发,位于 onPress 之前 + * + * @param handler 事件处理函数。若其返回true,则不会触发后续的 onPress 事件 + */ + @Contract("_ -> this") + ModKeyMapping onUp(@NotNull Supplier handler); + + /** 按下一个按键后经过足够短的时间后抬起时触发 */ + @Contract("_ -> this") + ModKeyMapping onPress(@NotNull Runnable handler); + + /** 按下一个按键后经过足够短的时间后抬起时触发 */ + @Contract("_ -> this") + ModKeyMapping onPress(@NotNull Supplier handler); + + /** 当按住一个按键时间足够长时触发 */ + @Contract("_ -> this") + ModKeyMapping onHold(@NotNull Runnable handler); + + /** 当按住一个按键时间足够长时触发 */ + @Contract("_ -> this") + ModKeyMapping onHold(@NotNull Supplier handler); + + /** + * 不设置默认按键 + * + *

    需要用户自行设置按键 + * + * @param id 按键映射的标识符,用于可翻译文本 + * @param categoryKey 类别标识符,用于可翻译文本 + */ + @Contract("_,_ -> new") + static @NotNull ModKeyMapping of(@NotNull String id, @NotNull String categoryKey) { + return of(id, InputConstants.UNKNOWN.getValue(), categoryKey); + } + + /** + * @param id 按键映射的标识符,用于可翻译文本 + * @param defaultValue 默认按键 + * @param categoryKey 类别标识符,用于可翻译文本 + */ + @Contract("_,_,_ -> new") + static @NotNull ModKeyMapping of( + @NotNull String id, int defaultValue, @NotNull String categoryKey) { + return new ModKeyMappingImpl(id, defaultValue, categoryKey); + } + + /** 使用 Architectury API 注册所有已实例化的按键映射 */ + static void registerAll() { + mappings.values().forEach(KeyMappingRegistry::register); + } +} diff --git a/common/src/main/java/com/github/leawind/thirdperson/util/modkeymapping/ModKeyMappingImpl.java b/common/src/main/java/com/github/leawind/thirdperson/util/modkeymapping/ModKeyMappingImpl.java new file mode 100644 index 00000000..7860ebcb --- /dev/null +++ b/common/src/main/java/com/github/leawind/thirdperson/util/modkeymapping/ModKeyMappingImpl.java @@ -0,0 +1,149 @@ +package com.github.leawind.thirdperson.util.modkeymapping; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.function.Supplier; +import net.minecraft.client.KeyMapping; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class ModKeyMappingImpl extends KeyMapping implements ModKeyMapping { + private long holdMs = 300; + private long pressMs = 300; + private long keyDownTime = 0; + private @Nullable Timer timer = null; + private @Nullable Supplier onDown = null; + private @Nullable Supplier onUp = null; + private @Nullable Supplier onHold = null; + private @Nullable Supplier onPress = null; + + /** + * @param id 按键映射的标识符,用于可翻译文本 + * @param defaultValue 默认按键 + * @param categoryKey 类别标识符,用于可翻译文本 + */ + public ModKeyMappingImpl(String id, int defaultValue, String categoryKey) { + super(id, defaultValue, categoryKey); + mappings.put(id, this); + } + + @Override + public boolean isDown() { + return super.isDown(); + } + + @Override + public void setDown(boolean down) { + boolean wasDown = isDown(); + super.setDown(down); + long now = System.currentTimeMillis(); + if (!wasDown && down) { + // key down + if (runIfNonNull(onDown)) { + return; + } + keyDownTime = now; + if (onHold != null) { + timer = new Timer(); + timer.schedule( + new TimerTask() { + @Override + public void run() { + runIfNonNull(onHold); + timer = null; + } + }, + holdMs); + } + } else if (wasDown && !down) { + // key up + long sinceKeydown = now - keyDownTime; + if (runIfNonNull(onUp)) { + return; + } + if (sinceKeydown < pressMs) { + if (timer != null) { + timer.cancel(); + timer = null; + } + runIfNonNull(onPress); + } + } + } + + @Override + public ModKeyMappingImpl holdMs(long holdLength) { + this.holdMs = holdLength; + return this; + } + + @Override + public ModKeyMappingImpl pressMs(long pressLength) { + this.pressMs = pressLength; + return this; + } + + @Override + public ModKeyMappingImpl onDown(@NotNull Runnable handler) { + return onDown( + () -> { + handler.run(); + return false; + }); + } + + @Override + public ModKeyMappingImpl onDown(@NotNull Supplier handler) { + onDown = handler; + return this; + } + + @Override + public ModKeyMappingImpl onUp(@NotNull Runnable handler) { + return onUp( + () -> { + handler.run(); + return false; + }); + } + + @Override + public ModKeyMappingImpl onUp(@NotNull Supplier handler) { + onUp = handler; + return this; + } + + @Override + public ModKeyMappingImpl onPress(@NotNull Runnable handler) { + return onPress( + () -> { + handler.run(); + return false; + }); + } + + @Override + public ModKeyMappingImpl onPress(@NotNull Supplier handler) { + onPress = handler; + return this; + } + + @Override + public ModKeyMappingImpl onHold(@NotNull Runnable handler) { + return onHold( + () -> { + handler.run(); + return false; + }); + } + + @Override + public ModKeyMappingImpl onHold(@NotNull Supplier handler) { + onHold = handler; + return this; + } + + private static boolean runIfNonNull(@Nullable Supplier handler) { + return handler != null && handler.get(); + } +} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/ExpectPlatformExample.java b/common/src/main/java/net/leawind/mc/thirdperson/ExpectPlatformExample.java deleted file mode 100644 index 75fdc016..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/ExpectPlatformExample.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.leawind.mc.thirdperson; - - -@SuppressWarnings("unused") -public class ExpectPlatformExample { - /** - * 虽然暂时没用,但是留着防止忘记,省的以后又去翻文档。 - */ - @dev.architectury.injectables.annotations.ExpectPlatform - public static String getMessage () { - throw new AssertionError(); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/ThirdPerson.java b/common/src/main/java/net/leawind/mc/thirdperson/ThirdPerson.java deleted file mode 100644 index f35bbe72..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/ThirdPerson.java +++ /dev/null @@ -1,58 +0,0 @@ -package net.leawind.mc.thirdperson; - - -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.thirdperson.api.config.ConfigManager; -import net.leawind.mc.thirdperson.api.core.CameraAgent; -import net.leawind.mc.thirdperson.api.core.EntityAgent; -import net.leawind.mc.thirdperson.impl.config.ConfigManagerImpl; -import net.minecraft.client.Minecraft; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Debug: - *

    - * # 快速装填5的弩
    - * /give @s crossbow{Enchantments:[{id:quick_charge,lvl:5}]}
    - * # 靶子村民
    - * /summon villager ~ ~ ~ {NoAI:1b}
    - * 
    - */ -public final class ThirdPerson { - public static final Minecraft mc = Minecraft.getInstance(); - public static final Logger LOGGER = LoggerFactory.getLogger(ThirdPersonConstants.MOD_NAME); - public static final ConfigManager CONFIG_MANAGER = new ConfigManagerImpl(); - public static EntityAgent ENTITY_AGENT; - public static CameraAgent CAMERA_AGENT; - - public static void init () { - ENTITY_AGENT = EntityAgent.create(mc); - CAMERA_AGENT = CameraAgent.create(mc); - CONFIG_MANAGER.tryLoad(); - ThirdPersonResources.register(); - ThirdPersonKeys.register(); - ThirdPersonEvents.register(); - } - - /** - * 判断:模组功能已启用,且相机和玩家都已经初始化 - */ - public static boolean isAvailable () { - return mc.player != null // - && mc.cameraEntity != null // - && getConfig().is_mod_enable // - && mc.gameRenderer.getMainCamera().isInitialized() // - ; - } - - /** - * 获取当前配置实例 - * - * @return 配置实例 - */ - public static @NotNull Config getConfig () { - return CONFIG_MANAGER.getConfig(); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonConstants.java b/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonConstants.java deleted file mode 100644 index 5515ce9d..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonConstants.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.leawind.mc.thirdperson; - - -import net.leawind.mc.thirdperson.impl.core.EntityAgentImpl; -import net.leawind.mc.util.annotations.VersionSensitive; -import net.minecraft.client.Minecraft; -import net.minecraft.world.level.ClipContext; - -import java.io.File; - -public final class ThirdPersonConstants { - public static final String MOD_ID = "leawind_third_person"; - public static final String MOD_NAME = "Leawind's Third Person"; - public static final String KEY_CATEGORY = "key.categories." + MOD_ID; - public static final File CONFIG_FILE = Minecraft.getInstance().gameDirectory.toPath().resolve("config/" + MOD_ID + ".json").toFile(); - public static final long CONFIG_LAZY_SAVE_DELAY = 60000L; - /** - * 成像平面到相机的距离,这是一个固定值,硬编码在Minecraft源码中。 - * - * @see net.minecraft.client.Camera#getNearPlane() - */ - public static final double NEAR_PLANE_DISTANCE = 0.050; - /** - * 服务器允许的最大交互距离 - * - * @see net.minecraft.server.network.ServerGamePacketListenerImpl#MAX_INTERACTION_DISTANCE - */ - @VersionSensitive public static final double MAX_INTERACTION_DISTANCE = 6.0; - public static final long CAMERA_FOLLOW_DELAY = 5000L; - public static final double CAMERA_PITCH_DEGREE_LIMIT = 89.800; - public static final double CAMERA_THROUGH_WALL_DETECTION = 0.180; - /** - * 当准星放在相机实体上时,将相机实体的不透明度降低到这个值。 - */ - public static final double GAZE_OPACITY = 0.32; - public static final double OPACITY_HALFLIFE = 0.0625; - /** - * 预测目标实体时仅考虑视锥角内的实体 - */ - public static final double TARGET_PREDICTION_DEGREES_LIMIT = 30; - /** - * 渲染相机实体的透明度阈值,当不透明度低于这个值时,将不渲染实体。 - * - * @see EntityAgentImpl#getSmoothOpacity() - */ - public static final float RENDERED_OPACITY_THRESHOLD = 0.01F; - public static final double FIRST_PERSON_TRANSITION_END_THRESHOLD = 0.05; - /** - * 平滑眼睛的半衰期乘数 - */ - public static final double EYE_HALFLIFE_MULTIPLIER = 0.1; - public static final double TRANSITION_HALFLIFE_MULTIPLIER = 0.3; - /** - * 阻挡相机的方块外形获取器 - */ - public static final ClipContext.Block CAMERA_OBSTACLE_BLOCK_SHAPE_GETTER = ClipContext.Block.OUTLINE; -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonEvents.java b/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonEvents.java deleted file mode 100644 index 4930ac8e..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonEvents.java +++ /dev/null @@ -1,302 +0,0 @@ -package net.leawind.mc.thirdperson; - - -import com.mojang.blaze3d.platform.Window; -import dev.architectury.event.EventResult; -import dev.architectury.event.events.client.ClientLifecycleEvent; -import dev.architectury.event.events.client.ClientPlayerEvent; -import dev.architectury.event.events.client.ClientRawInputEvent; -import dev.architectury.event.events.client.ClientTickEvent; -import net.leawind.mc.thirdperson.api.cameraoffset.CameraOffsetMode; -import net.leawind.mc.thirdperson.api.cameraoffset.CameraOffsetScheme; -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.thirdperson.mixin.CameraMixin; -import net.leawind.mc.thirdperson.mixin.GameRendererMixin; -import net.leawind.mc.thirdperson.mixin.MinecraftMixin; -import net.leawind.mc.thirdperson.mixin.MouseHandlerMixin; -import net.leawind.mc.util.itempattern.ItemPattern; -import net.leawind.mc.util.math.LMath; -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.leawind.mc.util.math.vector.api.Vector3d; -import net.minecraft.client.Camera; -import net.minecraft.client.Minecraft; -import net.minecraft.client.MouseHandler; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.core.BlockPos; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; -import net.minecraft.world.phys.shapes.CollisionContext; -import org.jetbrains.annotations.NotNull; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.Objects; -import java.util.Optional; - -public final class ThirdPersonEvents { - public static void register () { - ClientTickEvent.CLIENT_PRE.register(ThirdPersonEvents::onClientTickPre); - ClientLifecycleEvent.CLIENT_STOPPING.register(ThirdPersonEvents::onClientStopping); - ClientPlayerEvent.CLIENT_PLAYER_RESPAWN.register(ThirdPersonEvents::onClientPlayerRespawn); - ClientPlayerEvent.CLIENT_PLAYER_JOIN.register(ThirdPersonEvents::onClientPlayerJoin); - ClientRawInputEvent.MOUSE_SCROLLED.register(ThirdPersonEvents::onMouseScrolled); - } - - /** - * Client tick 前 - * - * @see ClientTickEvent#CLIENT_PRE - */ - private static void onClientTickPre (@NotNull Minecraft minecraft) { - if (minecraft.isPaused()) { - return; - } - if (!ThirdPerson.isAvailable()) { - return; - } - Config config = ThirdPerson.getConfig(); - ThirdPersonStatus.isTemporaryFirstPerson = false; - Entity cameraEntity = ThirdPerson.ENTITY_AGENT.getRawCameraEntity(); - { - /* - 如果玩家在墙里边,就暂时切换到第一人称 - 这可能是因为玩家窒息,或处于观察者模式穿墙 - */ - Vec3 eyePos = cameraEntity.getEyePosition(); - BlockPos blockPos = new BlockPos(eyePos); - AABB eyeAabb = AABB.ofSize(eyePos, 0.8, 0.8, 0.8); - boolean isInWall = ThirdPersonConstants.CAMERA_OBSTACLE_BLOCK_SHAPE_GETTER.get(cameraEntity.level.getBlockState(blockPos), cameraEntity.level, blockPos, CollisionContext.empty())// - .toAabbs().stream().anyMatch(a -> a.move(blockPos).intersects(eyeAabb)); - if (isInWall) { - ThirdPersonStatus.isTemporaryFirstPerson = true; - } - } - if (cameraEntity instanceof LivingEntity livingEntity) { - if (livingEntity.isUsingItem()) { - ThirdPersonStatus.isTemporaryFirstPerson |= ItemPattern.anyMatch(livingEntity.getUseItem(), config.getUseToFirstPersonItemPatterns(), ThirdPersonResources.itemPatternManager.useToFirstPersonItemPatterns); - } - } - ThirdPerson.ENTITY_AGENT.onClientTickPre(); - ThirdPerson.CAMERA_AGENT.onClientTickPre(); - } - - private static void onClientStopping (Minecraft minecraft) { - ThirdPerson.CONFIG_MANAGER.trySave(); - } - - /** - * 当玩家死亡后重生或加入新的维度时触发 - * - * @see ClientPlayerEvent#CLIENT_PLAYER_RESPAWN - */ - private static void onClientPlayerRespawn (@NotNull LocalPlayer oldPlayer, @NotNull LocalPlayer newPlayer) { - if (ThirdPerson.getConfig().is_mod_enable) { - onPlayerReset(); - ThirdPerson.LOGGER.info("on Client player respawn"); - } - } - - /** - * 当玩家加入时触发 - * - * @see ClientPlayerEvent#CLIENT_PLAYER_JOIN - */ - private static void onClientPlayerJoin (@NotNull LocalPlayer player) { - if (ThirdPerson.getConfig().is_mod_enable) { - onPlayerReset(); - ThirdPerson.LOGGER.info("on Client player join"); - } - } - - /** - * 使用滚轮调整距离 - * - * @param minecraft mc - * @param amount 向前滚是+1,向后滚是-1 - * @see ClientRawInputEvent#MOUSE_SCROLLED - */ - private static @NotNull EventResult onMouseScrolled (@NotNull Minecraft minecraft, double amount) { - Config config = ThirdPerson.getConfig(); - if (ThirdPersonStatus.isAdjustingCameraDistance()) { - double dist = config.getCameraOffsetScheme().getMode().getMaxDistance(); - dist = config.getDistanceMonoList().offset(dist, (int)-Math.signum(amount)); - config.getCameraOffsetScheme().getMode().setMaxDistance(dist); - return EventResult.interruptFalse(); - } else { - return EventResult.pass(); - } - } - - /** - * 重置玩家 - * - * @see ThirdPersonEvents#onClientPlayerRespawn(LocalPlayer, LocalPlayer) - * @see ThirdPersonEvents#onClientPlayerJoin(LocalPlayer) - */ - private static void onPlayerReset () { - ThirdPerson.ENTITY_AGENT.reset(); - ThirdPerson.CAMERA_AGENT.reset(); - } - - /** - * 调用Camera.setup时触发 - *

    - * 该调用位于真正渲染画面之前。 - *

    - * GameRender#render -> GameRender#renderLevel -> Camera#setup - * - * @see Camera#setup - * @see CameraMixin#setup_head - */ - public static void onCameraSetup (@NotNull BlockGetter level, float partialTick) { - ThirdPersonStatus.lastPartialTick = partialTick; - ThirdPerson.CAMERA_AGENT.setBlockGetter(level); - if (!ThirdPerson.ENTITY_AGENT.isCameraEntityExist()) { - return; - } - if (ThirdPersonStatus.isRenderingInThirdPerson()) { - ThirdPerson.CAMERA_AGENT.onCameraSetup(); - } - } - - /** - * gameRenderer 渲染之前 - * - * @see GameRenderer#render(float, long, boolean) - * @see GameRendererMixin#pre_render(float, long, boolean, CallbackInfo) - */ - public static void onPreRender (float partialTick) { - // in seconds - double now = System.currentTimeMillis() / 1000D; - double period = now - ThirdPersonStatus.lastRenderTickTimeStamp; - ThirdPersonStatus.lastRenderTickTimeStamp = now; - { - boolean shouldCameraTurnWithEntity = ThirdPersonStatus.shouldCameraTurnWithEntity(); - if (shouldCameraTurnWithEntity && !ThirdPersonStatus.wasSouldCameraTurnWithEntity) { - onStartCameraTurnWithEntity(); - } - ThirdPersonStatus.wasSouldCameraTurnWithEntity = shouldCameraTurnWithEntity; - } - final boolean isRenderInThirdPerson = !ThirdPerson.mc.options.getCameraType().isFirstPerson(); - if (isRenderInThirdPerson != ThirdPersonStatus.wasRenderInThirdPersonLastRenderTick) { - if (isRenderInThirdPerson) { - onEnterThirdPerson(); - } else { - onEnterFirstPerson(); - } - ThirdPerson.mc.levelRenderer.needsUpdate(); - ThirdPersonStatus.wasRenderInThirdPersonLastRenderTick = isRenderInThirdPerson; - } - if (ThirdPerson.isAvailable() && ThirdPerson.ENTITY_AGENT.isCameraEntityExist()) { - if (ThirdPersonStatus.isRenderingInThirdPerson()) { - ThirdPerson.ENTITY_AGENT.onRenderTickPre(now, period, partialTick); - ThirdPerson.CAMERA_AGENT.onRenderTickPre(now, period, partialTick); - } - } - } - - /** - * 进入“相机跟随玩家转动”状态 - */ - public static void onStartCameraTurnWithEntity () { - // 将玩家朝向设为与相机一致 - ThirdPerson.ENTITY_AGENT.setRawRotation(ThirdPerson.CAMERA_AGENT.getRotation()); - } - - /** - * @see ThirdPersonKeys#ADJUST_POSITION - */ - public static void onStartAdjustingCameraOffset () { - } - - /** - * @see ThirdPersonKeys#ADJUST_POSITION - */ - public static void onStopAdjustingCameraOffset () { - ThirdPerson.CONFIG_MANAGER.lazySave(); - } - - /** - * 移动鼠标调整相机偏移 - * - * @param movement 移动的像素 - * @see MouseHandler#turnPlayer() - * @see MouseHandlerMixin#turnPlayer_head(CallbackInfo) - */ - public static void onAdjustingCameraOffset (@NotNull Vector2d movement) { - if (movement.lengthSquared() == 0) { - return; - } - Config config = ThirdPerson.getConfig(); - Window window = ThirdPerson.mc.getWindow(); - Vector2d screenSize = Vector2d.of(window.getScreenWidth(), window.getScreenHeight()); - CameraOffsetScheme scheme = config.getCameraOffsetScheme(); - CameraOffsetMode mode = scheme.getMode(); - if (mode.isCentered()) { - // 相机在头顶,只能上下调整 - double topOffset = mode.getCenterOffsetRatio(); - topOffset += -movement.y() / screenSize.y(); - topOffset = LMath.clamp(topOffset, -1, 1); - mode.setCenterOffsetRatio(topOffset); - } else { - // 相机没固定在头顶,可以上下左右调整 - Vector2d offset = mode.getSideOffsetRatio(Vector2d.of()); - offset.sub(movement.div(screenSize)); - offset.clamp(-1, 1); - scheme.setSide(Math.signum(offset.x())); - mode.setSideOffsetRatio(offset); - } - } - - /** - * 当玩家与环境交互时,趁交互事件处理前,让玩家看向相机落点 - * - * @see MinecraftMixin#handleKeybinds_head(CallbackInfo) - */ - public static void onBeforeHandleKeybinds (@NotNull Minecraft minecraft) { - Config config = ThirdPerson.getConfig(); - /* - 接管“切换视角”按键绑定 - */ - while (minecraft.options.keyTogglePerspective.consumeClick()) { - config.is_third_person_mode = !config.is_third_person_mode; - } - if (ThirdPersonStatus.isRenderingInThirdPerson()) { - if (ThirdPerson.ENTITY_AGENT.wasInterecting()) { - /* - 立即调用 gameRender.pick 方法来更新玩家注视着的目标 (minecraft.hitResult) -

    - 这个 pick 方法也被使用 mixin 修改了 pick 的方向,使玩家朝向相机准星的落点。 - */ - minecraft.gameRenderer.pick(1f); - } - } - } - - /** - * 进入第一人称视角 - */ - public static void onEnterFirstPerson () { - if (ThirdPerson.getConfig().turn_with_camera_when_enter_first_person) { - Optional pickPosition = Objects.requireNonNull(ThirdPerson.mc.getCameraEntity()).isSpectator() ? Optional.empty(): ThirdPerson.CAMERA_AGENT.getPickPosition(); - if (pickPosition.isEmpty()) { - ThirdPerson.ENTITY_AGENT.setRawRotation(ThirdPerson.CAMERA_AGENT.getRotation()); - } else { - Vector3d eyeToHitResult = pickPosition.get().sub(ThirdPerson.ENTITY_AGENT.getRawEyePosition(1)); - ThirdPerson.ENTITY_AGENT.setRawRotation(LMath.rotationDegreeFromDirection(eyeToHitResult)); - } - } - } - - /** - * 进入第三人称视角 - */ - public static void onEnterThirdPerson () { - ThirdPersonStatus.lastPartialTick = Minecraft.getInstance().getFrameTime(); - ThirdPerson.CAMERA_AGENT.reset(); - ThirdPerson.ENTITY_AGENT.reset(); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonKeys.java b/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonKeys.java deleted file mode 100644 index 303b5a91..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonKeys.java +++ /dev/null @@ -1,65 +0,0 @@ -package net.leawind.mc.thirdperson; - - -import com.mojang.blaze3d.platform.InputConstants; -import net.leawind.mc.thirdperson.api.cameraoffset.CameraOffsetScheme; -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.thirdperson.impl.core.rotation.RotateTarget; -import net.leawind.mc.util.modkeymapping.ModKeyMapping; -import net.minecraft.client.Minecraft; -import org.jetbrains.annotations.NotNull; - -@SuppressWarnings("unused") -public final class ThirdPersonKeys { - public static final ModKeyMapping ADJUST_POSITION = ModKeyMapping.of(getId("adjust_position"), InputConstants.KEY_Z, ThirdPersonConstants.KEY_CATEGORY) // - .onDown(ThirdPersonEvents::onStartAdjustingCameraOffset) // - .onUp(ThirdPersonEvents::onStopAdjustingCameraOffset); - public static final ModKeyMapping FORCE_AIMING = ModKeyMapping.of(getId("force_aiming"), ThirdPersonConstants.KEY_CATEGORY); - public static final ModKeyMapping TOOGLE_MOD_ENABLE = ModKeyMapping.of(getId("toggle_mod_enable"), ThirdPersonConstants.KEY_CATEGORY).onDown(() -> { - Config config = ThirdPerson.getConfig(); - if (ThirdPersonStatus.isRenderingInThirdPerson()) { - if (config.is_mod_enable) { - ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTarget.CAMERA_ROTATION); - } else { - ThirdPersonEvents.onEnterThirdPerson(); - } - config.is_mod_enable = !config.is_mod_enable; - } - }); - public static final ModKeyMapping OPEN_CONFIG_MENU = ModKeyMapping.of(getId("open_config_menu"), ThirdPersonConstants.KEY_CATEGORY).onDown(() -> { - Minecraft mc = Minecraft.getInstance(); - if (mc.screen == null) { - mc.setScreen(ThirdPerson.CONFIG_MANAGER.getConfigScreen(null)); - } - }); - public static final ModKeyMapping TOGGLE_SIDE = ModKeyMapping.of(getId("toggle_side"), InputConstants.KEY_CAPSLOCK, ThirdPersonConstants.KEY_CATEGORY).onDown(() -> { - CameraOffsetScheme scheme = ThirdPerson.getConfig().getCameraOffsetScheme(); - if (scheme.isCentered()) { - scheme.toNextSide(); - return true; - } else { - return false; - } - }).onHold(() -> { - ThirdPerson.getConfig().getCameraOffsetScheme().setCentered(true); - }).onPress(() -> { - ThirdPerson.getConfig().getCameraOffsetScheme().toNextSide(); - }); - public static final ModKeyMapping TOGGLE_AIMING = ModKeyMapping.of(getId("toggle_aiming"), ThirdPersonConstants.KEY_CATEGORY).onDown(() -> { - if (ThirdPerson.isAvailable() && ThirdPersonStatus.isRenderingInThirdPerson()) { - ThirdPersonStatus.isToggleToAiming = !ThirdPersonStatus.isToggleToAiming; - } - }); - public static final ModKeyMapping TOGGLE_PITCH_LOCK = ModKeyMapping.of(getId("toggle_pitch_lock"), ThirdPersonConstants.KEY_CATEGORY).onDown(() -> { - Config config = ThirdPerson.getConfig(); - config.lock_camera_pitch_angle = !config.lock_camera_pitch_angle; - }); - - private static @NotNull String getId (@NotNull String name) { - return "key." + ThirdPersonConstants.MOD_ID + "." + name; - } - - public static void register () { - ModKeyMapping.registerAll(); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonResources.java b/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonResources.java deleted file mode 100644 index 540caf10..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonResources.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.leawind.mc.thirdperson; - - -import dev.architectury.injectables.annotations.ExpectPlatform; -import net.leawind.mc.thirdperson.resources.ItemPatternManager; -import net.leawind.mc.util.itempattern.ItemPattern; - -/** - * 自定义资源包 - */ -public final class ThirdPersonResources { - /** - * 物品模式管理器 - * - * @see ItemPattern - */ - public static ItemPatternManager itemPatternManager; - - @ExpectPlatform - public static void register () { - throw new AssertionError(); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonStatus.java b/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonStatus.java deleted file mode 100644 index 2dd1ed08..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/ThirdPersonStatus.java +++ /dev/null @@ -1,106 +0,0 @@ -package net.leawind.mc.thirdperson; - - -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.thirdperson.api.core.rotation.SmoothType; -import net.leawind.mc.thirdperson.impl.core.CameraAgentImpl; -import net.leawind.mc.thirdperson.impl.core.rotation.RotateTarget; -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.leawind.mc.util.math.vector.api.Vector3d; -import net.minecraft.world.entity.player.Player; -import org.jetbrains.annotations.NotNull; - -public final class ThirdPersonStatus { - public static final @NotNull Vector3d impulse = Vector3d.of(0); - public static final @NotNull Vector2d impulseHorizon = Vector2d.of(0); - /** - * @see ThirdPersonKeys#TOGGLE_AIMING - */ - public static boolean isToggleToAiming = false; - public static float lastPartialTick = 1; - public static double lastRenderTickTimeStamp = 0; - /** - * 上一tick中是否以第三人称视角渲染 mc.options.cameraType.isThirdPerson() - */ - public static boolean wasRenderInThirdPersonLastRenderTick = false; - /** - * 在第三人称视角下暂时使用第一人称视角 - */ - public static boolean isTemporaryFirstPerson = false; - /** - * 是否正在从第三人称过渡到第一人称 - */ - public static boolean isTransitioningToFirstPerson = false; - /** - * 在 {@link ThirdPersonEvents#onPreRender} 中更新 - * - * @see ThirdPersonStatus#shouldCameraTurnWithEntity - */ - public static boolean wasSouldCameraTurnWithEntity = false; - - /** - * 是否正在调整摄像机偏移量 - */ - public static boolean isAdjustingCameraOffset () { - return isAdjustingCameraDistance(); - } - - /** - * 检查相机距离是否正在调整。 - */ - public static boolean isAdjustingCameraDistance () { - return ThirdPerson.isAvailable() && isRenderingInThirdPerson() && ThirdPersonKeys.ADJUST_POSITION.isDown(); - } - - /** - * 当前是否以第三人称渲染 - */ - public static boolean isRenderingInThirdPerson () { - return !ThirdPerson.mc.options.getCameraType().isFirstPerson(); - } - - /** - * 当前是否显示准星 - */ - public static boolean shouldRenderCrosshair () { - Config config = ThirdPerson.getConfig(); - return ThirdPerson.isAvailable() && (ThirdPerson.ENTITY_AGENT.wasAiming() ? config.render_crosshair_when_aiming: config.render_crosshair_when_not_aiming); - } - - /** - * 根据玩家的按键判断玩家是否想瞄准 - */ - public static boolean doesPlayerWantToAim () { - return isToggleToAiming || ThirdPersonKeys.FORCE_AIMING.isDown(); - } - - /** - * 探测射线是否应当起始于相机处,而非玩家眼睛处 - */ - public static boolean shouldPickFromCamera () { - if (!ThirdPerson.ENTITY_AGENT.isCameraEntityExist()) { - return false; - } else if (!ThirdPerson.getConfig().use_camera_pick_in_creative) { - return false; - } - return ThirdPerson.ENTITY_AGENT.getRawCameraEntity() instanceof Player player && player.isCreative(); - } - - /** - * 根据不透明度判断是否需要渲染相机实体 - * - * @return 是否应当渲染相机实体 - */ - public static boolean shouldRenderCameraEntity () { - return ThirdPerson.ENTITY_AGENT.getSmoothOpacity() > ThirdPersonConstants.RENDERED_OPACITY_THRESHOLD; - } - - /** - * 第三人称下,通常是直接用鼠标控制相机的朝向{@link CameraAgentImpl#relativeRotation},再根据一些因素决定玩家的朝向。 - *

    - * 但为了与另一个模组 Do a Barrel Roll 兼容,在特定情况下,允许直接用鼠标控制玩家朝向,而相机跟随玩家旋转。 - */ - public static boolean shouldCameraTurnWithEntity () { - return ThirdPerson.ENTITY_AGENT.getRotateTarget() == RotateTarget.CAMERA_ROTATION && ThirdPerson.ENTITY_AGENT.getRotationSmoothType() == SmoothType.HARD; - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/api/cameraoffset/CameraOffsetMode.java b/common/src/main/java/net/leawind/mc/thirdperson/api/cameraoffset/CameraOffsetMode.java deleted file mode 100644 index fb356c43..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/api/cameraoffset/CameraOffsetMode.java +++ /dev/null @@ -1,92 +0,0 @@ -package net.leawind.mc.thirdperson.api.cameraoffset; - - -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.leawind.mc.util.math.vector.api.Vector3d; -import org.jetbrains.annotations.NotNull; - -/** - * 相机偏移模式 - *

    - * 描述相机应如何偏移 - *

    - * 相机偏移相关数据直接存储在配置对象中 - */ -public interface CameraOffsetMode { - /** - * 眼睛平滑半衰期 - */ - @NotNull Vector3d getEyeSmoothHalflife (); - - /** - * 距离平滑系数 - */ - double getDistanceSmoothHalflife (); - - /** - * 相机偏移平滑系数 - */ - @NotNull Vector2d getOffsetSmoothHalflife (); - - /** - * 相机到玩家的最大距离 - */ - double getMaxDistance (); - - /** - * 设置相机到玩家的最大距离 - */ - void setMaxDistance (double distance); - - /** - * 当前是否居中 - */ - boolean isCentered (); - - /** - * 设置是否居中 - */ - void setCentered (boolean isCentered); - - boolean isCameraLeftOfPlayer (); - - /** - * 设置相机在玩家的左边还是右边 - */ - void setSide (boolean isCameraLeftOfPlayer); - - /** - * 切换到另一边,如果当前居中,则退出居中 - */ - void toNextSide (); - - /** - * 获取偏移量 - *

    - * 根据当前是居中还是在两侧自动计算偏移量 - */ - void getOffsetRatio (@NotNull Vector2d v); - - /** - * 设置当相机位于两侧,而非居中时的偏移量。 - */ - void setSideOffsetRatio (@NotNull Vector2d v); - - /** - * 获取当相机居中时的,垂直偏移量 - */ - double getCenterOffsetRatio (); - - /** - * 设置当相机居中时的,垂直偏移量 - */ - void setCenterOffsetRatio (double offset); - - /** - * 获取当相机位于两侧,而非居中时的偏移量。 - * - * @param v 将取得的数据存入该向量 - * @return 与传入参数是同一个对象 - */ - @NotNull Vector2d getSideOffsetRatio (@NotNull Vector2d v); -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/api/cameraoffset/CameraOffsetScheme.java b/common/src/main/java/net/leawind/mc/thirdperson/api/cameraoffset/CameraOffsetScheme.java deleted file mode 100644 index a83b92e5..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/api/cameraoffset/CameraOffsetScheme.java +++ /dev/null @@ -1,69 +0,0 @@ -package net.leawind.mc.thirdperson.api.cameraoffset; - - -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.thirdperson.impl.cameraoffset.CameraOffsetSchemeImpl; -import org.jetbrains.annotations.NotNull; - -/** - * 第三人称相机的偏移方案 - *

    - * 第三人称下,相机会根据其当前所处的模式来确定相机的行为。例如如何跟随玩家、如何旋转、与玩家的相对位置如何确定等。 - *

    - * 默认有两种模式,按F5在第一人称和两种模式间切换 - */ -public interface CameraOffsetScheme { - static CameraOffsetScheme of (Config that) { - return new CameraOffsetSchemeImpl(that); - } - - /** - * 获取当前模式 - */ - @NotNull CameraOffsetMode getMode (); - - /** - * 获取当前未启用的模式 - */ - @NotNull CameraOffsetMode getAnotherMode (); - - /** - * 设置相机相对于玩家的方向 - *

    - * - * @param side 大于0表示相机在玩家左侧 - */ - void setSide (double side); - - /** - * 设置相机相对于玩家的方向 - * - * @param isCameraLeftOfPlayer 相机是否在玩家左侧 - */ - void setSide (boolean isCameraLeftOfPlayer); - - /** - * 切换到另一边 - */ - void toNextSide (); - - /** - * 当前是否居中 - */ - boolean isCentered (); - - /** - * 设置当前是否居中 - */ - void setCentered (boolean isCentered); - - /** - * 设置当前是否处于瞄准模式 - */ - boolean isAiming (); - - /** - * 当前是否处于瞄准模式 - */ - void setAiming (boolean aiming); -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/api/config/AbstractConfig.java b/common/src/main/java/net/leawind/mc/thirdperson/api/config/AbstractConfig.java deleted file mode 100644 index 6871212d..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/api/config/AbstractConfig.java +++ /dev/null @@ -1,73 +0,0 @@ -package net.leawind.mc.thirdperson.api.config; - - -import com.google.gson.annotations.Expose; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -/** - * 配置的抽象类 - *

    - * 定义所有配置选项及其默认值 - */ -abstract class AbstractConfig { - // ================================================================================General // - @Expose public boolean is_mod_enable = true; - @Expose public boolean is_third_person_mode = true; - @Expose public boolean lock_camera_pitch_angle = false; - //------------------------------Player Rotation - @Expose public boolean player_rotate_with_camera_when_not_aiming = false; - @Expose public boolean rotate_to_moving_direction = true; - @Expose public boolean auto_rotate_interacting = true; - @Expose public boolean rotate_interacting_type = true; - @Expose public boolean auto_turn_body_drawing_a_bow = false; - //------------------------------Camera Distance Adjustment - @Expose public int available_distance_count = 16; - @Expose public double camera_distance_min = 0.5; - @Expose public double camera_distance_max = 8; - // ================================================================================Miscellaneous // - @Expose public String config_screen_api = "Cloth Config"; - @Expose public boolean center_offset_when_flying = true; - @Expose public boolean use_camera_pick_in_creative = true; - @Expose public boolean turn_with_camera_when_enter_first_person = true; - @Expose public double camera_ray_trace_length = 256; - @Expose public boolean enable_target_entity_predict = true; - //------------------------------Player Fade out - @Expose public boolean player_fade_out_enabled = true; - //------------------------------Crosshair - @Expose public boolean render_crosshair_when_not_aiming = true; - @Expose public boolean render_crosshair_when_aiming = true; - // =================================================================================Smooth Factors // - @Expose public double flying_smooth_halflife = 0.20; - //------------------------------Adjusting Camera - @Expose public double adjusting_camera_offset_smooth_halflife = 0.04; - @Expose public double adjusting_distance_smooth_halflife = 0.08; - //------------------------------Normal Mode - @Expose public double normal_smooth_halflife_horizon = 0.25; - @Expose public double normal_smooth_halflife_vertical = 0.20; - @Expose public double normal_camera_offset_smooth_halflife = 0.08; - @Expose public double normal_distance_smooth_halflife = 0.72; - //------------------------------Aiming Mode - @Expose public double aiming_smooth_halflife_horizon = 0.05; - @Expose public double aiming_smooth_halflife_vertical = 0.05; - @Expose public double aiming_camera_offset_smooth_halflife = 0.03; - @Expose public double aiming_distance_smooth_halflife = 0.04; - // =================================================================================Camera Offset // - //------------------------------Normal Mode - @Expose public boolean normal_is_centered = false; - @Expose public double normal_max_distance = 2.5; - @Expose public double normal_offset_x = -0.28; - @Expose public double normal_offset_y = 0.31; - @Expose public double normal_offset_center = 0.24D; - //------------------------------Aiming Mode - @Expose public boolean aiming_is_centered = false; - @Expose public double aiming_max_distance = 0.89; - @Expose public double aiming_offset_x = -0.47; - @Expose public double aiming_offset_y = -0.09; - @Expose public double aiming_offset_center = 0.48; - @Expose @NotNull public List hold_to_aim_item_pattern_expressions = new ArrayList<>(); - @Expose @NotNull public List use_to_aim_item_pattern_expressions = new ArrayList<>(); - @Expose @NotNull public List use_to_first_person_pattern_expressions = new ArrayList<>(); -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/api/config/Config.java b/common/src/main/java/net/leawind/mc/thirdperson/api/config/Config.java deleted file mode 100644 index a883ab0e..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/api/config/Config.java +++ /dev/null @@ -1,99 +0,0 @@ -package net.leawind.mc.thirdperson.api.config; - - -import net.leawind.mc.thirdperson.api.cameraoffset.CameraOffsetScheme; -import net.leawind.mc.thirdperson.impl.config.ConfigImpl; -import net.leawind.mc.util.itempattern.ItemPattern; -import net.leawind.mc.util.math.monolist.MonoList; -import org.jetbrains.annotations.NotNull; - -import java.util.Set; - -/** - * 定义配置项的默认值、额外方法等信息 - */ -public abstract class Config extends AbstractConfig { - public static final @NotNull Class IMPL = ConfigImpl.class; - public static final @NotNull Config DEFAULTS = new DefaultConfig(); - - public static Config create () { - return new ConfigImpl(); - } - - /** - * 配置项发生变化时更新 - */ - abstract public void update (); - - /** - * 更新相机到玩家的距离的可调挡位们 - */ - abstract public void updateDistancesMonoList (); - - /** - * 更新配置的自动瞄准物品集合 - *

    - * aiming_items 是字符串数组,其中的元素是nbt标签表达式 - *

    - * aiming_item_tags 是解析好的nbt标签集合,用于匹配玩家手持物品 - * - * @see net.leawind.mc.thirdperson.resources.ItemPatternManager#apply - */ - abstract public void updateItemPatterns (); - - abstract public @NotNull Set getHoldToAimItemPatterns (); - - abstract public @NotNull Set getUseToAimItemPatterns (); - - abstract public @NotNull Set getUseToFirstPersonItemPatterns (); - - abstract public @NotNull CameraOffsetScheme getCameraOffsetScheme (); - - abstract public @NotNull MonoList getDistanceMonoList (); - - private static final class DefaultConfig extends Config { - @Override - public void update () { - throw illegalAccess(); - } - - @Override - public void updateDistancesMonoList () { - throw illegalAccess(); - } - - @Override - public void updateItemPatterns () { - throw illegalAccess(); - } - - @Override - public @NotNull Set getHoldToAimItemPatterns () { - throw illegalAccess(); - } - - @Override - public @NotNull Set getUseToAimItemPatterns () { - throw illegalAccess(); - } - - @Override - public @NotNull Set getUseToFirstPersonItemPatterns () { - throw illegalAccess(); - } - - @Override - public @NotNull CameraOffsetScheme getCameraOffsetScheme () { - throw illegalAccess(); - } - - @Override - public @NotNull MonoList getDistanceMonoList () { - throw illegalAccess(); - } - - private IllegalAccessError illegalAccess () { - return new IllegalAccessError("This method should not be invoked on default config"); - } - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/api/config/ConfigManager.java b/common/src/main/java/net/leawind/mc/thirdperson/api/config/ConfigManager.java deleted file mode 100644 index 1c36e5e7..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/api/config/ConfigManager.java +++ /dev/null @@ -1,77 +0,0 @@ -package net.leawind.mc.thirdperson.api.config; - - -import net.leawind.mc.thirdperson.ThirdPersonConstants; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; - -/** - * 配置管理器 - *

    - * 负则配置的加载与保存 - */ -public interface ConfigManager { - /** - * 在可翻译文本的键前加上modid前缀 - * - * @param name 键名 - * @return ${MODID}.${id} - */ - @Contract(value="_ -> new", pure=true) - static @NotNull Component getText (@NotNull String name) { - return Component.translatable(ThirdPersonConstants.MOD_ID + "." + name); - } - - /** - * 加载配置 - *

    - * 如果找不到文件,则保存一份。 - *

    - * 如果失败,则记录错误到日志 - */ - void tryLoad (); - - /** - * 尝试保存配置文件 - *

    - * 如果失败,则记录错误到日志 - */ - void trySave (); - - /** - * 惰性保存 - *

    - * 两次保存时间间隔至少为 {@link ThirdPersonConstants#CONFIG_LAZY_SAVE_DELAY} - */ - void lazySave (); - - /** - * 直接读取配置文件 - */ - void load () throws IOException; - - /** - * 直接保存配置文件 - */ - void save () throws IOException; - - /** - * 获取配置屏幕 - */ - @Nullable Screen getConfigScreen (@Nullable Screen parent); - - /** - * 是否有可用的配置屏幕 - */ - boolean isScreenAvailable (); - - /** - * 获取配置对象 - */ - @NotNull Config getConfig (); -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/api/core/CameraAgent.java b/common/src/main/java/net/leawind/mc/thirdperson/api/core/CameraAgent.java deleted file mode 100644 index 84fd0f3f..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/api/core/CameraAgent.java +++ /dev/null @@ -1,158 +0,0 @@ -package net.leawind.mc.thirdperson.api.core; - - -import net.leawind.mc.thirdperson.impl.core.CameraAgentImpl; -import net.leawind.mc.thirdperson.mixin.CameraMixin; -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.leawind.mc.util.math.vector.api.Vector3d; -import net.minecraft.client.Camera; -import net.minecraft.client.Minecraft; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.ClipContext; -import net.minecraft.world.phys.BlockHitResult; -import net.minecraft.world.phys.EntityHitResult; -import net.minecraft.world.phys.HitResult; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; - -public interface CameraAgent { - @Contract("_ -> new") - static @NotNull CameraAgent create (@NotNull Minecraft mc) { - return new CameraAgentImpl(mc); - } - - /** - * 重置各种属性 - */ - void reset (); - - /** - * 设置维度 - */ - void setBlockGetter (@NotNull BlockGetter blockGetter); - - /** - * 渲染前 - */ - void onRenderTickPre (double now, double period, float partialTick); - - /** - * 渲染过程中放置相机 - *

    - * 在原版的渲染方法中,会调用{@link Camera#setup}来设置相机的位置和朝向。 - *

    - * 在第三人称下,咱需要覆盖该方法的行为,重新设置相机的位置和朝向。 - *

    - * {@link CameraMixin#setup_invoke} - */ - void onCameraSetup (); - - /** - * client tick 前 - *

    - * 通常频率固定为 20Hz - */ - void onClientTickPre (); - - /** - * 获取原版相机对象 - */ - @NotNull Camera getRawCamera (); - - /** - * 获取原始相机位置 - */ - @NotNull Vector3d getRawCameraPosition (); - - /** - * 第三人称相机朝向 - */ - @NotNull Vector2d getRotation (); - - /** - * 假相机 - */ - @NotNull Camera getFakeCamera (); - - /** - * 玩家控制的相机旋转 - * - * @param dy 方向角变化量 - * @param dx 俯仰角变化量 - */ - void onCameraTurn (double dy, double dx); - - /** - * 获取相对旋转角度 - */ - @NotNull Vector2d getRelativeRotation (); - - /** - * 从相机pick,使用默认距离 - *

    - * 默认距离是相机到玩家的距离加上配置中相机的pick距离 - *

    - * 结果可能是实体或方块 - */ - @NotNull HitResult pick (); - - /** - * 获取pick结果坐标 - *

    - * 使用默认距离 - */ - @NotNull Optional getPickPosition (); - - /** - * 获取相机视线落点坐标 - * - * @param pickRange 最大探测距离 - */ - @NotNull Optional getPickPosition (double pickRange); - - /** - * 根据实体视线探测所选方块或实体。 - *

    - * 当探测不到时,返回的是{@link HitResult.Type#MISS}类型。坐标将为探测终点 - * - * @param pickRange 探测距离限制 - */ - @NotNull HitResult pick (double pickRange); - - /** - * 根据实体的视线确定所选实体 - *

    - * 如果探测不到就返回空值 - * - * @param pickRange 探测距离 - */ - @NotNull Optional pickEntity (double pickRange); - - /** - * 根据实体的视线确定所选方块。 - *

    - * 瞄准时会忽略草,因为使用的过滤器是 {@link ClipContext.Block#COLLIDER} - *

    - * 非瞄准时会包括所有方块,因为过滤器是 {@link ClipContext.Block#OUTLINE} - *

    - * 当目标无限远时,返回的是{@link HitResult.Type#MISS}类型,坐标将为探测终点,即 探测起点 + 视线向量.normalize(探测距离) - * - * @param pickRange 探测距离 - */ - @NotNull BlockHitResult pickBlock (double pickRange); - - /** - * 相机是否正在注视某个实体(无视其他实体或方块) - */ - boolean isLookingAt (@NotNull Entity entity); - - /** - * 预测玩家可能想要射击的目标实体 - *

    - * TODO 预测不够准确 - */ - @NotNull Optional predictTargetEntity (); -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/api/core/EntityAgent.java b/common/src/main/java/net/leawind/mc/thirdperson/api/core/EntityAgent.java deleted file mode 100644 index 590f1135..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/api/core/EntityAgent.java +++ /dev/null @@ -1,160 +0,0 @@ -package net.leawind.mc.thirdperson.api.core; - - -import net.leawind.mc.thirdperson.api.core.rotation.SmoothType; -import net.leawind.mc.thirdperson.impl.core.EntityAgentImpl; -import net.leawind.mc.thirdperson.impl.core.rotation.RotateTarget; -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.leawind.mc.util.math.vector.api.Vector3d; -import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.world.entity.Entity; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -/** - * 相机附着的实体 - *

    - * 当操控玩家时,玩家的旋转由本类接管。 - */ -public interface EntityAgent { - @Contract("_ -> new") - static @NotNull EntityAgent create (@NotNull Minecraft mc) { - return new EntityAgentImpl(mc); - } - - /** - * 相机实体 {@link Minecraft#cameraEntity} 是否已经存在 - */ - boolean isCameraEntityExist (); - - /** - * 重置各种属性 - *

    - * 当初始化或进入第三人称时调用 - */ - void reset (); - - /** - * 设置旋转目标 - */ - void setRotateTarget (@NotNull RotateTarget rotateTarget); - - @NotNull RotateTarget getRotateTarget (); - - /** - * 设置平滑类型 - *

    - * 在 clientTick 和 renderTick 中要根据平滑类型采用不同的处理方式 - */ - void setRotationSmoothType (@NotNull SmoothType smoothType); - - @NotNull SmoothType getRotationSmoothType (); - - /** - * 设置平滑转向的半衰期 - */ - void setSmoothRotationHalflife (double halflife); - - /** - * 获取相机实体不透明度 - */ - float getSmoothOpacity (); - - /** - * @param period 相邻两次 render tick 的时间差,单位:s - */ - void onRenderTickPre (double now, double period, float partialTick); - - /** - * 在 client tick 之前 - *

    - * 通常频率固定为 20Hz - */ - void onClientTickPre (); - - /** - * 玩家当前是否在操控这个实体 - */ - boolean isControlled (); - - /** - * 获取相机附着的实体 - * - * @see EntityAgent#isCameraEntityExist - */ - @NotNull Entity getRawCameraEntity (); - - /** - * 获取玩家实体 - */ - @NotNull LocalPlayer getRawPlayerEntity (); - - /** - * 直接从相机实体获取眼睛坐标 - */ - @NotNull Vector3d getRawEyePosition (float partialTick); - - /** - * 直接从实体获取坐标 - */ - @NotNull Vector3d getRawPosition (float partialTick); - - /** - * 直接从实体获取朝向 - */ - @NotNull Vector2d getRawRotation (float partialTick); - - /** - * 设置实体朝向 - */ - void setRawRotation (@NotNull Vector2d rot); - - /** - * 获取平滑的眼睛坐标 - */ - @NotNull Vector3d getSmoothEyePosition (float partialTick); - - /** - * 如果平滑系数为0,则返回完全不平滑的值 - *

    - * 如果平滑系数不为0,则采用 EXP_LINEAR 平滑 - */ - @NotNull Vector3d getPossiblySmoothEyePosition (float partialTick); - - /** - * 实体是否在交互 - *

    - * 当控制玩家时,相当于是否按下了 使用|攻击|选取 键 - *

    - * 当附身其他实体时,另做判断 - */ - boolean isInterecting (); - - /** - * 实体是否在飞行 - */ - boolean isFallFlying (); - - /** - * 实体是否在奔跑 - */ - boolean isSprinting (); - - /** - * 根据实体的手持物品和使用状态判断是否在瞄准 - *

    - * 不考虑玩家按键 - */ - boolean isAiming (); - - /** - * 在上一个 clientTick 中是否在瞄准 - */ - boolean wasAiming (); - - /** - * 在上一个 clientTick 中是否在交互 - */ - boolean wasInterecting (); -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/api/core/rotation/SmoothType.java b/common/src/main/java/net/leawind/mc/thirdperson/api/core/rotation/SmoothType.java deleted file mode 100644 index 65c7c403..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/api/core/rotation/SmoothType.java +++ /dev/null @@ -1,50 +0,0 @@ -package net.leawind.mc.thirdperson.api.core.rotation; - - -/** - *

    平滑类型

    - *

    - * 在渲染中实现平滑效果可以有多种方式,主要区别在于 render tick 和 client tick 中的处理方式不同。 - *

    - * 注意此处的 client tick 可以是mc的 client tick,也可以是自定义的固定间隔的tick,但是需要在 render tick 中计算 partialTick。 - *

    - * partialTick 是一个浮点数,表示自上一次 client tick 以来经过的时间占 client tick 间隔的比例,通常用于线性插值 - */ -public enum SmoothType { - /** - *

    梆硬

    - * 不用担心抽搐问题,但完全没有平滑效果 - *

    render tick

    - * 更新目标值并立即应用。 既然不需要平滑效果,自然不需要更新平滑值了 - *

    client tick

    - */ - HARD, - /** - *

    线性插值

    - * 相当于半衰期为0的 {@link SmoothType#EXP_LINEAR} - *

    render tick

    - * 根据 partialTick 获取新值与旧值的线性插值 - *

    client tick

    - * 更新平滑值并记录旧值 - */ - LINEAR, - /** - *

    指数衰减

    - * 需要指定平滑系数(半衰期),无需记录旧值即可实现平滑效果。可以在任意时刻更新目标值 - *

    - * 缺点:对时间精度要求高。当目标值变化速度较快时,平滑值的变化速度可能不平滑 - *

    render tick

    - * 取值前需要更新平滑值,然后直接取平滑值。 - *

    client tick

    - */ - EXP, - /** - *

    指数衰减+线性插值

    - * 解决了 {@link SmoothType#EXP} 的不平滑问题。但是响应速度可能略迟钝。 - *

    render tick

    - * 取值时不直接取平滑值,而是取线性插值 - *

    client tick

    - * 更新目标值, 更新平滑值并记录旧值 - */ - EXP_LINEAR, -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/api/screen/ConfigScreenBuilder.java b/common/src/main/java/net/leawind/mc/thirdperson/api/screen/ConfigScreenBuilder.java deleted file mode 100644 index f6afbd47..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/api/screen/ConfigScreenBuilder.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.leawind.mc.thirdperson.api.screen; - - -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.thirdperson.impl.config.ConfigManagerImpl; -import net.minecraft.client.gui.screens.Screen; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * 配置屏幕构建器 - * - * @see ConfigManagerImpl#getConfigScreen(Screen) - */ -public interface ConfigScreenBuilder { - /** - * 构建配置屏幕 - * - * @param config 配置实例 - * @param parent 父屏幕 - * @return 配置屏幕 - */ - @NotNull Screen build (@NotNull Config config, @Nullable Screen parent); -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/AbstractCameraOffsetMode.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/AbstractCameraOffsetMode.java deleted file mode 100644 index fab66b22..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/AbstractCameraOffsetMode.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.leawind.mc.thirdperson.impl.cameraoffset; - - -import net.leawind.mc.thirdperson.api.cameraoffset.CameraOffsetMode; -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.util.math.vector.api.Vector2d; -import org.jetbrains.annotations.NotNull; - -public abstract class AbstractCameraOffsetMode implements CameraOffsetMode { - protected final @NotNull Config config; - - public AbstractCameraOffsetMode (@NotNull Config config) { - this.config = config; - } - - @Override - public void setSide (boolean isCameraLeftOfPlayer) { - if (isCameraLeftOfPlayer ^ isCameraLeftOfPlayer()) { - toNextSide(); - setCentered(false); - } - } - - @Override - public void getOffsetRatio (@NotNull Vector2d v) { - if (isCentered()) { - v.set(0, getCenterOffsetRatio()); - } else { - getSideOffsetRatio(v); - } - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/CameraOffsetModeAiming.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/CameraOffsetModeAiming.java deleted file mode 100644 index 4b6deac4..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/CameraOffsetModeAiming.java +++ /dev/null @@ -1,84 +0,0 @@ -package net.leawind.mc.thirdperson.impl.cameraoffset; - - -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.util.math.LMath; -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.leawind.mc.util.math.vector.api.Vector3d; -import org.jetbrains.annotations.NotNull; - -public class CameraOffsetModeAiming extends AbstractCameraOffsetMode { - public CameraOffsetModeAiming (@NotNull Config config) { - super(config); - } - - @Override - public @NotNull Vector3d getEyeSmoothHalflife () { - return Vector3d.of(config.aiming_smooth_halflife_horizon, config.aiming_smooth_halflife_vertical, config.aiming_smooth_halflife_horizon); - } - - @Override - public double getDistanceSmoothHalflife () { - return config.aiming_distance_smooth_halflife; - } - - @Override - public @NotNull Vector2d getOffsetSmoothHalflife () { - return Vector2d.of(config.aiming_camera_offset_smooth_halflife); - } - - @Override - public double getMaxDistance () { - return config.aiming_max_distance; - } - - @Override - public void setMaxDistance (double distance) { - config.aiming_max_distance = distance; - } - - @Override - public boolean isCentered () { - return config.aiming_is_centered; - } - - @Override - public void setCentered (boolean isCentered) { - config.aiming_is_centered = isCentered; - } - - @Override - public boolean isCameraLeftOfPlayer () { - return config.aiming_offset_x > 0; - } - - @Override - public void toNextSide () { - if (isCentered()) { - setCentered(false); - } else { - config.aiming_offset_x = -config.aiming_offset_x; - } - } - - @Override - public void setSideOffsetRatio (@NotNull Vector2d v) { - config.aiming_offset_x = LMath.clamp(v.x(), -1, 1); - config.aiming_offset_y = LMath.clamp(v.y(), -1, 1); - } - - @Override - public double getCenterOffsetRatio () { - return config.aiming_offset_center; - } - - @Override - public void setCenterOffsetRatio (double offset) { - config.aiming_offset_center = LMath.clamp(offset, -1, 1); - } - - @Override - public @NotNull Vector2d getSideOffsetRatio (@NotNull Vector2d v) { - return v.set(config.aiming_offset_x, config.aiming_offset_y); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/CameraOffsetModeNormal.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/CameraOffsetModeNormal.java deleted file mode 100644 index 81f92461..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/CameraOffsetModeNormal.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.leawind.mc.thirdperson.impl.cameraoffset; - - -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.leawind.mc.util.math.vector.api.Vector3d; -import org.jetbrains.annotations.NotNull; - -public class CameraOffsetModeNormal extends AbstractCameraOffsetMode { - public CameraOffsetModeNormal (@NotNull Config config) { - super(config); - } - - @Override - public @NotNull Vector3d getEyeSmoothHalflife () { - return Vector3d.of(config.normal_smooth_halflife_horizon, config.normal_smooth_halflife_vertical, config.normal_smooth_halflife_horizon); - } - - @Override - public double getDistanceSmoothHalflife () { - return config.normal_distance_smooth_halflife; - } - - @Override - public @NotNull Vector2d getOffsetSmoothHalflife () { - return Vector2d.of(config.normal_camera_offset_smooth_halflife); - } - - @Override - public double getMaxDistance () { - return config.normal_max_distance; - } - - @Override - public void setMaxDistance (double distance) { - config.normal_max_distance = distance; - } - - @Override - public boolean isCentered () { - return config.normal_is_centered; - } - - @Override - public void setCentered (boolean isCentered) { - config.normal_is_centered = isCentered; - } - - @Override - public boolean isCameraLeftOfPlayer () { - return config.normal_offset_x > 0; - } - - @Override - public void toNextSide () { - if (isCentered()) { - setCentered(false); - } else { - config.normal_offset_x = -config.normal_offset_x; - } - } - - @Override - public void setSideOffsetRatio (@NotNull Vector2d v) { - config.normal_offset_x = v.x(); - config.normal_offset_y = v.y(); - } - - @Override - public double getCenterOffsetRatio () { - return config.normal_offset_center; - } - - @Override - public void setCenterOffsetRatio (double offset) { - config.normal_offset_center = offset; - } - - @Override - public @NotNull Vector2d getSideOffsetRatio (@NotNull Vector2d v) { - return v.set(config.normal_offset_x, config.normal_offset_y); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/CameraOffsetSchemeImpl.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/CameraOffsetSchemeImpl.java deleted file mode 100644 index 3abae4df..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/cameraoffset/CameraOffsetSchemeImpl.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.leawind.mc.thirdperson.impl.cameraoffset; - - -import net.leawind.mc.thirdperson.api.cameraoffset.CameraOffsetMode; -import net.leawind.mc.thirdperson.api.cameraoffset.CameraOffsetScheme; -import net.leawind.mc.thirdperson.api.config.Config; -import org.jetbrains.annotations.NotNull; - -public class CameraOffsetSchemeImpl implements CameraOffsetScheme { - private final @NotNull CameraOffsetMode normalMode; - private final @NotNull CameraOffsetMode aimingMode; - private boolean isAiming = false; - - public CameraOffsetSchemeImpl (@NotNull Config config) { - normalMode = new CameraOffsetModeNormal(config); - aimingMode = new CameraOffsetModeAiming(config); - } - - @Override - public @NotNull CameraOffsetMode getMode () { - return isAiming() ? aimingMode: normalMode; - } - - @Override - public @NotNull CameraOffsetMode getAnotherMode () { - return isAiming() ? normalMode: aimingMode; - } - - @Override - public void setSide (double side) { - setSide(side > 0); - } - - @Override - public void setSide (boolean isCameraLeftOfPlayer) { - aimingMode.setSide(isCameraLeftOfPlayer); - normalMode.setSide(isCameraLeftOfPlayer); - } - - @Override - public void toNextSide () { - aimingMode.toNextSide(); - normalMode.toNextSide(); - } - - @Override - public boolean isCentered () { - return getMode().isCentered(); - } - - @Override - public void setCentered (boolean isCentered) { - getMode().setCentered(isCentered); - getAnotherMode().setCentered(isCentered); - } - - @Override - public boolean isAiming () { - return isAiming; - } - - @Override - public void setAiming (boolean aiming) { - isAiming = aiming; - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/config/ConfigImpl.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/config/ConfigImpl.java deleted file mode 100644 index 882fcb56..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/config/ConfigImpl.java +++ /dev/null @@ -1,76 +0,0 @@ -package net.leawind.mc.thirdperson.impl.config; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.api.cameraoffset.CameraOffsetScheme; -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.util.itempattern.ItemPattern; -import net.leawind.mc.util.math.monolist.MonoList; -import net.leawind.mc.util.math.monolist.StaticMonoList; -import org.jetbrains.annotations.NotNull; - -import java.util.HashSet; -import java.util.Set; - -public class ConfigImpl extends Config { - private final @NotNull CameraOffsetScheme cameraOffsetScheme = CameraOffsetScheme.of(this); - private final @NotNull Set holdToAimItemPatterns = new HashSet<>(); - private final @NotNull Set useToAimItemPatterns = new HashSet<>(); - public final @NotNull Set useToFirstPersonItemPatterns = new HashSet<>(); - private MonoList distanceMonoList; - - public ConfigImpl () { - super(); - update(); - } - - @Override - public void update () { - updateDistancesMonoList(); - updateItemPatterns(); - } - - @Override - public void updateDistancesMonoList () { - distanceMonoList = StaticMonoList.of(available_distance_count, camera_distance_min, camera_distance_max, i -> i * i, Math::sqrt); - } - - @Override - public void updateItemPatterns () { - int count; - holdToAimItemPatterns.clear(); - count = ItemPattern.addToSet(holdToAimItemPatterns, hold_to_aim_item_pattern_expressions); - ThirdPerson.LOGGER.info("Loaded {} hold_to_aim item patterns from configuration", count); - useToAimItemPatterns.clear(); - count = ItemPattern.addToSet(useToAimItemPatterns, use_to_aim_item_pattern_expressions); - ThirdPerson.LOGGER.info("Loaded {} use_to_aim item patterns from configuration", count); - useToFirstPersonItemPatterns.clear(); - count = ItemPattern.addToSet(useToFirstPersonItemPatterns, use_to_first_person_pattern_expressions); - ThirdPerson.LOGGER.info("Loaded {} use_to_first_person item patterns from configuration", count); - } - - @Override - public @NotNull Set getHoldToAimItemPatterns () { - return holdToAimItemPatterns; - } - - @Override - public @NotNull Set getUseToAimItemPatterns () { - return useToAimItemPatterns; - } - - @Override - public @NotNull Set getUseToFirstPersonItemPatterns () { - return useToFirstPersonItemPatterns; - } - - @Override - public @NotNull CameraOffsetScheme getCameraOffsetScheme () { - return cameraOffsetScheme; - } - - @Override - public @NotNull MonoList getDistanceMonoList () { - return distanceMonoList; - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/config/ConfigManagerImpl.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/config/ConfigManagerImpl.java deleted file mode 100644 index e390c7a2..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/config/ConfigManagerImpl.java +++ /dev/null @@ -1,105 +0,0 @@ -package net.leawind.mc.thirdperson.impl.config; - - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonConstants; -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.thirdperson.api.config.ConfigManager; -import net.leawind.mc.thirdperson.api.screen.ConfigScreenBuilder; -import net.leawind.mc.thirdperson.impl.screen.ConfigScreenBuilders; -import net.minecraft.client.gui.screens.Screen; -import org.apache.commons.io.FileUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.Optional; -import java.util.Timer; -import java.util.TimerTask; - -public class ConfigManagerImpl implements ConfigManager { - private final @NotNull Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting().disableHtmlEscaping().create(); - private @NotNull Config config = Config.create(); - private final Timer lazySaveTimer = new Timer(); - private boolean isLazySaveScheduled = false; - - public ConfigManagerImpl () { - } - - @Override - public void tryLoad () { - try { - assert ThirdPersonConstants.CONFIG_FILE.getParentFile().mkdirs(); - if (ThirdPersonConstants.CONFIG_FILE.exists()) { - load(); - ThirdPerson.LOGGER.info("Config is loaded from {}", ThirdPersonConstants.CONFIG_FILE); - } else { - ThirdPerson.LOGGER.info("Config not found, creating one."); - trySave(); - } - } catch (IOException e) { - ThirdPerson.LOGGER.error("Failed to load config.", e); - } - config.update(); - } - - @Override - public void trySave () { - try { - save(); - ThirdPerson.LOGGER.info("Config is saved."); - } catch (IOException e) { - ThirdPerson.LOGGER.error("Failed to save config.", e); - } - config.update(); - } - - @Override - public void lazySave () { - if (!isLazySaveScheduled) { - isLazySaveScheduled = true; - lazySaveTimer.schedule(new TimerTask() { - @Override - public void run () { - trySave(); - isLazySaveScheduled = false; - } - }, ThirdPersonConstants.CONFIG_LAZY_SAVE_DELAY); - } - } - - @Override - public void load () throws IOException { - config = GSON.fromJson(Files.readString(ThirdPersonConstants.CONFIG_FILE.toPath(), StandardCharsets.UTF_8), Config.IMPL); - } - - @Override - public void save () throws IOException { - FileUtils.writeStringToFile(ThirdPersonConstants.CONFIG_FILE, GSON.toJson(this.config), StandardCharsets.UTF_8); - } - - @Override - public @Nullable Screen getConfigScreen (@Nullable Screen parent) { - Optional builder = ConfigScreenBuilders.getBuilder(); - if (builder.isPresent()) { - return builder.get().build(config, parent); - } else { - ThirdPerson.LOGGER.warn("No config screen builder available."); - return null; - } - } - - @Override - public boolean isScreenAvailable () { - return !ConfigScreenBuilders.getAvailableBuidlers().isEmpty(); - } - - @Override - public @NotNull Config getConfig () { - return this.config; - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/core/AimingTargetComparator.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/core/AimingTargetComparator.java deleted file mode 100644 index cecaeebf..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/core/AimingTargetComparator.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.leawind.mc.thirdperson.impl.core; - - -import net.leawind.mc.util.math.LMath; -import net.leawind.mc.util.math.vector.api.Vector3d; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.phys.Vec3; - -import java.util.Comparator; - -/** - * 在预测目标实体时,判断两个实体的优先级 - * - * @see CameraAgentImpl#predictTargetEntity() - */ -public record AimingTargetComparator(Vec3 pos, Vector3d viewVector) implements Comparator { - @Override - public int compare (Entity e1, Entity e2) { - return (int)Math.signum(getCost(e1) - getCost(e2)); - } - - /** - * 计算一个目标实体的代价,值越低越优先 - */ - public double getCost (Entity entity) { - Vec3 entityPos = entity.getPosition(1); - Vector3d vectorToTarget = LMath.toVector3d(entityPos.subtract(pos)).normalize(); - double dist = pos.distanceTo(entityPos); - double angleRadian = Math.acos(viewVector.dot(vectorToTarget)); - double angleDegrees = Math.toDegrees(angleRadian); - return Math.pow(dist, 2) * Math.pow(angleDegrees, 2.5); - // return dist * 2 + angleDegrees * 5; - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/core/CameraAgentImpl.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/core/CameraAgentImpl.java deleted file mode 100644 index 90a73b38..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/core/CameraAgentImpl.java +++ /dev/null @@ -1,378 +0,0 @@ -package net.leawind.mc.thirdperson.impl.core; - - -import com.google.common.collect.Lists; -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonConstants; -import net.leawind.mc.thirdperson.ThirdPersonStatus; -import net.leawind.mc.thirdperson.api.cameraoffset.CameraOffsetMode; -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.thirdperson.api.core.CameraAgent; -import net.leawind.mc.thirdperson.mixin.CameraInvoker; -import net.leawind.mc.thirdperson.mixin.ClientLevelInvoker; -import net.leawind.mc.util.annotations.VersionSensitive; -import net.leawind.mc.util.math.LMath; -import net.leawind.mc.util.math.smoothvalue.ExpSmoothDouble; -import net.leawind.mc.util.math.smoothvalue.ExpSmoothVector2d; -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.leawind.mc.util.math.vector.api.Vector3d; -import net.minecraft.client.Camera; -import net.minecraft.client.CameraType; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.core.Direction; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.projectile.ProjectileUtil; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.ClipContext; -import net.minecraft.world.level.entity.LevelEntityGetter; -import net.minecraft.world.phys.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -public class CameraAgentImpl implements CameraAgent { - private final @NotNull Minecraft minecraft; - private final @NotNull Camera fakeCamera = new Camera(); - /** - * 上次玩家操控转动视角的时间 - */ - private double lastCameraTurnTimeStamp = 0; - private final @NotNull Vector2d relativeRotation = Vector2d.of(0); - /** - * 相机偏移量 - */ - private final @NotNull ExpSmoothVector2d smoothOffsetRatio; - /** - * 虚相机到平滑眼睛的距离 - */ - private final @NotNull ExpSmoothDouble smoothDistanceToEye; - private @Nullable BlockGetter blockGetter; - - public CameraAgentImpl (@NotNull Minecraft minecraft) { - this.minecraft = minecraft; - smoothOffsetRatio = new ExpSmoothVector2d(); - smoothDistanceToEye = new ExpSmoothDouble(); - } - - @Override - public void reset () { - ThirdPerson.LOGGER.debug("Reset CameraAgent"); - smoothOffsetRatio.setValue(0, 0); - smoothDistanceToEye.set(ThirdPerson.getConfig().getDistanceMonoList().get(0)); - if (ThirdPerson.ENTITY_AGENT.isCameraEntityExist()) { - Entity entity = ThirdPerson.ENTITY_AGENT.getRawCameraEntity(); - relativeRotation.set(-entity.getViewXRot(ThirdPersonStatus.lastPartialTick), entity.getViewYRot(ThirdPersonStatus.lastPartialTick) - 180); - } - } - - @Override - public void setBlockGetter (@NotNull BlockGetter blockGetter) { - this.blockGetter = blockGetter; - } - - @Override - public void onRenderTickPre (double now, double period, float partialTick) { - if (!minecraft.isPaused()) { - // 平滑更新距离 - updateSmoothVirtualDistance(period); - // 平滑更新相机偏移量 - updateSmoothOffsetRatio(period); - // - if (ThirdPersonStatus.shouldCameraTurnWithEntity()) { - // 将相机朝向与相机实体朝向同步 - Vector2d rot = ThirdPerson.ENTITY_AGENT.getRawRotation(partialTick); - relativeRotation.set(-rot.x(), rot.y() - 180); - } - } - } - - @Override - public void onCameraSetup () { - updateFakeCameraRotationPosition(); - preventThroughWall(); - updateFakeCameraRotationPosition(); - applyCamera(); - } - - @Override - public void onClientTickPre () { - ThirdPersonStatus.isTransitioningToFirstPerson = false; - boolean isTargetThirdPerson = ThirdPerson.getConfig().is_third_person_mode && !ThirdPersonStatus.isTemporaryFirstPerson; - if (isTargetThirdPerson) { - // 目标是第三人称,那就直接以第三人称渲染 - ThirdPerson.mc.options.setCameraType(CameraType.THIRD_PERSON_BACK); - ThirdPerson.mc.gameRenderer.checkEntityPostEffect(null); - } else if (!ThirdPerson.mc.options.getCameraType().isFirstPerson()) { - // 目标是第一人称,但是相机当前以第三人称渲染,那么开始过渡 - ThirdPersonStatus.isTransitioningToFirstPerson = true; - } - if (ThirdPersonStatus.isTransitioningToFirstPerson) { - // 正在从第三人称过渡到第一人称 - if (smoothDistanceToEye.get() < ThirdPersonConstants.FIRST_PERSON_TRANSITION_END_THRESHOLD) { - // 距离足够近,结束过渡 - ThirdPersonStatus.isTransitioningToFirstPerson = false; - ThirdPerson.mc.options.setCameraType(CameraType.FIRST_PERSON); - ThirdPerson.mc.gameRenderer.checkEntityPostEffect(ThirdPerson.mc.getCameraEntity()); - } - } - } - - public @NotNull Camera getRawCamera () { - return Objects.requireNonNull(Minecraft.getInstance().gameRenderer.getMainCamera()); - } - - @Override - public @NotNull Vector3d getRawCameraPosition () { - return LMath.toVector3d(getRawCamera().getPosition()); - } - - @Override - public @NotNull Vector2d getRotation () { - return Vector2d.of(-relativeRotation.x(), relativeRotation.y() + 180); - } - - @Override - public @NotNull Camera getFakeCamera () { - return fakeCamera; - } - - @Override - public void onCameraTurn (double dy, double dx) { - Config config = ThirdPerson.getConfig(); - if (config.is_mod_enable && !ThirdPersonStatus.isAdjustingCameraOffset()) { - dy *= 0.15; - dx *= config.lock_camera_pitch_angle ? 0: -0.15; - if (dy != 0 || dx != 0) { - lastCameraTurnTimeStamp = System.currentTimeMillis() / 1000D; - double rx = getRelativeRotation().x() + dx; - double ry = getRelativeRotation().y() + dy; - rx = LMath.clamp(rx, -ThirdPersonConstants.CAMERA_PITCH_DEGREE_LIMIT, ThirdPersonConstants.CAMERA_PITCH_DEGREE_LIMIT); - relativeRotation.set(rx, ry % 360f); - } - } - } - - @Override - public @NotNull Vector2d getRelativeRotation () { - return relativeRotation; - } - - @Override - public @NotNull HitResult pick () { - return pick(smoothDistanceToEye.get() + ThirdPerson.getConfig().camera_ray_trace_length); - } - - @Override - public @NotNull Optional getPickPosition () { - return getPickPosition(smoothDistanceToEye.get() + ThirdPerson.getConfig().camera_ray_trace_length); - } - - @Override - public @NotNull Optional getPickPosition (double pickRange) { - HitResult hitResult = pick(pickRange); - return Optional.ofNullable(hitResult.getType() == HitResult.Type.MISS ? null: LMath.toVector3d(hitResult.getLocation())); - } - - @Override - @VersionSensitive - public @NotNull HitResult pick (double pickRange) { - Camera camera = getRawCamera(); - Vec3 cameraPos = camera.getPosition(); - Optional entityHitResult = pickEntity(pickRange).map(hr -> hr); - HitResult blockHitResult = pickBlock(pickRange); - double blockHitResultDistance = blockHitResult.getLocation().distanceTo(cameraPos); - return entityHitResult.filter(hitResult -> !(blockHitResultDistance < hitResult.getLocation().distanceTo(cameraPos))).orElse(blockHitResult); - } - - @Override - @VersionSensitive - public @NotNull Optional pickEntity (double pickRange) { - if (!ThirdPerson.ENTITY_AGENT.isCameraEntityExist()) { - return Optional.empty(); - } - Entity cameraEntity = ThirdPerson.ENTITY_AGENT.getRawCameraEntity(); - Camera camera = getRawCamera(); - Vec3 viewVector = new Vec3(camera.getLookVector()); - Vec3 pickEnd = viewVector.scale(pickRange).add(camera.getPosition()); - AABB aabb = cameraEntity.getBoundingBox().expandTowards(viewVector.scale(pickRange)).inflate(1.0D, 1.0D, 1.0D); - aabb = aabb.move(cameraEntity.getEyePosition(1).vectorTo(camera.getPosition())); - return Optional.ofNullable(ProjectileUtil.getEntityHitResult(cameraEntity, camera.getPosition(), pickEnd, aabb, (Entity target) -> !target.isSpectator() && target.isPickable(), pickRange)); - } - - @Override - @VersionSensitive - public @NotNull BlockHitResult pickBlock (double pickRange) { - Camera camera = getRawCamera(); - Vec3 pickStart = camera.getPosition(); - Vec3 viewVector = new Vec3(camera.getLookVector()); - Vec3 pickEnd = viewVector.scale(pickRange).add(pickStart); - Entity cameraEntity = ThirdPerson.ENTITY_AGENT.getRawCameraEntity(); - return cameraEntity.level.clip(new ClipContext(pickStart, pickEnd, ThirdPerson.ENTITY_AGENT.wasAiming() ? ClipContext.Block.COLLIDER: ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, cameraEntity)); - } - - @VersionSensitive - @Override - public boolean isLookingAt (@NotNull Entity entity) { - Vec3 from = getRawCamera().getPosition(); - Vec3 to = from.add(new Vec3(getRawCamera().getLookVector()).scale(ThirdPerson.getConfig().camera_ray_trace_length)); - AABB aabb = entity.getBoundingBox(); - return aabb.contains(from) || aabb.clip(from, to).isPresent(); - } - - /** - * @see AimingTargetComparator#getCost(Entity) - */ - @Override - public @NotNull Optional predictTargetEntity () { - Config config = ThirdPerson.getConfig(); - // 候选目标实体 - List candidateTargets = Lists.newArrayList(); - Vec3 cameraPos = getRawCamera().getPosition(); - Vector2d cameraRot = getRotation(); - Vector3d cameraViewVector = LMath.directionFromRotationDegree(cameraRot).normalize(); - if (ThirdPerson.ENTITY_AGENT.isControlled()) { - Entity playerEntity = ThirdPerson.ENTITY_AGENT.getRawPlayerEntity(); - ClientLevel clientLevel = (ClientLevel)playerEntity.getLevel(); - LevelEntityGetter entityGetter = ((ClientLevelInvoker)clientLevel).invokeGetEntityGetter(); - for (Entity target: entityGetter.getAll()) { - if (!(target instanceof LivingEntity)) { - continue; - } - double distance = target.distanceTo(playerEntity); - // 排除距离太近和太远的 - if (distance < 2 || distance > config.camera_ray_trace_length) { - continue; - } - if (!target.is(playerEntity)) { - Vec3 targetPos = target.getPosition(ThirdPersonStatus.lastPartialTick); - Vector3d bottomY = LMath.toVector3d(targetPos.with(Direction.Axis.Y, target.getBoundingBox().minY)); - Vector3d vectorToBottom = bottomY.copy().sub(ThirdPerson.ENTITY_AGENT.getRawEyePosition(ThirdPersonStatus.lastPartialTick)); - if (LMath.rotationDegreeFromDirection(vectorToBottom).x() < cameraRot.x()) { - continue; - } - Vector3d vectorToTarget = LMath.toVector3d(targetPos.subtract(cameraPos)).normalize(); - double angleRadian = Math.acos(cameraViewVector.dot(vectorToTarget)); - if (Math.toDegrees(angleRadian) < ThirdPersonConstants.TARGET_PREDICTION_DEGREES_LIMIT) { - candidateTargets.add(target); - } - } - } - } - if (!candidateTargets.isEmpty()) { - candidateTargets.sort(new AimingTargetComparator(cameraPos, cameraViewVector)); - // candidateTargets.get(0).setYHeadRot((float)(System.currentTimeMillis() / 2d % 360)); // debug - return Optional.of(candidateTargets.get(0)); - } - return Optional.empty(); - } - - /** - * 根据角度、距离、偏移量计算假相机实际朝向和位置 - */ - private void updateFakeCameraRotationPosition () { - Minecraft mc = ThirdPerson.mc; - // 宽高比 - double aspectRatio = (double)mc.getWindow().getWidth() / mc.getWindow().getHeight(); - // 垂直视野角度一半(弧度制) - double verticalRadianHalf = Math.toRadians(mc.options.fov().get()) / 2; - // 成像平面宽高 - double heightHalf = Math.tan(verticalRadianHalf) * ThirdPersonConstants.NEAR_PLANE_DISTANCE; - double widthHalf = aspectRatio * heightHalf; - // // 水平视野角度一半(弧度制) - // double horizonalRadianHalf = Math.atan(widthHalf / NEAR_PLANE_DISTANCE); - // 平滑值 - Vector2d smoothOffsetRatioValue = smoothOffsetRatio.get(); - double smoothVirtualDistanceValue = smoothDistanceToEye.get(); - // 偏移量 - double upOffset = smoothOffsetRatioValue.y() * smoothVirtualDistanceValue * Math.tan(verticalRadianHalf); - double leftOffset = smoothOffsetRatioValue.x() * smoothVirtualDistanceValue * widthHalf / ThirdPersonConstants.NEAR_PLANE_DISTANCE; - // 没有偏移的情况下相机位置 - Vector3d positionWithoutOffset = calculatePositionWithoutOffset(); - // 应用到假相机 - ((CameraInvoker)fakeCamera).invokeSetRotation((float)(relativeRotation.y() + 180), (float)-relativeRotation.x()); - ((CameraInvoker)fakeCamera).invokeSetPosition(LMath.toVec3(positionWithoutOffset)); - ((CameraInvoker)fakeCamera).invokeMove(0, upOffset, leftOffset); - } - - /** - * 为防止穿墙,重新计算 {@link CameraAgentImpl#smoothDistanceToEye} 的值 - * - * @see Camera#getMaxZoom(double) - */ - private void preventThroughWall () { - // 防止穿墙 - Vec3 cameraPosition = fakeCamera.getPosition(); - Vec3 smoothEyePosition = LMath.toVec3(ThirdPerson.ENTITY_AGENT.getSmoothEyePosition(ThirdPersonStatus.lastPartialTick)); - Vec3 smoothEyeToCamera = smoothEyePosition.vectorTo(cameraPosition); - double initDistance = smoothEyeToCamera.length(); - double minDistance = initDistance; - assert blockGetter != null; - for (int i = 0; i < 8; ++i) { - double offsetX = (i & 1) * 2 - 1; - double offsetY = (i >> 1 & 1) * 2 - 1; - double offsetZ = (i >> 2 & 1) * 2 - 1; - offsetX *= ThirdPersonConstants.CAMERA_THROUGH_WALL_DETECTION; - offsetY *= ThirdPersonConstants.CAMERA_THROUGH_WALL_DETECTION; - offsetZ *= ThirdPersonConstants.CAMERA_THROUGH_WALL_DETECTION; - Vec3 pickStart = smoothEyePosition.add(offsetX, offsetY, offsetZ); - Vec3 pickEnd = pickStart.add(smoothEyeToCamera); - HitResult hitResult = blockGetter.clip(new ClipContext(pickStart, pickEnd, ThirdPersonConstants.CAMERA_OBSTACLE_BLOCK_SHAPE_GETTER, ClipContext.Fluid.NONE, ThirdPerson.ENTITY_AGENT.getRawCameraEntity())); - if (hitResult.getType() != HitResult.Type.MISS) { - minDistance = Math.min(minDistance, hitResult.getLocation().distanceTo(pickStart)); - } - } - smoothDistanceToEye.setValue(smoothDistanceToEye.get() * minDistance / initDistance); - } - - /** - * 将假相机的朝向和位置应用到真相机上 - */ - private void applyCamera () { - Camera camera = getRawCamera(); - ((CameraInvoker)camera).invokeSetRotation(fakeCamera.getYRot(), fakeCamera.getXRot()); - ((CameraInvoker)camera).invokeSetPosition(fakeCamera.getPosition()); - } - - private @NotNull Vector3d calculatePositionWithoutOffset () { - return ThirdPerson.ENTITY_AGENT.getPossiblySmoothEyePosition(ThirdPersonStatus.lastPartialTick).add(LMath.directionFromRotationDegree(relativeRotation).mul(smoothDistanceToEye.get())); - } - - private void updateSmoothVirtualDistance (double period) { - Config config = ThirdPerson.getConfig(); - boolean isAdjusting = ThirdPersonStatus.isAdjustingCameraDistance(); - CameraOffsetMode mode = config.getCameraOffsetScheme().getMode(); - if (ThirdPersonStatus.isTransitioningToFirstPerson) { - smoothDistanceToEye.setHalflife(config.adjusting_distance_smooth_halflife * ThirdPersonConstants.TRANSITION_HALFLIFE_MULTIPLIER); - smoothDistanceToEye.setTarget(0); - } else { - smoothDistanceToEye.setHalflife(isAdjusting ? config.adjusting_distance_smooth_halflife: mode.getDistanceSmoothHalflife()); - smoothDistanceToEye.setTarget(mode.getMaxDistance()); - } - smoothDistanceToEye.update(period); - // 如果是非瞄准模式下,且距离过远则强行放回去 - if (!config.getCameraOffsetScheme().isAiming() && !isAdjusting) { - smoothDistanceToEye.set(Math.min(mode.getMaxDistance(), smoothDistanceToEye.get())); - } - } - - private void updateSmoothOffsetRatio (double period) { - Config config = ThirdPerson.getConfig(); - CameraOffsetMode mode = config.getCameraOffsetScheme().getMode(); - if (ThirdPersonStatus.isAdjustingCameraOffset()) { - smoothOffsetRatio.setHalflife(config.adjusting_camera_offset_smooth_halflife); - } else { - smoothOffsetRatio.setHalflife(mode.getOffsetSmoothHalflife()); - } - if (config.center_offset_when_flying && ThirdPerson.ENTITY_AGENT.isFallFlying()) { - smoothOffsetRatio.setTarget(0, 0); - } else { - mode.getOffsetRatio(smoothOffsetRatio.target); - } - smoothOffsetRatio.update(period); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/core/EntityAgentImpl.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/core/EntityAgentImpl.java deleted file mode 100644 index b5053377..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/core/EntityAgentImpl.java +++ /dev/null @@ -1,331 +0,0 @@ -package net.leawind.mc.thirdperson.impl.core; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonConstants; -import net.leawind.mc.thirdperson.ThirdPersonResources; -import net.leawind.mc.thirdperson.ThirdPersonStatus; -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.thirdperson.api.core.EntityAgent; -import net.leawind.mc.thirdperson.api.core.rotation.SmoothType; -import net.leawind.mc.thirdperson.impl.core.rotation.RotateStrategy; -import net.leawind.mc.thirdperson.impl.core.rotation.RotateTarget; -import net.leawind.mc.util.annotations.VersionSensitive; -import net.leawind.mc.util.itempattern.ItemPattern; -import net.leawind.mc.util.math.LMath; -import net.leawind.mc.util.math.decisionmap.api.DecisionMap; -import net.leawind.mc.util.math.smoothvalue.ExpSmoothDouble; -import net.leawind.mc.util.math.smoothvalue.ExpSmoothRotation; -import net.leawind.mc.util.math.smoothvalue.ExpSmoothVector3d; -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.leawind.mc.util.math.vector.api.Vector3d; -import net.minecraft.client.Minecraft; -import net.minecraft.client.Options; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.HumanoidArm; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.Items; -import org.apache.logging.log4j.util.PerformanceSensitive; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -public class EntityAgentImpl implements EntityAgent { - private final Minecraft minecraft; - private final ExpSmoothVector3d smoothEyePosition; - private final ExpSmoothRotation smoothRotation = ExpSmoothRotation.createWithHalflife(0.5); - private final ExpSmoothDouble smoothOpacity; - private final DecisionMap rotateDecisionMap = DecisionMap.of(RotateStrategy.class); - private @NotNull RotateTarget rotateTarget = RotateTarget.NONE; - private @NotNull SmoothType smoothRotationType = SmoothType.EXP_LINEAR; - /** - * 在上一个 client tick 中的 isAiming() 的值 - */ - private boolean wasAiming = false; - /** - * 上一个 client tick 中的 isInterecting 的值 - */ - private boolean wasInterecting = false; - - public EntityAgentImpl (@NotNull Minecraft minecraft) { - this.minecraft = minecraft; - { - smoothEyePosition = new ExpSmoothVector3d(); - } - { - smoothOpacity = new ExpSmoothDouble(); - smoothOpacity.set(1d); - } - ThirdPerson.LOGGER.debug(rotateDecisionMap.toString()); - } - - @Override - public boolean isCameraEntityExist () { - return minecraft.cameraEntity != null; - } - - @Override - public void reset () { - ThirdPerson.LOGGER.debug("Reset EntityAgent"); - ThirdPersonStatus.lastPartialTick = minecraft.getFrameTime(); - if (isCameraEntityExist()) { - smoothEyePosition.set(getRawEyePosition(ThirdPersonStatus.lastPartialTick)); - } - smoothOpacity.set(0d); - wasAiming = false; - wasInterecting = false; - } - - @Override - public void setRotateTarget (@NotNull RotateTarget rotateTarget) { - this.rotateTarget = rotateTarget; - } - - @Override - @NotNull - public RotateTarget getRotateTarget () { - return rotateTarget; - } - - @Override - public void setRotationSmoothType (@NotNull SmoothType smoothType) { - smoothRotationType = smoothType; - } - - @Override - public @NotNull SmoothType getRotationSmoothType () { - return smoothRotationType; - } - - @Override - public void setSmoothRotationHalflife (double halflife) { - smoothRotation.setHalflife(halflife); - } - - @Override - public float getSmoothOpacity () { - return smoothOpacity.get(ThirdPersonStatus.lastPartialTick).floatValue(); - } - - @PerformanceSensitive - @Override - public void onRenderTickPre (double now, double period, float partialTick) { - if (!isControlled()) { - return; - } - if (!ThirdPersonStatus.shouldCameraTurnWithEntity()) { - Vector2d targetRotation = rotateTarget.getRotation(); - smoothRotation.setTarget(targetRotation); - switch (smoothRotationType) { - case HARD -> setRawRotation(targetRotation); - case LINEAR, EXP_LINEAR -> setRawRotation(smoothRotation.get(partialTick)); - case EXP -> { - smoothRotation.update(period); - setRawRotation(smoothRotation.get()); - } - } - } - } - - @Override - public void onClientTickPre () { - final double period = 0.05; - Config config = ThirdPerson.getConfig(); - wasInterecting = isInterecting(); - wasAiming = isAiming(); - config.getCameraOffsetScheme().setAiming(wasAiming()); - updateRotateStrategy(); - updateSmoothOpacity(period, 1); - smoothRotation.update(period); - { - Vector3d eyePosition = getRawEyePosition(1); - { - final Vector3d halflife; - if (ThirdPersonStatus.isTransitioningToFirstPerson) { - halflife = Vector3d.of(0); - } else if (isFallFlying()) { - halflife = Vector3d.of(config.flying_smooth_halflife); - } else { - halflife = config.getCameraOffsetScheme().getMode().getEyeSmoothHalflife(); - } - final double dist = getSmoothEyePosition(1).distance(ThirdPerson.CAMERA_AGENT.getRawCameraPosition()); - halflife.mul(dist * ThirdPersonConstants.EYE_HALFLIFE_MULTIPLIER); - smoothEyePosition.setHalflife(halflife); - } - smoothEyePosition.setTarget(eyePosition); - smoothEyePosition.update(period); - } - switch (smoothRotationType) { - case HARD, EXP -> { - } - case LINEAR, EXP_LINEAR -> { - smoothRotation.setTarget(rotateTarget.getRotation()); - smoothRotation.update(period); - } - } - } - - /** - * 立即设置玩家朝向 - *

    - * 同时修改原始玩家实体的朝向和旧朝向 - */ - @Override - public void setRawRotation (@NotNull Vector2d rot) { - Entity entity = getRawPlayerEntity(); - entity.setYRot(entity.yRotO = (float)rot.y()); - entity.setXRot(entity.xRotO = (float)rot.x()); - } - - @Override - public boolean isControlled () { - return getRawPlayerEntity() == getRawCameraEntity(); - } - - @Override - public @NotNull Entity getRawCameraEntity () { - return Objects.requireNonNull(minecraft.cameraEntity); - } - - @Override - public @NotNull LocalPlayer getRawPlayerEntity () { - return Objects.requireNonNull(minecraft.player); - } - - @Override - public @NotNull Vector3d getRawEyePosition (float partialTick) { - return LMath.toVector3d(getRawCameraEntity().getEyePosition(partialTick)); - } - - @Override - public @NotNull Vector3d getRawPosition (float partialTick) { - return LMath.toVector3d(Objects.requireNonNull(getRawCameraEntity()).getPosition(partialTick)); - } - - @Override - @VersionSensitive - public @NotNull Vector2d getRawRotation (float partialTick) { - Entity entity = getRawCameraEntity(); - return Vector2d.of(entity.getViewXRot(partialTick), entity.getViewYRot(partialTick)); - } - - @Override - public @NotNull Vector3d getSmoothEyePosition (float partialTick) { - return smoothEyePosition.get(partialTick); - } - - @Override - public @NotNull Vector3d getPossiblySmoothEyePosition (float partialTick) { - Vector3d smoothEyePositionValue = smoothEyePosition.get(partialTick); - Vector3d rawEyePosition = LMath.toVector3d(getRawCameraEntity().getEyePosition(partialTick)); - Vector3d smoothFactor = smoothEyePosition.smoothFactor.copy(); - boolean isHorizontalZero = smoothFactor.x() * smoothFactor.z() == 0; - boolean isVerticalZero = smoothFactor.y() == 0; - if (isHorizontalZero || isVerticalZero) { - smoothEyePositionValue = Vector3d.of(isHorizontalZero ? rawEyePosition.x(): smoothEyePositionValue.x(),// - isVerticalZero ? rawEyePosition.y(): smoothEyePositionValue.y(),// - isHorizontalZero ? rawEyePosition.z(): smoothEyePositionValue.z()); - } - return smoothEyePositionValue; - } - - @Override - @VersionSensitive - public boolean isInterecting () { - if (isControlled()) { - Options options = minecraft.options; - return options.keyUse.isDown() || options.keyAttack.isDown() || options.keyPickItem.isDown(); - } else { - return getRawCameraEntity() instanceof LivingEntity livingEntity && livingEntity.isUsingItem(); - } - } - - @Override - @VersionSensitive - public boolean isFallFlying () { - return getRawCameraEntity() instanceof LivingEntity livingEntity && livingEntity.isFallFlying(); - } - - @Override - public boolean isSprinting () { - return getRawCameraEntity().isSprinting(); - } - - @Override - public boolean isAiming () { - Config config = ThirdPerson.getConfig(); - if (ThirdPersonStatus.doesPlayerWantToAim()) { - return true; - } - if (getRawCameraEntity() instanceof LivingEntity livingEntity) { - boolean shouldBeAiming = ItemPattern.anyMatch(livingEntity.getMainHandItem(), config.getHoldToAimItemPatterns(), ThirdPersonResources.itemPatternManager.holdToAimItemPatterns) || // - ItemPattern.anyMatch(livingEntity.getOffhandItem(), config.getHoldToAimItemPatterns(), ThirdPersonResources.itemPatternManager.holdToAimItemPatterns); - if (livingEntity.isUsingItem()) { - shouldBeAiming |= ItemPattern.anyMatch(livingEntity.getUseItem(), config.getUseToAimItemPatterns(), ThirdPersonResources.itemPatternManager.useToAimItemPatterns); - } - return shouldBeAiming; - } - return false; - } - - @Override - public boolean wasAiming () { - return wasAiming; - } - - @Override - public boolean wasInterecting () { - return wasInterecting; - } - - /** - * 更新旋转策略、平滑类型、平滑系数 - *

    - * 根据配置、游泳、飞行、瞄准等状态判断。 - */ - private void updateRotateStrategy () { - setSmoothRotationHalflife(rotateDecisionMap.remake()); - updateBodyRotation(); - } - - /** - * 脖子最多左右转85度 - * - * @see net.minecraft.client.renderer.entity.LivingEntityRenderer#render - */ - private void updateBodyRotation () { - // net.minecraft.client.renderer.entity.LivingEntityRenderer.render - Config config = ThirdPerson.getConfig(); - if (config.auto_turn_body_drawing_a_bow && ThirdPerson.ENTITY_AGENT.isControlled()) { - LocalPlayer player = getRawPlayerEntity(); - if (player.isUsingItem() && player.getUseItem().is(Items.BOW)) { - double k = player.getUsedItemHand() == InteractionHand.MAIN_HAND ? 1: -1; - if (minecraft.options.mainHand().get() == HumanoidArm.LEFT) { - k = -k; - } - player.yBodyRot = (float)(k * 45 + player.getYRot()); - } - } - } - - /** - * 更新平滑的透明度 - */ - private void updateSmoothOpacity (double period, float partialTick) { - double targetOpacity = 1.0; - if (ThirdPerson.getConfig().player_fade_out_enabled) { - final double C = ThirdPersonConstants.CAMERA_THROUGH_WALL_DETECTION * 2; - Vector3d cameraPosition = LMath.toVector3d(ThirdPerson.CAMERA_AGENT.getRawCamera().getPosition()); - final double distance = getRawEyePosition(partialTick).distance(cameraPosition); - targetOpacity = (distance - C) / (1 - C); - if (targetOpacity > ThirdPersonConstants.GAZE_OPACITY && !isFallFlying() && ThirdPerson.CAMERA_AGENT.isLookingAt(getRawCameraEntity())) { - targetOpacity = ThirdPersonConstants.GAZE_OPACITY; - } - } - smoothOpacity.setTarget(LMath.clamp(targetOpacity, 0, 1)); - smoothOpacity.setHalflife(ThirdPersonConstants.OPACITY_HALFLIFE * (wasAiming() ? 0.25: 1)); - smoothOpacity.update(period); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/core/rotation/RotateStrategy.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/core/rotation/RotateStrategy.java deleted file mode 100644 index 307f0785..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/core/rotation/RotateStrategy.java +++ /dev/null @@ -1,82 +0,0 @@ -package net.leawind.mc.thirdperson.impl.core.rotation; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonStatus; -import net.leawind.mc.thirdperson.api.core.rotation.SmoothType; -import net.leawind.mc.util.math.decisionmap.api.DecisionFactor; -import net.leawind.mc.util.math.decisionmap.api.DecisionMap; -import net.leawind.mc.util.math.decisionmap.api.anno.ADecisionFactor; -import net.minecraft.world.entity.Entity; -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; -import java.util.function.Supplier; - -/** - * 玩家旋转策略 - */ -public interface RotateStrategy { - @ADecisionFactor DecisionFactor is_swimming = DecisionFactor.of(() -> ThirdPerson.ENTITY_AGENT.getRawCameraEntity().isSwimming()); - @ADecisionFactor DecisionFactor is_aiming = DecisionFactor.of(() -> ThirdPerson.ENTITY_AGENT.isAiming() || ThirdPersonStatus.doesPlayerWantToAim()); - @ADecisionFactor DecisionFactor is_fall_flying = DecisionFactor.of(() -> ThirdPerson.ENTITY_AGENT.isFallFlying()); - @ADecisionFactor DecisionFactor should_rotate_with_camera_when_not_aiming = DecisionFactor.of(() -> ThirdPerson.getConfig().player_rotate_with_camera_when_not_aiming); - @ADecisionFactor DecisionFactor rotate_interacting = DecisionFactor.of(() -> ThirdPerson.getConfig().auto_rotate_interacting && ThirdPerson.ENTITY_AGENT.isInterecting()); - /** - * 默认策略:移动时转向前进方向,静止时不旋转 - */ - Supplier DEFAULT = () -> { - RotateTarget rotateTarget = ThirdPerson.getConfig().rotate_to_moving_direction // - ? RotateTarget.HORIZONTAL_IMPULSE_DIRECTION // - : RotateTarget.NONE; - SmoothType smoothType = ThirdPerson.mc.options.keySprint.isDown() || ThirdPerson.ENTITY_AGENT.isSprinting() // - ? SmoothType.HARD // - : SmoothType.EXP_LINEAR; - ThirdPerson.ENTITY_AGENT.setRotateTarget(rotateTarget); - ThirdPerson.ENTITY_AGENT.setRotationSmoothType(smoothType); - return 0.1D; - }; - Supplier SWIMMING = () -> { - ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTarget.IMPULSE_DIRECTION); - ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothType.LINEAR); - return 0.01D; - }; - Supplier AIMING = () -> { - ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTarget.PREDICTED_TARGET_ENTITY); - ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothType.HARD); - return 0D; - }; - Supplier FALL_FLYING = () -> { - ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTarget.CAMERA_ROTATION); - ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothType.HARD); - return 0D; - }; - Supplier WITH_CAMERA_NOT_AIMING = () -> { - ThirdPerson.ENTITY_AGENT.setRotateTarget(RotateTarget.CAMERA_ROTATION); - ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothType.LINEAR); - return 0D; - }; - Supplier INTERECTING = () -> { - ThirdPerson.ENTITY_AGENT.setRotateTarget(ThirdPerson.getConfig().rotate_interacting_type // - ? RotateTarget.CAMERA_HIT_RESULT // - : RotateTarget.CAMERA_ROTATION); - ThirdPerson.ENTITY_AGENT.setRotationSmoothType(SmoothType.LINEAR); - return 0D; - }; - - /** - * 将通过反射调用此方法 - *

    - * {@link DecisionMap#of(Class)} - */ - @SuppressWarnings("unused") - static void build (@NotNull DecisionMap map) { - map.addRule(0, 0, DEFAULT) // - .addRule(~0, rotate_interacting.mask(), INTERECTING) // - .addRule(~0, should_rotate_with_camera_when_not_aiming.mask(), WITH_CAMERA_NOT_AIMING) // - .addRule(~0, is_fall_flying.mask(), FALL_FLYING) // - .addRule(~0, is_swimming.mask(), SWIMMING) // - .addRule(~0, is_aiming.mask(), AIMING) // - ; - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/core/rotation/RotateTarget.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/core/rotation/RotateTarget.java deleted file mode 100644 index 46e61c0b..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/core/rotation/RotateTarget.java +++ /dev/null @@ -1,103 +0,0 @@ -package net.leawind.mc.thirdperson.impl.core.rotation; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonStatus; -import net.leawind.mc.thirdperson.api.core.CameraAgent; -import net.leawind.mc.util.math.LMath; -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.leawind.mc.util.math.vector.api.Vector3d; -import net.minecraft.client.Camera; -import net.minecraft.world.entity.Entity; -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; -import java.util.function.Supplier; - -/** - * 旋转目标,即玩家应该转向何处 - */ -public enum RotateTarget { - /** - * 保持当前朝向,不旋转 - */ - NONE(() -> ThirdPerson.ENTITY_AGENT.getRawRotation(1)), - /** - * 与相机朝向相同 - */ - CAMERA_ROTATION(() -> ThirdPerson.CAMERA_AGENT.getRotation()), - /** - * 转向相机的视线落点,即准星所指的位置 - */ - CAMERA_HIT_RESULT(() -> { - Optional cameraHitPosition = ThirdPerson.CAMERA_AGENT.getPickPosition(); - if (cameraHitPosition.isEmpty()) { - return CAMERA_ROTATION.getRotation(); - } else { - Vector3d eyePosition = ThirdPerson.ENTITY_AGENT.getRawEyePosition(1); - Vector3d viewVector = cameraHitPosition.get().sub(eyePosition); - return LMath.rotationDegreeFromDirection(viewVector); - } - }), - /** - * 预测玩家想射击的目标实体 - *

    - * 玩家将朝向的目标点为 相机位置 + 相机射线单位向量*目标实体距离 - *

    - * 这样在射击远处的实体时,就不需要考虑玩家视线与相机视线间的偏移量了。 - *

    - * 但是问题在于,当周围有许多实体时,对目标实体的预测可能不准确。 - * - * @see CameraAgent#predictTargetEntity() - */ - PREDICTED_TARGET_ENTITY(() -> { - Vector2d rotation = CAMERA_HIT_RESULT.getRotation(); - if (ThirdPerson.getConfig().enable_target_entity_predict && ThirdPerson.ENTITY_AGENT.isControlled()) { - Optional predicted = ThirdPerson.CAMERA_AGENT.predictTargetEntity(); - if (predicted.isPresent()) { - Camera camera = ThirdPerson.CAMERA_AGENT.getRawCamera(); - Entity target = predicted.get(); - Vector3d playerEyePos = ThirdPerson.ENTITY_AGENT.getRawEyePosition(ThirdPersonStatus.lastPartialTick); - Vector3d cameraPos = ThirdPerson.CAMERA_AGENT.getRawCameraPosition(); - Vector3d targetPos = LMath.toVector3d(target.getPosition(ThirdPersonStatus.lastPartialTick)); - double distance = cameraPos.distance(targetPos); - Vector3d end = LMath.toVector3d(camera.getLookVector()).normalize(distance).add(cameraPos); - return LMath.rotationDegreeFromDirection(end.copy().sub(playerEyePos)); - } - } - return rotation; - }), - /** - * 使用键盘控制的移动方向 - *

    - * 当没有使用键盘控制时保持当前朝向 - */ - IMPULSE_DIRECTION(() -> ThirdPersonStatus.impulseHorizon.length() < 1e-5 // - ? NONE.getRotation() // - : LMath.rotationDegreeFromDirection(ThirdPersonStatus.impulse)), - /** - * 使用键盘控制的移动方向(仅水平) - *

    - * 当没有使用键盘控制时保持当前朝向 - */ - HORIZONTAL_IMPULSE_DIRECTION(() -> { - if (ThirdPersonStatus.impulseHorizon.length() < 1e-5) { - return NONE.getRotation(); - } else { - double absoluteYRotDegree = LMath.rotationDegreeFromDirection(ThirdPersonStatus.impulseHorizon); - return Vector2d.of(0.1, absoluteYRotDegree); - } - }); - private final Supplier rotationGetter; - - RotateTarget (@NotNull Supplier rotationGetter) { - this.rotationGetter = rotationGetter; - } - - /** - * 获取玩家当前的目标朝向 - */ - public @NotNull Vector2d getRotation () { - return rotationGetter.get(); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/screen/ClothConfigScreenBuilder.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/screen/ClothConfigScreenBuilder.java deleted file mode 100644 index 3ff1559c..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/screen/ClothConfigScreenBuilder.java +++ /dev/null @@ -1,161 +0,0 @@ -package net.leawind.mc.thirdperson.impl.screen; - - -import me.shedaniel.clothconfig2.api.ConfigBuilder; -import me.shedaniel.clothconfig2.api.ConfigCategory; -import me.shedaniel.clothconfig2.api.ConfigEntryBuilder; -import me.shedaniel.clothconfig2.gui.entries.BooleanListEntry; -import me.shedaniel.clothconfig2.gui.entries.DoubleListEntry; -import me.shedaniel.clothconfig2.gui.entries.IntegerSliderEntry; -import me.shedaniel.clothconfig2.gui.entries.StringListListEntry; -import me.shedaniel.clothconfig2.impl.builders.SubCategoryBuilder; -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.thirdperson.api.config.ConfigManager; -import net.leawind.mc.thirdperson.api.screen.ConfigScreenBuilder; -import net.leawind.mc.util.itempattern.ItemPattern; -import net.minecraft.client.gui.screens.Screen; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.function.Consumer; - -public class ClothConfigScreenBuilder implements ConfigScreenBuilder { - public @NotNull Screen build (@NotNull Config config, @Nullable Screen parent) { - final ConfigBuilder builder = ConfigBuilder.create() // - .setParentScreen(parent) // - .setTitle(ConfigManager.getText("text.title")) // - .setSavingRunnable(ThirdPerson.CONFIG_MANAGER::trySave); - final ConfigEntryBuilder entryBuilder = builder.entryBuilder(); - Config defaults = Config.DEFAULTS; - //==================================================================================================================================================// - //==================================================================================================================================================// - // Category: general - final ConfigCategory CATEGORY_GENERAL = builder.getOrCreateCategory(ConfigManager.getText("option_category.general")); - { - CATEGORY_GENERAL.addEntry(buildBooleanEntry("is_mod_enable", defaults.is_mod_enable, config.is_mod_enable, v -> config.is_mod_enable = v, entryBuilder)); - CATEGORY_GENERAL.addEntry(buildBooleanEntry("is_third_person_mode", defaults.is_third_person_mode, config.is_third_person_mode, v -> config.is_third_person_mode = v, entryBuilder)); - CATEGORY_GENERAL.addEntry(buildBooleanEntry("lock_camera_pitch_angle", defaults.lock_camera_pitch_angle, config.lock_camera_pitch_angle, v -> config.lock_camera_pitch_angle = v, entryBuilder)); - if (ConfigScreenBuilders.getAvailableBuidlers().size() > 1) { - CATEGORY_GENERAL.addEntry(entryBuilder.startDropdownMenu(ConfigManager.getText("option.config_screen_api"), config.config_screen_api, v -> config.config_screen_api = v).setSelections(ConfigScreenBuilders.getAvailableBuidlers().keySet()).build()); - } - // SubCategory: Player Rotation - final SubCategoryBuilder SUBCATEGORY_PLAYER_ROTATION = buildSubCategory("player_rotation", entryBuilder); - SUBCATEGORY_PLAYER_ROTATION.add(buildBooleanEntry("player_rotate_with_camera_when_not_aiming", defaults.player_rotate_with_camera_when_not_aiming, config.player_rotate_with_camera_when_not_aiming, v -> config.player_rotate_with_camera_when_not_aiming = v, entryBuilder)); - SUBCATEGORY_PLAYER_ROTATION.add(buildBooleanEntry("rotate_to_moving_direction", defaults.rotate_to_moving_direction, config.rotate_to_moving_direction, v -> config.rotate_to_moving_direction = v, entryBuilder)); - SUBCATEGORY_PLAYER_ROTATION.add(buildBooleanEntry("auto_rotate_interacting", defaults.auto_rotate_interacting, config.auto_rotate_interacting, v -> config.auto_rotate_interacting = v, entryBuilder)); - SUBCATEGORY_PLAYER_ROTATION.add(buildBooleanEntry("rotate_interacting_type", defaults.rotate_interacting_type, config.rotate_interacting_type, v -> config.rotate_interacting_type = v, entryBuilder)); - SUBCATEGORY_PLAYER_ROTATION.add(buildBooleanEntry("auto_turn_body_drawing_a_bow", defaults.auto_turn_body_drawing_a_bow, config.auto_turn_body_drawing_a_bow, v -> config.auto_turn_body_drawing_a_bow = v, entryBuilder)); - CATEGORY_GENERAL.addEntry(SUBCATEGORY_PLAYER_ROTATION.build()); - // SubCategory: Camera Distance Adjustment - final SubCategoryBuilder Subcategory_Camera_Distance_Adjustment = buildSubCategory("camera_distance_adjustment", entryBuilder); - Subcategory_Camera_Distance_Adjustment.add(buildIntSliderEntry("available_distance_count", 2, 64, defaults.available_distance_count, config.available_distance_count, v -> config.available_distance_count = v, entryBuilder)); - Subcategory_Camera_Distance_Adjustment.add(buildDoubleEntry("camera_distance_min", 0.5, 2.0, defaults.camera_distance_min, config.camera_distance_min, v -> config.camera_distance_min = v, entryBuilder)); - Subcategory_Camera_Distance_Adjustment.add(buildDoubleEntry("camera_distance_max", 2.0, 16D, defaults.camera_distance_max, config.camera_distance_max, v -> config.camera_distance_max = v, entryBuilder)); - CATEGORY_GENERAL.addEntry(Subcategory_Camera_Distance_Adjustment.build()); - } - //==================================================================================================================================================// - //==================================================================================================================================================// - // Category: Miscellaneous - final ConfigCategory CATEGORY_MISC = builder.getOrCreateCategory(ConfigManager.getText("option_category.misc")); - { - CATEGORY_MISC.addEntry(buildBooleanEntry("center_offset_when_flying", defaults.center_offset_when_flying, config.center_offset_when_flying, v -> config.center_offset_when_flying = v, entryBuilder)); - CATEGORY_MISC.addEntry(buildBooleanEntry("use_camera_pick_in_creative", defaults.use_camera_pick_in_creative, config.use_camera_pick_in_creative, v -> config.use_camera_pick_in_creative = v, entryBuilder)); - CATEGORY_MISC.addEntry(buildBooleanEntry("turn_with_camera_when_enter_first_person", defaults.turn_with_camera_when_enter_first_person, config.turn_with_camera_when_enter_first_person, v -> config.turn_with_camera_when_enter_first_person = v, entryBuilder)); - CATEGORY_MISC.addEntry(buildDoubleEntry("camera_ray_trace_length", 32D, 2048D, defaults.camera_ray_trace_length, config.camera_ray_trace_length, v -> config.camera_ray_trace_length = v, entryBuilder)); - CATEGORY_MISC.addEntry(buildBooleanEntry("enable_target_entity_predict", defaults.enable_target_entity_predict, config.enable_target_entity_predict, v -> config.enable_target_entity_predict = v, entryBuilder)); - // SubCategory: Player Fade out - final SubCategoryBuilder Subcategory_Player_Fade_Out = buildSubCategory("player_fade_out", entryBuilder); - Subcategory_Player_Fade_Out.add(buildBooleanEntry("player_fade_out_enabled", defaults.player_fade_out_enabled, config.player_fade_out_enabled, v -> config.player_fade_out_enabled = v, entryBuilder)); - CATEGORY_MISC.addEntry(Subcategory_Player_Fade_Out.build()); - // SubCategory: Crosshair - final SubCategoryBuilder Subcategory_Crosshair = buildSubCategory("crosshair", entryBuilder); - Subcategory_Crosshair.add(buildBooleanEntry("render_crosshair_when_not_aiming", defaults.render_crosshair_when_not_aiming, config.render_crosshair_when_not_aiming, v -> config.render_crosshair_when_not_aiming = v, entryBuilder)); - Subcategory_Crosshair.add(buildBooleanEntry("render_crosshair_when_aiming", defaults.render_crosshair_when_aiming, config.render_crosshair_when_aiming, v -> config.render_crosshair_when_aiming = v, entryBuilder)); - CATEGORY_MISC.addEntry(Subcategory_Crosshair.build()); - } - //==================================================================================================================================================// - //==================================================================================================================================================// - // Category: smooth factors - final ConfigCategory CATEGORY_SMOOTH_FACTORS = builder.getOrCreateCategory(ConfigManager.getText("option_category.smooth_halflife")); - { - CATEGORY_SMOOTH_FACTORS.addEntry(buildSmoothHalflifeEntry("flying_smooth_halflife", defaults.flying_smooth_halflife, config.flying_smooth_halflife, v -> config.flying_smooth_halflife = v, entryBuilder)); - // SubCategory: Adjusting Camera - final SubCategoryBuilder Subcategory_Adjusting_Camera = buildSubCategory("adjusting_camera", entryBuilder); - Subcategory_Adjusting_Camera.add(buildSmoothHalflifeEntry("adjusting_camera_offset_smooth_halflife", defaults.adjusting_camera_offset_smooth_halflife, config.adjusting_camera_offset_smooth_halflife, v -> config.adjusting_camera_offset_smooth_halflife = v, entryBuilder)); - Subcategory_Adjusting_Camera.add(buildSmoothHalflifeEntry("adjusting_distance_smooth_halflife", defaults.adjusting_distance_smooth_halflife, config.adjusting_distance_smooth_halflife, v -> config.adjusting_distance_smooth_halflife = v, entryBuilder)); - CATEGORY_SMOOTH_FACTORS.addEntry(Subcategory_Adjusting_Camera.build()); - // SubCategory: Normal Mode - final SubCategoryBuilder SubCategory_Normal_Mode = buildSubCategory("normal_mode", entryBuilder); - SubCategory_Normal_Mode.add(buildSmoothHalflifeEntry("smooth_halflife_horizon", defaults.normal_smooth_halflife_horizon, config.normal_smooth_halflife_horizon, v -> config.normal_smooth_halflife_horizon = v, entryBuilder)); - SubCategory_Normal_Mode.add(buildSmoothHalflifeEntry("smooth_halflife_vertical", defaults.normal_smooth_halflife_vertical, config.normal_smooth_halflife_vertical, v -> config.normal_smooth_halflife_vertical = v, entryBuilder)); - SubCategory_Normal_Mode.add(buildSmoothHalflifeEntry("camera_offset_smooth_halflife", defaults.normal_camera_offset_smooth_halflife, config.normal_camera_offset_smooth_halflife, v -> config.normal_camera_offset_smooth_halflife = v, entryBuilder)); - SubCategory_Normal_Mode.add(buildSmoothHalflifeEntry("distance_smooth_halflife", defaults.normal_distance_smooth_halflife, config.normal_distance_smooth_halflife, v -> config.normal_distance_smooth_halflife = v, entryBuilder)); - CATEGORY_SMOOTH_FACTORS.addEntry(SubCategory_Normal_Mode.build()); - // SubCategory: Aiming Mode - final SubCategoryBuilder Subcategory_Aiming_Mode = buildSubCategory("aiming_mode", entryBuilder); - Subcategory_Aiming_Mode.add(buildSmoothHalflifeEntry("smooth_halflife_horizon", defaults.aiming_smooth_halflife_horizon, config.aiming_smooth_halflife_horizon, v -> config.aiming_smooth_halflife_horizon = v, entryBuilder)); - Subcategory_Aiming_Mode.add(buildSmoothHalflifeEntry("smooth_halflife_vertical", defaults.aiming_smooth_halflife_vertical, config.aiming_smooth_halflife_vertical, v -> config.aiming_smooth_halflife_vertical = v, entryBuilder)); - Subcategory_Aiming_Mode.add(buildSmoothHalflifeEntry("camera_offset_smooth_halflife", defaults.aiming_camera_offset_smooth_halflife, config.aiming_camera_offset_smooth_halflife, v -> config.aiming_camera_offset_smooth_halflife = v, entryBuilder)); - Subcategory_Aiming_Mode.add(buildSmoothHalflifeEntry("distance_smooth_halflife", defaults.aiming_distance_smooth_halflife, config.aiming_distance_smooth_halflife, v -> config.aiming_distance_smooth_halflife = v, entryBuilder)); - CATEGORY_SMOOTH_FACTORS.addEntry(Subcategory_Aiming_Mode.build()); - } - //==================================================================================================================================================// - //==================================================================================================================================================// - // Category: Camera Offset - final ConfigCategory CATEGORY_CAMERA_OFFSET = builder.getOrCreateCategory(ConfigManager.getText("option_category.camera_offset")); - { - // SubCategory: Normal Mode - final SubCategoryBuilder SubCategory_Normal_Mode = buildSubCategory("normal_mode", entryBuilder); - SubCategory_Normal_Mode.add(buildDoubleEntry("max_distance", 0.5, 32, defaults.normal_max_distance, config.normal_max_distance, v -> config.normal_max_distance = v, entryBuilder)); - SubCategory_Normal_Mode.add(buildDoubleEntry("offset_x", -1, +1, defaults.normal_offset_x, config.normal_offset_x, v -> config.normal_offset_x = v, entryBuilder)); - SubCategory_Normal_Mode.add(buildDoubleEntry("offset_y", -1, +1, defaults.normal_offset_y, config.normal_offset_y, v -> config.normal_offset_y = v, entryBuilder)); - SubCategory_Normal_Mode.add(buildBooleanEntry("is_centered", defaults.normal_is_centered, config.normal_is_centered, v -> config.normal_is_centered = v, entryBuilder)); - SubCategory_Normal_Mode.add(buildDoubleEntry("offset_center", -1, +1, defaults.normal_offset_center, config.normal_offset_center, v -> config.normal_offset_center = v, entryBuilder)); - CATEGORY_CAMERA_OFFSET.addEntry(SubCategory_Normal_Mode.build()); - // SubCategory: Aiming Mode - final SubCategoryBuilder Subcategory_Aiming_Mode = buildSubCategory("aiming_mode", entryBuilder); - Subcategory_Aiming_Mode.add(buildDoubleEntry("max_distance", 0.5, 32, defaults.aiming_max_distance, config.aiming_max_distance, v -> config.aiming_max_distance = v, entryBuilder)); - Subcategory_Aiming_Mode.add(buildDoubleEntry("offset_x", -1, +1, defaults.aiming_offset_x, config.aiming_offset_x, v -> config.aiming_offset_x = v, entryBuilder)); - Subcategory_Aiming_Mode.add(buildDoubleEntry("offset_y", -1, +1, defaults.aiming_offset_y, config.aiming_offset_y, v -> config.aiming_offset_y = v, entryBuilder)); - Subcategory_Aiming_Mode.add(buildBooleanEntry("is_centered", defaults.aiming_is_centered, config.aiming_is_centered, v -> config.aiming_is_centered = v, entryBuilder)); - Subcategory_Aiming_Mode.add(buildDoubleEntry("offset_center", -1, +1, defaults.aiming_offset_center, config.aiming_offset_center, v -> config.aiming_offset_center = v, entryBuilder)); - CATEGORY_CAMERA_OFFSET.addEntry(Subcategory_Aiming_Mode.build()); - } - //==================================================================================================================================================// - //==================================================================================================================================================// - // Category: Aiming Check - final ConfigCategory CATEGORY_AIMING_CHECK = builder.getOrCreateCategory(ConfigManager.getText("option_category.aiming_check")); - { - CATEGORY_AIMING_CHECK.addEntry(buildStringListEntry("hold_to_aim_item_pattern_expressions", defaults.hold_to_aim_item_pattern_expressions, config.hold_to_aim_item_pattern_expressions, v -> config.hold_to_aim_item_pattern_expressions = v, entryBuilder)); - CATEGORY_AIMING_CHECK.addEntry(buildStringListEntry("use_to_aim_item_pattern_expressions", defaults.use_to_aim_item_pattern_expressions, config.use_to_aim_item_pattern_expressions, v -> config.use_to_aim_item_pattern_expressions = v, entryBuilder)); - CATEGORY_AIMING_CHECK.addEntry(buildStringListEntry("use_to_first_person_pattern_expressions", defaults.use_to_first_person_pattern_expressions, config.use_to_first_person_pattern_expressions, v -> config.use_to_first_person_pattern_expressions = v, entryBuilder)); - } - return builder.build(); - } - - private BooleanListEntry buildBooleanEntry (String name, boolean defaultValue, boolean currentValue, Consumer setter, ConfigEntryBuilder entryBuilder) { - return entryBuilder.startBooleanToggle(ConfigManager.getText("option." + name), currentValue).setTooltip(ConfigManager.getText("option." + name + ".desc")).setDefaultValue(defaultValue).setSaveConsumer(setter).build(); - } - - private SubCategoryBuilder buildSubCategory (String name, ConfigEntryBuilder entryBuilder) { - return entryBuilder.startSubCategory(ConfigManager.getText("option_group." + name)).setExpanded(true).setTooltip(ConfigManager.getText("option_group." + name + ".desc")); - } - - private IntegerSliderEntry buildIntSliderEntry (String name, int min, int max, int defaultValue, int currentValue, Consumer setter, ConfigEntryBuilder entryBuilder) { - return entryBuilder.startIntSlider(ConfigManager.getText("option." + name), currentValue, min, max).setTooltip(ConfigManager.getText("option." + name + ".desc")).setDefaultValue(defaultValue).setSaveConsumer(setter).build(); - } - - private DoubleListEntry buildDoubleEntry (String name, double min, double max, double defaultValue, double currentValue, Consumer setter, ConfigEntryBuilder entryBuilder) { - return entryBuilder.startDoubleField(ConfigManager.getText("option." + name), currentValue).setTooltip(ConfigManager.getText("option." + name + ".desc")).setDefaultValue(defaultValue).setSaveConsumer(setter).setMin(min).setMax(max).build(); - } - - private DoubleListEntry buildSmoothHalflifeEntry (String name, double defaultValue, double currentValue, Consumer setter, ConfigEntryBuilder entryBuilder) { - return buildDoubleEntry(name, 0, 4, defaultValue, currentValue, setter, entryBuilder); - } - - private StringListListEntry buildStringListEntry (String name, List defaultValue, List currentValue, Consumer> setter, ConfigEntryBuilder entryBuilder) { - return entryBuilder.startStrList(ConfigManager.getText("option." + name), currentValue).setTooltip(ConfigManager.getText("option." + name + ".desc")).setSaveConsumer(setter).setDefaultValue(defaultValue).setDeleteButtonEnabled(true).setCellErrorSupplier(ItemPattern::supplyError).setExpanded(true).build(); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/screen/ConfigScreenBuilders.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/screen/ConfigScreenBuilders.java deleted file mode 100644 index b4b240d6..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/screen/ConfigScreenBuilders.java +++ /dev/null @@ -1,62 +0,0 @@ -package net.leawind.mc.thirdperson.impl.screen; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.api.screen.ConfigScreenBuilder; -import net.leawind.mc.util.OptionalFunction; -import org.jetbrains.annotations.NotNull; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.function.Supplier; - -/** - * 配置屏幕构建器 - */ -@SuppressWarnings("all") -public final class ConfigScreenBuilders { - /** - * 已经实现或将来可能实现的构建器们 - */ - private static final Map> builders = new HashMap<>(); - - static { - builders.put("Cloth Config", OptionalFunction.of(() -> new ClothConfigScreenBuilder(), packageExists("me.shedaniel.clothconfig2.api.ConfigBuilder"))); - builders.put("YACL", OptionalFunction.of(() -> new YaclConfigScreenBuilder(), () -> false)); - if (getAvailableBuidlers().isEmpty()) { - ThirdPerson.LOGGER.warn("No config screen API available."); - } - } - - public static @NotNull Optional getBuilder () { - final Map> availables = getAvailableBuidlers(); - final String expected = ThirdPerson.getConfig().config_screen_api; - if (availables.isEmpty()) { - return Optional.empty(); - } else { - return Optional.of(availables.getOrDefault(expected, availables.values().iterator().next()).get()); - } - } - - public static @NotNull Map> getAvailableBuidlers () { - final Map> availableBuilders = new HashMap<>(); - builders.forEach((name, builder) -> { - if (builder.isAvailable()) { - availableBuilders.put(name, builder); - } - }); - return availableBuilders; - } - - private static Supplier packageExists (String packageName) { - return () -> { - try { - Class.forName(packageName); - return true; - } catch (Exception e) { - return false; - } - }; - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/impl/screen/YaclConfigScreenBuilder.java b/common/src/main/java/net/leawind/mc/thirdperson/impl/screen/YaclConfigScreenBuilder.java deleted file mode 100644 index 0a503fae..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/impl/screen/YaclConfigScreenBuilder.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.leawind.mc.thirdperson.impl.screen; - - -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.thirdperson.api.screen.ConfigScreenBuilder; -import net.minecraft.client.gui.screens.Screen; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class YaclConfigScreenBuilder implements ConfigScreenBuilder { - @Override - public @NotNull Screen build (@NotNull Config config, @Nullable Screen parent) { - throw new AssertionError(); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/CameraInvoker.java b/common/src/main/java/net/leawind/mc/thirdperson/mixin/CameraInvoker.java deleted file mode 100644 index 448ae22d..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/CameraInvoker.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.leawind.mc.thirdperson.mixin; - - -import net.minecraft.world.phys.Vec3; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Invoker; - -@Mixin(net.minecraft.client.Camera.class) -public interface CameraInvoker { - @Invoker("setPosition") - void invokeSetPosition (Vec3 pos); - - /** - * 单位:角度制 - * - * @param yRot 偏航角,z轴正向是0,顺时针为正向 - * @param xRot 俯仰角,俯正仰负 [-90,90] - */ - @Invoker("setRotation") - void invokeSetRotation (float yRot, float xRot); - - /** - * 相对于当前位置移动相机 - */ - @Invoker("move") - void invokeMove (double forward, double up, double left); -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/CameraMixin.java b/common/src/main/java/net/leawind/mc/thirdperson/mixin/CameraMixin.java deleted file mode 100644 index 2c7e9d8f..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/CameraMixin.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.leawind.mc.thirdperson.mixin; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonEvents; -import net.minecraft.client.Camera; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.level.BlockGetter; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(value=net.minecraft.client.Camera.class, priority=2000) -public abstract class CameraMixin { - /** - * 插入到 setup 方法中的第一个 move(DDD)V 调用之前 - *

    - * 调用{@link ThirdPersonEvents#onCameraSetup}方法,更新相机的朝向、位置等信息 - *

    - * 该调用位于真正渲染画面之前。 - *

    - * GameRender#render -> GameRender#renderLevel -> Camera#setup - */ - @Inject(method="setup", at=@At(value="INVOKE", target="Lnet/minecraft/client/Camera;move(DDD)V", shift=At.Shift.BEFORE), cancellable=true) - public void setup_invoke (BlockGetter level, Entity attachedEntity, boolean detached, boolean reversedView, float partialTick, CallbackInfo ci) { - if (ThirdPerson.isAvailable()) { - if (reversedView) { - // 咱给它转回去 - Camera camera = (Camera)(Object)this; - ((CameraInvoker)camera).invokeSetRotation(camera.getYRot() + 180.0f, -camera.getXRot()); - } - ThirdPersonEvents.onCameraSetup(level, partialTick); - ci.cancel(); - } - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/EntityMixin.java b/common/src/main/java/net/leawind/mc/thirdperson/mixin/EntityMixin.java deleted file mode 100644 index e5b68aa6..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/EntityMixin.java +++ /dev/null @@ -1,96 +0,0 @@ -package net.leawind.mc.thirdperson.mixin; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonConstants; -import net.leawind.mc.thirdperson.ThirdPersonStatus; -import net.leawind.mc.util.math.LMath; -import net.leawind.mc.util.math.vector.api.Vector3d; -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.phys.BlockHitResult; -import net.minecraft.world.phys.EntityHitResult; -import net.minecraft.world.phys.HitResult; -import net.minecraft.world.phys.Vec3; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -/** - * 方法{@link Entity#pick}用于计算玩家视线落点 - *

    - * 为了在第三人称视角中能够随时选取准星所指的方块,将获取到的玩家 viewVector 修改为朝向相机视线落点的方向 - */ -@Mixin(value=net.minecraft.world.entity.Entity.class, priority=2000) -public class EntityMixin { - /** - * 将探测起点更改为相机处 - * - * @param pickStartFake 探测起点,默认为玩家眼睛坐标 - * @see GameRendererMixin#pick_storePickStart(Vec3) - */ - @ModifyVariable(method="pick", at=@At("STORE"), ordinal=0) - public Vec3 pick_storePickStart (Vec3 pickStartFake) { - if (ThirdPerson.isAvailable() && ThirdPersonStatus.isRenderingInThirdPerson() && ThirdPersonStatus.shouldPickFromCamera()) { - return ThirdPerson.CAMERA_AGENT.getRawCamera().getPosition(); - } else { - return pickStartFake; - } - } - - /** - * {@link Entity#pick}方法在{@link GameRenderer#pick}中被用于探测方块, - *

    - * 原本 viewVector 是通过{@link Entity#getViewVector(float)}取得的,之后要用于计算 pickEnd - *

    - * 咱得在 viewVector 被赋值时拦截,重新计算 viewVector 的值。 - * - * @param viewVectorFake 探测向量 - * @see GameRendererMixin#pick_storeViewVector(Vec3) - * @see ThirdPersonStatus#shouldPickFromCamera() - */ - @ModifyVariable(method="pick", at=@At("STORE"), ordinal=1) - public Vec3 pick_storeViewVector (Vec3 viewVectorFake) { - if (ThirdPerson.isAvailable() && ThirdPersonStatus.isRenderingInThirdPerson()) { - if (ThirdPersonStatus.shouldPickFromCamera()) { - Vector3d viewVector = LMath.toVector3d(ThirdPerson.CAMERA_AGENT.getRawCamera().getLookVector()).normalize(); - return LMath.toVec3(viewVector); - } else { - Vec3 eyePosition = LMath.toVec3(ThirdPerson.ENTITY_AGENT.getRawEyePosition(1)); - HitResult cameraHitResult = ThirdPerson.CAMERA_AGENT.pick(); - Vec3 eyeToHitResult = eyePosition.vectorTo(cameraHitResult.getLocation()); - return eyeToHitResult.normalize(); - } - } else { - return viewVectorFake; - } - } - - /** - * 限制距离为 {@link ThirdPersonConstants#MAX_INTERACTION_DISTANCE} - *

    - * 通过检查玩家的眼睛位置与选定实体或方块之间的距离来修改其返回值。如果距离大于预定义的最大交互距离,则修改返回的HitResult以指示未命中。 - */ - @Inject(method="pick", at=@At("RETURN"), cancellable=true) - public void pick_return (double pickRange, float partialTick, boolean includeFluid, CallbackInfoReturnable ci) { - HitResult hitResult = ci.getReturnValue(); - if (hitResult instanceof EntityHitResult entityHitResult) { - Entity entity = entityHitResult.getEntity(); - Vec3 viewVector = entity.getViewVector(1f); - if (ThirdPerson.ENTITY_AGENT.getRawPlayerEntity().getEyePosition().distanceTo(entityHitResult.getLocation()) > ThirdPersonConstants.MAX_INTERACTION_DISTANCE) { - hitResult = BlockHitResult.miss(hitResult.getLocation(), Direction.getNearest(viewVector.x, viewVector.y, viewVector.z), new BlockPos(hitResult.getLocation())); - } - } else if (hitResult instanceof BlockHitResult blockHitResult && hitResult.getType() != HitResult.Type.MISS) { - BlockPos blockPos = blockHitResult.getBlockPos(); - Vec3 centerOfBlockPos = Vec3.atCenterOf(blockPos); - if (ThirdPerson.ENTITY_AGENT.getRawPlayerEntity().getEyePosition().distanceTo(centerOfBlockPos) > ThirdPersonConstants.MAX_INTERACTION_DISTANCE) { - hitResult = BlockHitResult.miss(hitResult.getLocation(), blockHitResult.getDirection(), new BlockPos(hitResult.getLocation())); - } - } - ci.setReturnValue(hitResult); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/GameRendererMixin.java b/common/src/main/java/net/leawind/mc/thirdperson/mixin/GameRendererMixin.java deleted file mode 100644 index 479e0896..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/GameRendererMixin.java +++ /dev/null @@ -1,90 +0,0 @@ -package net.leawind.mc.thirdperson.mixin; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonEvents; -import net.leawind.mc.thirdperson.ThirdPersonStatus; -import net.leawind.mc.util.math.LMath; -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.projectile.ProjectileUtil; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.HitResult; -import net.minecraft.world.phys.Vec3; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -/** - * {@link GameRenderer#pick}会先调用{@link Entity#pick}探测方块,再通过{@link ProjectileUtil#getEntityHitResult}探测实体,然后计算最终探测结果 - */ -@Mixin(value=net.minecraft.client.renderer.GameRenderer.class, priority=2000) -public class GameRendererMixin { - @Inject(method="render", at=@At("HEAD")) - public void pre_render (float particalTicks, long l, boolean bl, CallbackInfo ci) { - if (ThirdPerson.getConfig().is_mod_enable) { - ThirdPersonEvents.onPreRender(particalTicks); - } - } - - /** - * @param pickStartFake 探测起点,默认为玩家眼睛坐标 - * @see EntityMixin#pick_storePickStart(Vec3) - */ - @ModifyVariable(method="pick", at=@At("STORE"), ordinal=0) - public Vec3 pick_storePickStart (Vec3 pickStartFake) { - if (ThirdPerson.isAvailable() && ThirdPersonStatus.isRenderingInThirdPerson() && ThirdPersonStatus.shouldPickFromCamera()) { - return ThirdPerson.CAMERA_AGENT.getRawCamera().getPosition(); - } else { - return pickStartFake; - } - } - - /** - * 在 viewVector 赋值时截获,将其修改为朝向相机视线落点的方向。 - *

    - * 这样后面就可以计算出正确的 pickEnd 和 aabb - * - * @param viewVectorFake 探测向量 - * @see EntityMixin#pick_storeViewVector(Vec3) - */ - @ModifyVariable(method="pick", at=@At("STORE"), ordinal=1) - public Vec3 pick_storeViewVector (Vec3 viewVectorFake) { - if (ThirdPerson.isAvailable() && ThirdPersonStatus.isRenderingInThirdPerson()) { - if (ThirdPersonStatus.shouldPickFromCamera()) { - return new Vec3(ThirdPerson.CAMERA_AGENT.getRawCamera().getLookVector()); - } else { - Vec3 eyePosition = LMath.toVec3(ThirdPerson.ENTITY_AGENT.getRawEyePosition(1)); - HitResult cameraHitResult = ThirdPerson.CAMERA_AGENT.pick(); - Vec3 eyeToHitResult = eyePosition.vectorTo(cameraHitResult.getLocation()); - return eyeToHitResult.normalize(); - } - } else { - return viewVectorFake; - } - } - - /** - * 延长 pickRange - */ - @ModifyVariable(method="pick", at=@At("STORE"), ordinal=0) - public double pick_storePickRange (double pickRange) { - if (ThirdPerson.isAvailable() && ThirdPersonStatus.isRenderingInThirdPerson() && ThirdPersonStatus.shouldPickFromCamera()) { - pickRange += Math.max(0, ThirdPerson.ENTITY_AGENT.getRawEyePosition(1).distance(LMath.toVector3d(ThirdPerson.CAMERA_AGENT.getRawCamera().getPosition()))); - } - return pickRange; - } - - /** - * pick 实体时,将AABB移动到以相机为起点处 - */ - @ModifyVariable(method="pick", at=@At("STORE"), ordinal=0) - public AABB pick_storeAabb (AABB aabb) { - if (ThirdPerson.isAvailable() && ThirdPersonStatus.isRenderingInThirdPerson() && ThirdPersonStatus.shouldPickFromCamera()) { - aabb.move(ThirdPerson.CAMERA_AGENT.getRawCamera().getPosition().subtract(ThirdPerson.ENTITY_AGENT.getRawCameraEntity().getEyePosition())); - } - return aabb; - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/GuiMixin.java b/common/src/main/java/net/leawind/mc/thirdperson/mixin/GuiMixin.java deleted file mode 100644 index 8cb9946b..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/GuiMixin.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.leawind.mc.thirdperson.mixin; - - -import net.leawind.mc.thirdperson.ThirdPersonStatus; -import net.minecraft.client.CameraType; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Gui; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -@Mixin(value=Gui.class, priority=2000) -public class GuiMixin { - /** - * 重定向 isFirstPerson()Z 方法 - *

    - * 原本仅在第一人称下显示准星,现在第三人称下也可以显示准星 - */ - @Redirect(method="renderCrosshair(Lcom/mojang/blaze3d/vertex/PoseStack;)V", at=@At(value="INVOKE", target="Lnet/minecraft/client/CameraType;isFirstPerson ()Z")) - public boolean renderCrosshair_invoke (CameraType instance) { - return ThirdPersonStatus.shouldRenderCrosshair() || Minecraft.getInstance().options.getCameraType().isFirstPerson(); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/KeyboardInputMixin.java b/common/src/main/java/net/leawind/mc/thirdperson/mixin/KeyboardInputMixin.java deleted file mode 100644 index be927618..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/KeyboardInputMixin.java +++ /dev/null @@ -1,56 +0,0 @@ -package net.leawind.mc.thirdperson.mixin; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonStatus; -import net.leawind.mc.util.math.LMath; -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.leawind.mc.util.math.vector.api.Vector3d; -import net.minecraft.client.player.KeyboardInput; -import org.apache.logging.log4j.util.PerformanceSensitive; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(value=KeyboardInput.class, priority=2000) -public class KeyboardInputMixin { - /** - * 注入到tick的末尾,重新计算 leftImpulse 和 forwardImpulse 的值 - */ - @Inject(method="tick", at=@At(value="TAIL")) - @PerformanceSensitive - public void tick_tail (boolean isMoveSlowly, float sneakingSpeedBonus, CallbackInfo ci) { - KeyboardInput that = ((KeyboardInput)(Object)this); - if (ThirdPerson.isAvailable() && ThirdPersonStatus.isRenderingInThirdPerson() && ThirdPerson.ENTITY_AGENT.isControlled()) { - // 相机坐标系下的impulse - double cameraLookImpulse = (that.up ? 1: 0) - (that.down ? 1: 0); - double cameraLeftImpulse = (that.left ? 1: 0) - (that.right ? 1: 0); - // 计算世界坐标系下的向前和向左 impulse - Vector3d lookImpulse = LMath.toVector3d(ThirdPerson.CAMERA_AGENT.getFakeCamera().getLookVector()).normalize(); // 视线向量 - Vector3d leftImpulse = LMath.toVector3d(ThirdPerson.CAMERA_AGENT.getFakeCamera().getLeftVector()).normalize(); - Vector2d lookImpulseHorizon = Vector2d.of(lookImpulse.x(), lookImpulse.z()).normalize(); // 水平方向上的视线向量 - Vector2d leftImpulseHorizon = Vector2d.of(leftImpulse.x(), leftImpulse.z()).normalize(); - lookImpulse.mul(cameraLookImpulse); // 这才是 impulse - leftImpulse.mul(cameraLeftImpulse); - lookImpulseHorizon.mul(cameraLookImpulse); // 水平 impulse - leftImpulseHorizon.mul(cameraLeftImpulse); - // 世界坐标系下的 impulse - lookImpulse.add(leftImpulse, ThirdPersonStatus.impulse); - lookImpulseHorizon.add(leftImpulseHorizon, ThirdPersonStatus.impulseHorizon); - // impulse 不为0, - if (ThirdPersonStatus.impulseHorizon.length() > 1E-5) { - ThirdPersonStatus.impulseHorizon.normalize(); - float playerYRot = ThirdPerson.ENTITY_AGENT.getRawPlayerEntity().getViewYRot(ThirdPersonStatus.lastPartialTick); - Vector2d playerLookHorizon = LMath.directionFromRotationDegree(playerYRot).normalize(); - Vector2d playerLeftHorizon = LMath.directionFromRotationDegree(playerYRot - 90).normalize(); - that.forwardImpulse = (float)(ThirdPersonStatus.impulseHorizon.dot(playerLookHorizon)); - that.leftImpulse = (float)(ThirdPersonStatus.impulseHorizon.dot(playerLeftHorizon)); - if (isMoveSlowly) { - that.forwardImpulse *= sneakingSpeedBonus; - that.leftImpulse *= sneakingSpeedBonus; - } - } - } - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/LevelRendererMixin.java b/common/src/main/java/net/leawind/mc/thirdperson/mixin/LevelRendererMixin.java deleted file mode 100644 index 464de9db..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/LevelRendererMixin.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.leawind.mc.thirdperson.mixin; - - -import com.mojang.blaze3d.vertex.PoseStack; -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonStatus; -import net.minecraft.client.renderer.LevelRenderer; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.world.entity.Entity; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(value=LevelRenderer.class, priority=2000) -public class LevelRendererMixin { - /** - * 如果不透明度足够低则取消渲染实体 - */ - @Inject(method="renderEntity", at=@At("HEAD"), cancellable=true) - public void renderEntity_head (Entity entity, double x, double y, double z, float partialTick, PoseStack poseStack, MultiBufferSource multiBufferSource, CallbackInfo ci) { - if (entity == ThirdPerson.ENTITY_AGENT.getRawCameraEntity()) { - if (!ThirdPersonStatus.shouldRenderCameraEntity()) { - ci.cancel(); - } - } - } - - @Inject(method="renderEntity", at=@At("TAIL")) - public void renderEntity_tail (Entity entity, double x, double y, double z, float partialTick, PoseStack poseStack, MultiBufferSource multiBufferSource, CallbackInfo ci) { - if (entity == ThirdPerson.ENTITY_AGENT.getRawCameraEntity()) { - if (ThirdPersonStatus.shouldRenderCameraEntity()) { - ((MultiBufferSource.BufferSource)multiBufferSource).endLastBatch(); - } - } - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/LocalPlayerInvoker.java b/common/src/main/java/net/leawind/mc/thirdperson/mixin/LocalPlayerInvoker.java deleted file mode 100644 index b12a7b97..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/LocalPlayerInvoker.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.leawind.mc.thirdperson.mixin; - - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Invoker; - -@Mixin(net.minecraft.client.player.LocalPlayer.class) -public interface LocalPlayerInvoker { - @Invoker("isControlledCamera") - boolean invokeIsControlledCamera (); -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/MinecraftMixin.java b/common/src/main/java/net/leawind/mc/thirdperson/mixin/MinecraftMixin.java deleted file mode 100644 index a1564729..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/MinecraftMixin.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.leawind.mc.thirdperson.mixin; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonEvents; -import net.minecraft.client.Minecraft; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -/** - * handleKeybinds 方法中会处理各种按键事件, 其中包括鼠标使用、攻击、选取按键 - *

    - */ -@Mixin(value=net.minecraft.client.Minecraft.class, priority=2000) -public class MinecraftMixin { - /** - * 注入到 handleKeybinds 头部,触发相应事件 - */ - @Inject(method="handleKeybinds", at=@At(value="HEAD")) - public void handleKeybinds_head (CallbackInfo ci) { - if (ThirdPerson.isAvailable()) { - ThirdPersonEvents.onBeforeHandleKeybinds(Minecraft.getInstance()); - } - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/ModelPartCubeMixin.java b/common/src/main/java/net/leawind/mc/thirdperson/mixin/ModelPartCubeMixin.java deleted file mode 100644 index 299d9557..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/ModelPartCubeMixin.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.leawind.mc.thirdperson.mixin; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonStatus; -import net.minecraft.client.model.geom.ModelPart; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyVariable; - -@Mixin(value=ModelPart.Cube.class, priority=2000) -public class ModelPartCubeMixin { - @ModifyVariable(at=@At("HEAD"), method="compile", index=8, argsOnly=true) - public float compile (float opacity) { - return ThirdPersonStatus.isRenderingInThirdPerson() ? Math.min(opacity, ThirdPerson.ENTITY_AGENT.getSmoothOpacity()): opacity; - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/MouseHandlerMixin.java b/common/src/main/java/net/leawind/mc/thirdperson/mixin/MouseHandlerMixin.java deleted file mode 100644 index decfb2cb..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/MouseHandlerMixin.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.leawind.mc.thirdperson.mixin; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonEvents; -import net.leawind.mc.thirdperson.ThirdPersonStatus; -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.minecraft.client.MouseHandler; -import net.minecraft.client.player.LocalPlayer; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(value=MouseHandler.class, priority=2000) -public class MouseHandlerMixin { - @Shadow private double accumulatedDX; - @Shadow private double accumulatedDY; - - /** - * 在 MouseHandler 尝试转动玩家前,阻止其行为。 - *

    - * 如果此时相机跟随玩家旋转,那么不作更改 - *

    - * 如果此时正在调整相机,那么不转动玩家,而是触发转动相机事件 - *

    - * 处理完后要重置累积变化量(accumulatedDX|Y) - */ - @Inject(method="turnPlayer()V", at=@At(value="HEAD"), cancellable=true) - public void turnPlayer_head (CallbackInfo ci) { - if (ThirdPerson.isAvailable() && ThirdPersonStatus.isAdjustingCameraOffset() && !ThirdPersonStatus.shouldCameraTurnWithEntity()) { - ThirdPersonEvents.onAdjustingCameraOffset(Vector2d.of(accumulatedDX, accumulatedDY)); - accumulatedDX = 0; - accumulatedDY = 0; - ci.cancel(); - } - } - - /** - * 在计算完dx,dy之后,原本会调用LocalPlayer.turn方法来旋转玩家 - *

    - * 这里可以重定向该方法,改成旋转咱的相机 - * - * @param dx x轴角度(俯仰角)变化量 - * @param dy y轴角度(偏航角)变化量 - */ - @Redirect(method="turnPlayer()V", at=@At(value="INVOKE", target="Lnet/minecraft/client/player/LocalPlayer;turn(DD)V")) - public void turnPlayer_invoke (LocalPlayer instance, double dy, double dx) { - if (ThirdPerson.isAvailable() && ThirdPersonStatus.isRenderingInThirdPerson() && !ThirdPersonStatus.shouldCameraTurnWithEntity()) { - ThirdPerson.CAMERA_AGENT.onCameraTurn(dy, dx); - } else { - ThirdPerson.ENTITY_AGENT.getRawPlayerEntity().turn(dy, dx); - } - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/mixin/RenderTypeMixin.java b/common/src/main/java/net/leawind/mc/thirdperson/mixin/RenderTypeMixin.java deleted file mode 100644 index 480f493e..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/mixin/RenderTypeMixin.java +++ /dev/null @@ -1,49 +0,0 @@ -package net.leawind.mc.thirdperson.mixin; - - -import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import com.mojang.blaze3d.vertex.VertexFormat; -import net.leawind.mc.thirdperson.api.core.EntityAgent; -import net.leawind.mc.util.annotations.VersionSensitive; -import net.minecraft.Util; -import net.minecraft.client.renderer.RenderStateShard; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.resources.ResourceLocation; -import org.jetbrains.annotations.NotNull; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.function.Function; - -@Mixin(value=RenderType.class, priority=2000) -public class RenderTypeMixin extends RenderStateShard { - public RenderTypeMixin (String name, Runnable setupState, Runnable clearState) { - super(name, setupState, clearState); - } - - /** - * 修改自 net.minecraft.client.renderer.RenderType#ARMOR_CUTOUT_NO_CULL - *

    - * 将 NO_TRANSPARENCY 改成了 TRANSLUCENT_TRANSPARENCY - */ - @Unique private static final Function ARMOR_CUTOUT_NO_CULL_TRANSLUCENT = Util.memoize((resourceLocation) -> { - RenderType.CompositeState compositeState = RenderType.CompositeState.builder().setShaderState(RENDERTYPE_ARMOR_CUTOUT_NO_CULL_SHADER).setTextureState(new RenderStateShard.TextureStateShard(resourceLocation, false, false)).setTransparencyState( - TRANSLUCENT_TRANSPARENCY).setCullState(NO_CULL).setLightmapState(LIGHTMAP).setOverlayState(OVERLAY).setLayeringState(VIEW_OFFSET_Z_LAYERING).createCompositeState(true); - return RenderType.create("armor_cutout_no_cull", DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, true, false, compositeState); - }); - - /** - * 对盔甲和鞘翅使用自定义的 RenderType 提供器,实现半透明效果 - * - * @see ModelPartCubeMixin#compile(float) - * @see EntityAgent#getSmoothOpacity() - */ - @VersionSensitive - @Inject(method="armorCutoutNoCull", at=@At(value="HEAD", target="Ljava/util/function/Function;apply(Ljava/lang/Object;)Ljava/lang/Object;"), cancellable=true) - private static void setTransparencyState (ResourceLocation resourceLocation, @NotNull CallbackInfoReturnable cir) { - cir.setReturnValue(ARMOR_CUTOUT_NO_CULL_TRANSLUCENT.apply(resourceLocation)); - } -} diff --git a/common/src/main/java/net/leawind/mc/thirdperson/resources/ItemPatternManager.java b/common/src/main/java/net/leawind/mc/thirdperson/resources/ItemPatternManager.java deleted file mode 100644 index 13200ada..00000000 --- a/common/src/main/java/net/leawind/mc/thirdperson/resources/ItemPatternManager.java +++ /dev/null @@ -1,96 +0,0 @@ -package net.leawind.mc.thirdperson.resources; - - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.api.config.Config; -import net.leawind.mc.util.annotations.VersionSensitive; -import net.leawind.mc.util.itempattern.ItemPattern; -import net.minecraft.client.resources.SplashManager; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.MultiPackResourceManager; -import net.minecraft.server.packs.resources.ResourceManager; -import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; -import net.minecraft.util.profiling.ProfilerFiller; -import net.minecraft.world.level.storage.loot.LootTables; -import org.jetbrains.annotations.NotNull; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * 物品模式管理器 - *

    - * 物品模式采用json格式存储。 - *

    - * 重载资源包时,mc会调用{@link ItemPatternManager#apply}方法处理读取到的json数据。 - * - * @see ItemPattern - * @see LootTables - * @see SplashManager - */ -@VersionSensitive("SimpleJsonResourceReloadListener may not exist in other mc version") -public class ItemPatternManager extends SimpleJsonResourceReloadListener { - private static final Gson GSON = new GsonBuilder().create(); - public static final String ID = "item_patterns"; - private static final String SET_HOLD_TO_AIM = "hold_to_aim"; - private static final String SET_USE_TO_AIM = "use_to_aim"; - private static final String SET_USE_TO_FIRST_PERSON = "use_to_first_person"; - public final Set holdToAimItemPatterns = new HashSet<>(); - public final Set useToAimItemPatterns = new HashSet<>(); - public final Set useToFirstPersonItemPatterns = new HashSet<>(); - - public ItemPatternManager () { - super(GSON, ID); - } - - /** - * 重载资源包时会调用此方法,处理资源包中的json数据 - * - * @param map 资源地址与json数据的映射表 - * @param resourceManager {@link MultiPackResourceManager}的实例 - * @see Config#updateItemPatterns() - */ - @Override - public void apply (@NotNull Map map, ResourceManager resourceManager, ProfilerFiller profile) { - holdToAimItemPatterns.clear(); - useToAimItemPatterns.clear(); - useToFirstPersonItemPatterns.clear(); - map.forEach((resourceLocation, jsonElement) -> { - JsonArray aimingCheckObj = jsonElement.getAsJsonArray(); - String[] resourcePath = resourceLocation.getPath().split("/"); - if (resourcePath.length >= 2) { - String resourceSetName = resourcePath[0]; - switch (resourceSetName) { - case SET_HOLD_TO_AIM -> { - int count = addToSet(holdToAimItemPatterns, aimingCheckObj); - ThirdPerson.LOGGER.info("Loaded {} hold_to_aim item patterns from {}", count, resourceLocation); - } - case SET_USE_TO_AIM -> { - int count = addToSet(useToAimItemPatterns, aimingCheckObj); - ThirdPerson.LOGGER.info("Loaded {} use_to_aim item patterns from {}", count, resourceLocation); - } - case SET_USE_TO_FIRST_PERSON -> { - int count = addToSet(useToFirstPersonItemPatterns, aimingCheckObj); - ThirdPerson.LOGGER.info("Loaded {} use_to_first_person item patterns from {}", count, resourceLocation); - } - } - } - }); - } - - private int addToSet (@NotNull Set set, @NotNull JsonArray arr) { - arr.forEach(ele -> { - try { - set.add(ItemPattern.of(ele.getAsString())); - } catch (Exception e) { - ThirdPerson.LOGGER.warn(e.getMessage()); - } - }); - return arr.size(); - } -} diff --git a/common/src/main/java/net/leawind/mc/util/OptionalFunction.java b/common/src/main/java/net/leawind/mc/util/OptionalFunction.java deleted file mode 100644 index fb6d1031..00000000 --- a/common/src/main/java/net/leawind/mc/util/OptionalFunction.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.leawind.mc.util; - - -import java.util.function.Supplier; - -public interface OptionalFunction { - static OptionalFunction of (Supplier supplier, Supplier availablePredicate) { - return new OptionalFunction() { - @Override - public E get () { - return supplier.get(); - } - - @Override - public boolean isAvailable () { - return availablePredicate.get(); - } - }; - } - - T get (); - - boolean isAvailable (); -} diff --git a/common/src/main/java/net/leawind/mc/util/annotations/VersionSensitive.java b/common/src/main/java/net/leawind/mc/util/annotations/VersionSensitive.java deleted file mode 100644 index 89a78de9..00000000 --- a/common/src/main/java/net/leawind/mc/util/annotations/VersionSensitive.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.leawind.mc.util.annotations; - - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * 用于标记版本敏感内容 - *

    - * 有些内容很可能需要随着Minecraft版本更新而更新。 - *

    - * 对这类内容使用此注解,以便在将此模组移植到其他MC版本时检查相关内容。 - *

    - * 通常应当对实现方法,而非接口方法使用此注解。 - */ -@Retention(RetentionPolicy.SOURCE) -public @interface VersionSensitive { - String value () default ""; -} diff --git a/common/src/main/java/net/leawind/mc/util/itempattern/ItemPattern.java b/common/src/main/java/net/leawind/mc/util/itempattern/ItemPattern.java deleted file mode 100644 index 2d65d310..00000000 --- a/common/src/main/java/net/leawind/mc/util/itempattern/ItemPattern.java +++ /dev/null @@ -1,234 +0,0 @@ -package net.leawind.mc.util.itempattern; - - -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import net.leawind.mc.thirdperson.ThirdPerson; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtUtils; -import net.minecraft.nbt.Tag; -import net.minecraft.nbt.TagParser; -import net.minecraft.network.chat.Component; -import net.minecraft.world.item.ItemStack; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Optional; -import java.util.Set; -import java.util.regex.Pattern; - -/** - * 物品模式 - *

    - * 可用于根据物品 id 和 nbt 标签匹配物品 - *

    - * 使用 {@link ItemPattern#of(String)} 或 {@link ItemPattern#of(String, String)} 构建实例 - *

    - * 示例: - *

    - * // 获取玩家手持物品
    - * ItemStack   item        = Minecraft.getInstance().player.getMainHandItem();
    - * // 创建物品模式实例
    - * ItemPattern pattern     = ItemPattern.of("crossbow{Charged:1b}");	// 已装填的弩
    - * // 测试物品是否匹配该模式
    - * boolean     matchResult = pattern.match(item);
    - * 
    - */ -@SuppressWarnings("unused") -public interface ItemPattern { - /** - * 正规的物品描述符 - */ - Pattern RGX_REGULAR_ID = Pattern.compile("^item\\.[a-z_]+\\.[a-z_]+$"); - /** - * 纯物品名,没有命名空间。 - *

    - * 例如 bread - */ - Pattern RGX_PURE_ID = Pattern.compile("^[a-z_]+$"); - /** - * <命名空间>:<物品ID> - *

    - * 例如 minecraft:apple - */ - Pattern RGX_NAMESPACE_ID = Pattern.compile("^[a-z_]+[.:][a-z_]+$"); - /** - * 宽松规则的物品ID加上NBT标签 - *

    - * 例如 crossbow{Charged:1b} - */ - Pattern RGX_ID_NBT = Pattern.compile("^[a-z.:_]+\\{.*}$"); - /** - * 宽松规则的物品ID - */ - Pattern RGX_ID = Pattern.compile("^[a-z.:_]+$"); - /** - * NBT标签表达式 - */ - Pattern RGX_NBT = Pattern.compile("^\\{.*}$"); - /** - * 匹配一切物品 - */ - ItemPattern ANY = of(null, null); - - @SafeVarargs - static boolean anyMatch (@Nullable ItemStack itemStack, Iterable @NotNull ... itemPatternsList) { - for (Iterable patterns: itemPatternsList) { - for (ItemPattern ip: patterns) { - if (ip.match(itemStack)) { - return true; - } - } - } - return false; - } - - /** - * 如果规则表达式不合语法,则提供相关错误信息 - * - * @param expression 表达式 - * @return 错误信息,空值表示没有错误 - */ - static @NotNull Optional supplyError (@Nullable String expression) { - try { - of(expression); - return Optional.empty(); - } catch (IllegalArgumentException e) { - return Optional.of(Component.literal(e.getMessage())); - } - } - - /** - * 由宽松规则的表达式创建物品模式对象。 - *

    - * 表达式示例: - *

    - * snowball - *

    - * crossbow{Charged:1b} - *

    - * minecraft:crossbow{Charged:1b} - *

    - * item.minecraft.crossbow{Charged:1b} - */ - @Contract("_ -> new") - static @NotNull ItemPattern of (@Nullable String expression) { - if (expression == null) { - return ANY; - } else if (RGX_ID.matcher(expression).matches()) { - return of(expression, null); - } else if (RGX_ID_NBT.matcher(expression).matches()) { - int i = expression.indexOf('{'); - return of(expression.substring(0, i), expression.substring(i)); - } else if (RGX_NBT.matcher(expression).matches()) { - return of(null, expression); - } else { - throw new IllegalArgumentException(String.format("Invalid item pattern expression: %s", expression)); - } - } - - /** - * @param idExp 宽松规则的物品ID - * @param tagExp NBT复合标签表达式 - */ - @Contract("_,_ -> new") - static @NotNull ItemPattern of (@Nullable String idExp, @Nullable String tagExp) { - return new ItemPatternImpl(parseDescriptionId(idExp), parsePatternTag(tagExp)); - } - - /** - * 将宽松规则的物品ID解析为严格的descriptionId。 - *

    - * 允许 4 种输入格式: - *

  • null,解析结果也将是null
  • - *
  • "ID",例如 snowball
  • - *
  • "命名空间:ID",例如 minecraft:snowball
  • - *
  • "descriptionId",例如 item.minecraft.snowball
  • - * - * @param idExp 宽松规则的物品ID,允许3种格式: - */ - @Contract("null->null") - static @Nullable String parseDescriptionId (@Nullable String idExp) { - if (idExp == null || idExp.isEmpty()) { - return null; - } else if (RGX_REGULAR_ID.matcher(idExp).matches()) { - return idExp; - } else if (RGX_PURE_ID.matcher(idExp).matches()) { - return "item.minecraft." + idExp; - } else if (RGX_NAMESPACE_ID.matcher(idExp).matches()) { - return "item." + idExp.replace(':', '.'); - } else { - throw new IllegalArgumentException("Invalid item description id: " + idExp); - } - } - - /** - * 解析给定的标签表达式,并在成功时返回一个CompoundTag。 - * - * @param tagExp 要解析的标签表达式 - * @return 解析后的CompoundTag,如果tagExp为null则返回null - */ - @Contract("null->null; !null ->new") - static @Nullable CompoundTag parsePatternTag (@Nullable String tagExp) { - if (tagExp == null) { - return null; - } - try { - return TagParser.parseTag(tagExp); - } catch (CommandSyntaxException exception) { - throw new IllegalArgumentException(String.format("Invalid NBT expression: %s\n%s", tagExp, exception.getMessage())); - } - } - - /** - * 迭代解析表达式,并将解析的物品模式添加到指定集合, - * - * @param itemPatterns 要添加到的物品模式集合 - * @param expressions 包含表达式的可迭代对象 - */ - static int addToSet (@NotNull Set itemPatterns, @Nullable Iterable expressions) { - int count = 0; - if (expressions != null) { - for (String expression: expressions) { - try { - itemPatterns.add(of(expression)); - count++; - } catch (IllegalArgumentException e) { - ThirdPerson.LOGGER.error("Skip invalid item pattern expression: {}", expression); - } - } - } - return count; - } - - /** - * 匹配物品 - *

    - * 仅当 id 和 nbt 都匹配成功时,才匹配成功 - * - * @param itemStack 物品槽 - */ - default boolean match (@Nullable ItemStack itemStack) { - return matchId(itemStack) && matchNbt(itemStack); - } - - /** - * 根据id匹配物品。 - *

    - * 当 id 为 null 时总是匹配成功 - *

    - * 否则只有当id相同时才匹配成功 - * - * @param itemStack 物品槽 - */ - boolean matchId (@Nullable ItemStack itemStack); - - /** - * 根据 nbt 标签匹配物品 - *

    - * 使用 {@link NbtUtils#compareNbt(Tag, Tag, boolean)} 进行匹配 - * - * @param itemStack 物品槽 - */ - boolean matchNbt (@Nullable ItemStack itemStack); -} diff --git a/common/src/main/java/net/leawind/mc/util/itempattern/ItemPatternImpl.java b/common/src/main/java/net/leawind/mc/util/itempattern/ItemPatternImpl.java deleted file mode 100644 index aab3c65f..00000000 --- a/common/src/main/java/net/leawind/mc/util/itempattern/ItemPatternImpl.java +++ /dev/null @@ -1,81 +0,0 @@ -package net.leawind.mc.util.itempattern; - - -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtUtils; -import net.minecraft.world.item.ItemStack; -import org.jetbrains.annotations.Nullable; - -import java.util.Objects; - -public class ItemPatternImpl implements ItemPattern { - /** - * 物品描述标识符 - *

    - * 可通过 {@link net.minecraft.world.item.ItemStack#getDescriptionId()} 获取 - */ - private final @Nullable String descriptionId; - /** - * 用于匹配物品的NBT标签。null 表示匹配任意标签。 - */ - private final @Nullable CompoundTag tag; - /** - * NBT标签的表达式 - */ - private final @Nullable String tagExp; - private final int hashCode; - - /** - * @param descriptionId 正规的 descriptionId - * @param patternTag NBT模式标签 - */ - public ItemPatternImpl (@Nullable String descriptionId, @Nullable CompoundTag patternTag) { - if (!(descriptionId == null || RGX_REGULAR_ID.matcher(descriptionId).matches())) { - throw new IllegalArgumentException(String.format("Irregular item description id: %s", descriptionId)); - } - this.descriptionId = descriptionId; - this.tag = patternTag; - this.tagExp = patternTag == null ? null: patternTag.getAsString(); - this.hashCode = Objects.hashCode(descriptionId) ^ Objects.hashCode(tagExp); - } - - @Override - public boolean matchId (@Nullable ItemStack itemStack) { - if (descriptionId == null) { - return true; - } else if (itemStack == null) { - return false; - } else { - return descriptionId.equals(itemStack.getDescriptionId()); - } - } - - @Override - public boolean matchNbt (@Nullable ItemStack itemStack) { - CompoundTag itemTag = itemStack == null ? null: itemStack.getTag(); - return NbtUtils.compareNbt(this.tag, itemTag, true); - } - - @Override - public int hashCode () { - return hashCode; - } - - @Override - public boolean equals (Object obj) { - if (this == obj) { - return true; - } else if (obj == null) { - return false; - } else if (getClass() != obj.getClass()) { - return false; - } - ItemPatternImpl ip = (ItemPatternImpl)obj; - return Objects.equals(descriptionId, ip.descriptionId) && Objects.equals(tagExp, ip.tagExp); - } - - @Override - public String toString () { - return (descriptionId == null ? "": descriptionId) + (tagExp == null ? "": tagExp); - } -} diff --git a/common/src/main/java/net/leawind/mc/util/math/LMath.java b/common/src/main/java/net/leawind/mc/util/math/LMath.java deleted file mode 100644 index 6b374137..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/LMath.java +++ /dev/null @@ -1,177 +0,0 @@ -package net.leawind.mc.util.math; - - -import com.mojang.math.Vector3f; -import net.leawind.mc.util.math.vector.api.Vector2d; -import net.leawind.mc.util.math.vector.api.Vector3d; -import net.minecraft.world.phys.Vec3; -import org.jetbrains.annotations.Contract; - -@SuppressWarnings("unused") -public interface LMath { - /** - * 将一个向量相对原本方向旋转一定弧度 - * - * @param vec 原向量 - * @param dy 偏航角变化量(弧度制) - * @param dx 俯仰角变化量(弧度制) - */ - @Contract(pure=true) - static Vector3d rotateRadian (Vector3d vec, float dy, float dx) { - return directionFromRotationDegree(rotationRadianFromDirection(vec).add(Vector2d.of(dx, dy))).mul(vec.length()); - } - - @Contract(pure=true) - static Vector3d directionFromRotationDegree (Vector2d r) { - return directionFromRotationDegree(r.x(), r.y()); - } - - /** - * 弧度制 - * - * @param d 方向 - * @return [x=俯仰角, y=偏航角] - */ - @Contract(pure=true) - static Vector2d rotationRadianFromDirection (Vector3d d) { - d.normalize(); - return Vector2d.of(-Math.asin(d.y()), Math.atan2(-d.x(), d.z())); - } - - @Contract(pure=true) - static Vector3d directionFromRotationDegree (double x, double y) { - double h = Math.cos(-y * 0.017453292519943295 - Math.PI); - double i = Math.sin(-y * 0.017453292519943295 - Math.PI); - double j = -Math.cos(-x * 0.017453292519943295); - double k = Math.sin(-x * 0.017453292519943295); - return Vector3d.of(i * j, k, h * j); - } - - /** - * 将一个向量相对原本方向旋转一定弧度 - * - * @param vec 原向量 - * @param rotation 弧度变化量(弧度制) - */ - @Contract(pure=true) - static Vector3d rotateRadian (Vector3d vec, Vector2d rotation) { - return directionFromRotationDegree(rotationRadianFromDirection(vec).add(rotation)).mul(vec.length()); - } - - /** - * 将一个向量相对原本方向旋转一定角度 - * - * @param vec 原向量 - * @param dy 偏航角变化量(角度制) - * @param dx 俯仰角变化量(角度制) - */ - @Contract(pure=true) - static Vector3d rotateDegree (Vector3d vec, double dy, double dx) { - return directionFromRotationDegree(rotationDegreeFromDirection(vec).add(Vector2d.of(dx, dy))).mul(vec.length()); - } - - /** - * 角度制 - * - * @param d 方向 - * @return [x=俯仰角, y=偏航角] - */ - @Contract(pure=true) - static Vector2d rotationDegreeFromDirection (Vector3d d) { - d.normalize(); - return Vector2d.of((-Math.toDegrees(Math.asin(d.y()))), Math.toDegrees(Math.atan2(-d.x(), d.z()))); - } - - /** - * 将一个向量相对原本方向旋转一定角度 - * - * @param vec 原向量 - * @param rotationAngle 角度变化量(角度制) - */ - @Contract(pure=true) - static Vector3d rotateDegree (Vector3d vec, Vector2d rotationAngle) { - return directionFromRotationDegree(rotationDegreeFromDirection(vec).add(rotationAngle)).mul(vec.length()); - } - - @Contract(pure=true) - static double rotationDegreeFromDirection (Vector2d d) { - return -Math.toDegrees(Math.atan2(d.x(), d.y())); - } - - @Contract(pure=true) - static Vector2d directionFromRotationDegree (double yRot) { - double x = Math.sin(yRot * 0.017453292519943295 + Math.PI); - double z = -Math.cos(yRot * 0.017453292519943295 + Math.PI); - return Vector2d.of(x, z); - } - - @Contract(pure=true) - static Vector3d toVector3d (Vec3 v) { - return Vector3d.of(v.x, v.y, v.z); - } - - @Contract(pure=true) - static Vector3d toVector3d (Vector3f v) { - return Vector3d.of(v.x(), v.y(), v.z()); - } - - @Contract(pure=true) - static Vec3 toVec3 (Vector3d v) { - return new Vec3(v.x(), v.y(), v.z()); - } - - @Contract(pure=true) - static int clamp (int d, int min, int max) { - return d < min ? min: Math.min(d, max); - } - - @Contract(pure=true) - static long clamp (long d, long min, long max) { - return d < min ? min: Math.min(d, max); - } - - @Contract(pure=true) - static float clamp (float d, float min, float max) { - return d < min ? min: Math.min(d, max); - } - - @Contract(pure=true) - static void clamp (Vector2d v, double min, double max) { - v.set(clamp(v.x(), min, max), clamp(v.y(), min, max)); - } - - @Contract(pure=true) - static double clamp (double d, double min, double max) { - return d < min ? min: Math.min(d, max); - } - - @Contract(pure=true) - static double lerp (double start, double end, double t) { - return start + t * (end - start); - } - - @Contract(pure=true) - static double floorMod (double x, double y) { - return ((x % y) + y) % y; - } - - @Contract(pure=true) - static float floorMod (float x, float y) { - return ((x % y) + y) % y; - } - - @Contract(pure=true) - static int floorMod (int x, int y) { - return Math.floorMod(x, y); - } - - @Contract(pure=true) - static int floorMod (long x, int y) { - return Math.floorMod(x, y); - } - - @Contract(pure=true) - static long floorMod (long x, long y) { - return Math.floorMod(x, y); - } -} diff --git a/common/src/main/java/net/leawind/mc/util/math/decisionmap/api/DecisionFactor.java b/common/src/main/java/net/leawind/mc/util/math/decisionmap/api/DecisionFactor.java deleted file mode 100644 index 86f772e8..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/decisionmap/api/DecisionFactor.java +++ /dev/null @@ -1,50 +0,0 @@ -package net.leawind.mc.util.math.decisionmap.api; - - -import net.leawind.mc.util.math.decisionmap.impl.DecisionFactorImpl; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.util.function.BooleanSupplier; - -/** - * 决策因素 - */ -public interface DecisionFactor { - @Contract("_ -> new") - static @NotNull DecisionFactor of (BooleanSupplier getter) { - return new DecisionFactorImpl(getter); - } - - /** - * 重新计算因素的值 - */ - @Contract("-> this") - DecisionFactor update (); - - /** - * 获取上次的计算结果 - */ - boolean get (); - - @NotNull String getName (); - - void setName (@NotNull String name); - - /** - * 设置索引 - */ - void setIndex (int index); - - /** - * 在所有因素中的索引 - */ - int index (); - - /** - * 掩码,即 1< { - int MAX_FACTOR_COUNT = 32; - - @Contract("_ -> new") - static @NotNull DecisionMap of (@NotNull Class clazz) { - return new DecisionMapImpl<>(clazz); - } - - static int toFlagBits (boolean @NotNull [] flagList) { - int flagBits = 0; - for (int i = 0; i < flagList.length; i++) { - if (flagList[i]) { - flagBits |= 1 << i; - } - } - return flagBits; - } - - void reset (); - - /** - * 更新因素 - */ - @Contract("-> this") - DecisionMap updateFactors (); - - /** - * 不更新因素,直接做出决策 - */ - T make (); - - /** - * 获取因素对应的决策 - * - * @param flagBits 因素位图 - */ - @NotNull Optional> getStrategy (int flagBits); - - /** - * 更新因素并做出决策 - */ - T remake (); - - /** - * 获取因素数量 - */ - int getFactorCount (); - - /** - * 2^因素数量 - *

    - * 每个因素有2中可能性,返回所有可能性总数 - */ - int getMapSize (); - - /** - * 构建 - */ - @Contract("-> this") - @NotNull DecisionMap build (); - - /** - * 是否已经构建 - */ - boolean isBuilt (); - - @Contract("_-> this") - @NotNull DecisionMap addRule (@NotNull Function> func); - - /** - * 添加规则 - *

    - * 此方法会立即使之前在此对象上调用的所有{@link DecisionMap#addRule}方法失效 - * - * @param func 根据输入的各个因素返回相应的决策函数 - *

    - *

  • Integer flag bits
  • - *
  • boolean[] flag list
  • - * @return 原对象 - */ - @Contract("_-> this") - @NotNull DecisionMap addRule (@NotNull BiFunction> func); - - /** - * 添加规则 - * - * @param strategy 决策方法 - * @return 原对象 - */ - @Contract("_,_ -> this") - @NotNull DecisionMap addRule (int flagBits, @NotNull Supplier strategy); - - /** - * 添加规则 - * - * @param flagBits 因素列表 - * @param mask 掩码 - * @param strategy 决策方法 - * @return 原对象 - */ - @Contract("_,_,_ -> this") - @NotNull DecisionMap addRule (int flagBits, int mask, @NotNull Supplier strategy); -} diff --git a/common/src/main/java/net/leawind/mc/util/math/decisionmap/api/anno/ADecisionFactor.java b/common/src/main/java/net/leawind/mc/util/math/decisionmap/api/anno/ADecisionFactor.java deleted file mode 100644 index f21a84ad..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/decisionmap/api/anno/ADecisionFactor.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.leawind.mc.util.math.decisionmap.api.anno; - - -import net.leawind.mc.util.math.decisionmap.api.DecisionMap; - -import java.lang.annotation.*; - -/** - * 决策因素 - *

    - * 只有拥有此注解的字段会被视为决策因素 - *

    - * value表示此字段的索引。若不同字段指定了相同索引,将会抛出异常。 - *

    - * 如果value为-1,将根据mask的值计算索引。 - *

    - * 如果mask为-1,则自动递增索引 - *

    - * 计算出的索引值不能重复,不能超过{@link DecisionMap#MAX_FACTOR_COUNT} - */ -@Documented -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -public @interface ADecisionFactor { - /** - * 期望索引 - */ - int value () default -1; - - /** - * 期望掩码 - */ - int mask () default -1; -} diff --git a/common/src/main/java/net/leawind/mc/util/math/decisionmap/impl/DecisionFactorImpl.java b/common/src/main/java/net/leawind/mc/util/math/decisionmap/impl/DecisionFactorImpl.java deleted file mode 100644 index a84f1609..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/decisionmap/impl/DecisionFactorImpl.java +++ /dev/null @@ -1,49 +0,0 @@ -package net.leawind.mc.util.math.decisionmap.impl; - - -import net.leawind.mc.util.math.decisionmap.api.DecisionFactor; -import org.jetbrains.annotations.NotNull; - -import java.util.function.BooleanSupplier; - -public class DecisionFactorImpl implements DecisionFactor { - private final @NotNull BooleanSupplier getter; - protected int index; - private boolean value = false; - private @NotNull String name = "unnamed"; - - public DecisionFactorImpl (@NotNull BooleanSupplier getter) { - this.getter = getter; - } - - @Override - public @NotNull DecisionFactor update () { - value = getter.getAsBoolean(); - return this; - } - - @Override - public boolean get () { - return value; - } - - @Override - public @NotNull String getName () { - return name; - } - - @Override - public void setName (@NotNull String name) { - this.name = name; - } - - @Override - public void setIndex (int index) { - this.index = index; - } - - @Override - public int index () { - return index; - } -} diff --git a/common/src/main/java/net/leawind/mc/util/math/decisionmap/impl/DecisionMapImpl.java b/common/src/main/java/net/leawind/mc/util/math/decisionmap/impl/DecisionMapImpl.java deleted file mode 100644 index e5e7787e..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/decisionmap/impl/DecisionMapImpl.java +++ /dev/null @@ -1,285 +0,0 @@ -package net.leawind.mc.util.math.decisionmap.impl; - - -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.util.math.decisionmap.api.DecisionFactor; -import net.leawind.mc.util.math.decisionmap.api.DecisionMap; -import net.leawind.mc.util.math.decisionmap.api.anno.ADecisionFactor; -import org.jetbrains.annotations.NotNull; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.*; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Supplier; - -public class DecisionMapImpl implements DecisionMap { - private final Class initializer; - /** - * 规则构建器们 - */ - private final List ruleBuilders = new LinkedList<>(); - /** - * 因素列表 - */ - private final @NotNull List factors = new ArrayList<>(); - /** - * 列出所有可能的情况的列表 - *

    - * flagBits -> Supplier - */ - private final @NotNull List> strategyMap = new ArrayList<>(); - private final Map, String> nameMap = new HashMap<>(); - /** - * 此对象是否已经构建 - */ - private boolean isBuilt = false; - private int flagBits = 0; - - /** - * 根据类中的定义构建决策表 - *

    - * 类中应当包含带有 ADecisionFactor 注解的公共静态字段 - *

    - * 还要包含 public static void build(DecisionMap) 方法 - *

    -	 * {@literal @ADecisionFactor public static final DecisionFactor is_swimming = DecisionFactor.of(()->false)}
    -	 * {@literal public static void build(DecisionMap\ map){}}
    -	 * 
    - */ - public DecisionMapImpl (@NotNull Class clazz) { - initializer = clazz; - // Register Factors - List adfListIndexed = new LinkedList<>(); - List adfListAutoIndex = new LinkedList<>(); - for (Field field: clazz.getDeclaredFields()) { - if (field.isAnnotationPresent(ADecisionFactor.class)) { - if (field.getType() != DecisionFactor.class) { - throw new RuntimeException(String.format("Type %s required, got %s", DecisionFactor.class, field)); - } else if (!Modifier.isStatic(field.getModifiers())) { - throw new RuntimeException(String.format("Static required: %s", field)); - } else if (!field.canAccess(null)) { - throw new RuntimeException(String.format("Cannot access field: %s", field)); - } - ADecisionFactor adf = field.getAnnotation(ADecisionFactor.class); - if (adf.value() == -1 && adf.mask() == -1) { - adfListAutoIndex.add(field); - } else { - adfListIndexed.add(field); - } - } else if (field.getType() == Supplier.class) { - try { - nameMap.put((Supplier)field.get(null), field.getName()); - } catch (IllegalAccessException ignored) { - } - } - } - int factorCount = adfListIndexed.size() + adfListAutoIndex.size(); - if (factorCount > MAX_FACTOR_COUNT) { - throw new RuntimeException(String.format("Too many (%d) DecisioinFactors in class %s", factorCount, clazz)); - } - while (factors.size() < factorCount) { - factors.add(null); - } - try { - for (Field field: adfListIndexed) { - ADecisionFactor adf = field.getAnnotation(ADecisionFactor.class); - int index = adf.value() != -1 ? adf.value(): Integer.numberOfTrailingZeros(adf.mask()); - if (factors.get(index) != null) { - throw new RuntimeException(String.format("Field %s: Index %d has been used already.", field, index)); - } - DecisionFactor df = (DecisionFactor)field.get(null); - df.setName(field.getName()); - factors.set(index, df); - } - for (Field field: adfListAutoIndex) { - for (int index = 0; index < factors.size(); index++) { - if (factors.get(index) == null) { - DecisionFactor df = (DecisionFactor)field.get(null); - df.setName(field.getName()); - factors.set(index, df); - df.setIndex(index); - break; - } - } - } - } catch (IllegalAccessException e) { - ThirdPerson.LOGGER.error("This should never happen!"); - throw new RuntimeException(e); - } - // Build - try { - Method building = getBuildMethod(clazz); - building.invoke(null, this); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - ThirdPerson.LOGGER.error("This should never happen!"); - throw new RuntimeException(e); - } - build(); - } - - private void assertBuilt (boolean expected) { - if (expected ^ isBuilt) { - throw new UnsupportedOperationException(isBuilt ? "DecisionMap has been built already.": "DecisionMap not built yet."); - } - } - - private static @NotNull Method getBuildMethod (@NotNull Class clazz) throws NoSuchMethodException { - Method method = clazz.getMethod("build", DecisionMap.class); - if (!Modifier.isStatic(method.getModifiers())) { - throw new RuntimeException(String.format("Expected static method %s", method)); - } else if (!method.canAccess(null)) { - throw new RuntimeException(String.format("Cannot access method %s", method)); - } else if (method.getReturnType() != void.class) { - throw new RuntimeException(String.format("Required return type void for method %s", method)); - } - return method; - } - - @Override - public void reset () { - ruleBuilders.clear(); - factors.clear(); - strategyMap.clear(); - isBuilt = false; - flagBits = 0; - } - - @Override - public String toString () { - StringBuilder sb = new StringBuilder(); - sb.append(String.format("%s from %s (factorCount=%d, mapSize=%d)\n", DecisionMap.class.getSimpleName(), initializer.getSimpleName(), getFactorCount(), getMapSize())); - sb.append("Factors:\n"); - for (int i = 0; i < factors.size(); i++) { - sb.append(String.format("\t[%d] %s\n", i, factors.get(i).getName())); - } - sb.append("Strategy Map:\n"); - for (int flagBits = 0; flagBits < getMapSize(); flagBits++) { - Supplier func = getStrategy(flagBits).orElse(null); - sb.append(String.format("\t%s %s\n", padStart(Integer.toBinaryString(flagBits), factors.size(), '0'), nameMap.getOrDefault(func, "unnamed"))); - } - return sb.toString(); - } - - private @NotNull String padStart (String s, int length, char filler) { - return String.format("%" + length + "s", s).replace(' ', filler); - } - - @Override - public DecisionMap updateFactors () { - flagBits = 0; - final int factorCount = getFactorCount(); - for (int i = 0; i < factorCount; i++) { - flagBits |= (factors.get(i).update().get() ? 1: 0) << i; - } - return this; - } - - @Override - public T make () { - assertBuilt(true); - return getStrategy(flagBits).map(Supplier::get).orElse(null); - } - - @Override - public @NotNull Optional> getStrategy (int flagBits) { - return Optional.ofNullable(strategyMap.get(flagBits)); - } - - @Override - public T remake () { - assertBuilt(true); - return updateFactors().make(); - } - - @Override - public int getFactorCount () { - return factors.size(); - } - - @Override - public int getMapSize () { - return (int)Math.pow(2, getFactorCount()); - } - - @Override - public @NotNull DecisionMap build () { - strategyMap.clear(); - while (strategyMap.size() < getMapSize()) { - strategyMap.add(null); - } - for (Runnable ruleBuilder: ruleBuilders) { - ruleBuilder.run(); - } - isBuilt = true; - return this; - } - - @Override - public boolean isBuilt () { - return isBuilt; - } - - @Override - public @NotNull DecisionMap addRule (@NotNull Function> func) { - assertBuilt(false); - ruleBuilders.clear(); - ruleBuilders.add(() -> { - int factorCount = getFactorCount(); - int mapSize = getMapSize(); - boolean[] params = new boolean[factorCount]; - for (int flagBits = 0; flagBits < mapSize; flagBits++) { - for (int factorIndex = 0; factorIndex < factorCount; factorIndex++) { - boolean factorValue = (flagBits & (1 << factorIndex)) > 0; - params[factorIndex] = factorValue; - } - strategyMap.set(flagBits, func.apply(params)); - } - }); - return this; - } - - @Override - public @NotNull DecisionMap addRule (@NotNull BiFunction> func) { - assertBuilt(false); - ruleBuilders.clear(); - ruleBuilders.add(() -> { - int factorCount = getFactorCount(); - int mapSize = getMapSize(); - boolean[] flagList = new boolean[factorCount]; - for (int flagBits = 0; flagBits < mapSize; flagBits++) { - for (int factorIndex = 0; factorIndex < factorCount; factorIndex++) { - boolean factorValue = (flagBits & (1 << factorIndex)) > 0; - flagList[factorIndex] = factorValue; - } - strategyMap.set(flagBits, func.apply(flagBits, flagList)); - } - }); - return this; - } - - @Override - public @NotNull DecisionMap addRule (int flagBits, @NotNull Supplier strategy) { - assertBuilt(false); - ruleBuilders.add(() -> strategyMap.set(flagBits, strategy)); - return this; - } - - @Override - public @NotNull DecisionMap addRule (int flagBits, int mask, @NotNull Supplier strategy) { - assertBuilt(false); - ruleBuilders.add(() -> { - int mapSize = strategyMap.size(); - int im = flagBits & mask; - for (int i = 0; i < mapSize; i++) { - if ((i & mask) == im) { - strategyMap.set(i, strategy); - } - } - }); - return this; - } -} diff --git a/common/src/main/java/net/leawind/mc/util/math/monolist/DeferedMonoList.java b/common/src/main/java/net/leawind/mc/util/math/monolist/DeferedMonoList.java deleted file mode 100644 index 210ad901..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/monolist/DeferedMonoList.java +++ /dev/null @@ -1,108 +0,0 @@ -package net.leawind.mc.util.math.monolist; - - -import net.leawind.mc.util.math.LMath; -import org.jetbrains.annotations.NotNull; - -import java.util.function.Function; - -/** - * 延迟计算的单调列表 - *

    - * 列表项是下标的单调函数 - *

    - * 数列中每一项的值只有在被访问时才计算 - */ -@SuppressWarnings("unused") -public class DeferedMonoList implements MonoList { - private final int length; - private final @NotNull Function getter; - private final int sgn; - - /** - * @param length 列表长度 - * @param getter 值与下标的对应关系 - */ - protected DeferedMonoList (int length, @NotNull Function getter) { - this.length = length; - this.getter = getter; - this.sgn = getter.apply(1) > getter.apply(0) ? 1: -1; - } - - public static @NotNull DeferedMonoList exp (int length) { - return new DeferedMonoList(length, Math::exp); - } - - public static @NotNull DeferedMonoList squared (int length) { - return new DeferedMonoList(length, i -> (double)(i * i)); - } - - public static @NotNull DeferedMonoList of (int length, @NotNull Function getter) { - return new DeferedMonoList(length, getter); - } - - @Override - public double offset (double value, int offset) { - int i = iadsorption(value) + offset; - i = LMath.clamp(i, 0, length() - 1); - return get(i); - } - - @Override - public int iadsorption (double value) { - int ileft = 0; - int iright = length() - 1; - int icenter = length() / 2; - while (true) { - double vi = get(icenter); - if (vi < value) { - ileft = icenter; - } else if (vi > value) { - iright = icenter; - } else { - return icenter; - } - if (iright - ileft == 1) { - break; - } - icenter = (ileft + iright) / 2; - } - double emin = value - get(ileft); - double emax = get(iright) - value; - if (emin <= emax) { - return ileft; - } else { - return iright; - } - } - - @Override - public double get (int i) { - return getter.apply(i); - } - - @Override - public double adsorption (double value) { - return get(iadsorption(value)); - } - - @Override - public int sgn () { - return sgn; - } - - @Override - public int length () { - return length; - } - - @Override - public double getNext (double value) { - return offset(value, 1); - } - - @Override - public double getLast (double value) { - return offset(value, -1); - } -} diff --git a/common/src/main/java/net/leawind/mc/util/math/monolist/MonoList.java b/common/src/main/java/net/leawind/mc/util/math/monolist/MonoList.java deleted file mode 100644 index cd10efe9..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/monolist/MonoList.java +++ /dev/null @@ -1,96 +0,0 @@ -package net.leawind.mc.util.math.monolist; - - -/** - * 单调列表 - *

    - * 列表中的数据是单调递增或单调递减的 - */ -@SuppressWarnings("unused") -public interface MonoList { - /** - * 获取下标对应的值 - */ - double get (int i); - - /** - * 计算当前值对应的下标,加上偏移量后获取值 - *

    - * 如果加上偏移量后超出范围,则取边缘的值(第一个或最后一个值) - *

    - * 例: - *

    -	 * {@code
    -	 * value ≈ B
    -	 * offset = 2
    -	 *
    -	 * 下标: | 0 | 1 | 2 | 3 | 4 |
    -	 * 数值: | A | B | C | D | E |
    -	 *             ↑       ↑
    -	 *           value     |
    -	 *                     |
    -	 *            index(value)+offset
    -	 * }
    -	 * 
    - * - * @param value 值 - * @param offset 偏移量 - */ - double offset (double value, int offset); - - /** - * 取最接近的一个值的下标 - *

    - * 例: - *

    -	 * {@code
    -	 * value ≈ 2.4
    -	 *             result
    -	 *               ↓
    -	 * 下标: | 0   | 1   | 2   | 3   | 4   |
    -	 * 数值: | 1.0 | 2.0 | 3.0 | 4.0 | 5.0 |
    -	 * }
    -	 * 
    - */ - int iadsorption (double value); - - /** - * 找最近的一个值 - *

    - * 例: - *

    -	 *  {@code
    -	 *  value ≈ 2.4
    -	 *  下标: | 0   | 1   | 2   | 3   | 4   |
    -	 *  数值: | 1.0 | 2.0 | 3.0 | 4.0 | 5.0 |
    -	 *                ↑
    -	 *              result
    -	 *  }
    -	 *  
    - */ - double adsorption (double value); - - /** - * 获取指定值的下一个值 - *

    - * 如果该值已经位于最后一个区间,那么直接返回最后一个值 - */ - double getNext (double value); - - /** - * 获取指定值的上一个值 - *

    - * 如果该值已经位于第一个区间,那么直接返回第一个值 - */ - double getLast (double value); - - /** - * @return 如果单调递增则为1,单调递减则为-1 - */ - int sgn (); - - /** - * @return 列表长度 - */ - int length (); -} diff --git a/common/src/main/java/net/leawind/mc/util/math/monolist/StaticMonoList.java b/common/src/main/java/net/leawind/mc/util/math/monolist/StaticMonoList.java deleted file mode 100644 index cfc8dcc7..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/monolist/StaticMonoList.java +++ /dev/null @@ -1,135 +0,0 @@ -package net.leawind.mc.util.math.monolist; - - -import net.leawind.mc.util.math.LMath; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.util.function.Function; - -/** - * 静态单调列表 - *

    - * 列表中每一项的值会在列表对象被实例化时创建,不可更改 - */ -public class StaticMonoList implements MonoList { - private final int sgn; - private final double[] list; - - /** - * 根据列表直接创建 - * - * @param list 列表 - */ - public StaticMonoList (double @NotNull [] list) { - this.list = list; - sgn = (int)Math.signum(list[1] - list[0]); - if (!isMono()) { - throw new IllegalArgumentException("Invalid list"); - } - } - - private boolean isMono () { - double lastValue = list[0]; - for (double value: list) { - if (value != lastValue && Math.signum(value - lastValue) != sgn) { - return false; - } - } - return true; - } - - public static @NotNull StaticMonoList linear (int length) { - return of(length, d -> (double)d); - } - - public static @NotNull StaticMonoList of (int length, @NotNull Function getter) { - double[] list = new double[length]; - for (int i = 0; i < length; i++) { - list[i] = getter.apply(i); - } - return of(list); - } - - @Contract("_ -> new") - public static @NotNull StaticMonoList of (double[] list) { - return new StaticMonoList(list); - } - - public static @NotNull StaticMonoList exp (int length) { - return of(length, Math::exp); - } - - public static @NotNull StaticMonoList squared (int length) { - return of(length, i -> (double)(i * i)); - } - - public static @NotNull StaticMonoList of (int length, double min, double max, @NotNull Function f, @NotNull Function fInv) { - double xmin = fInv.apply(min); - double xrange = fInv.apply(max) - xmin; - return of(length, i -> f.apply(i * xrange / length + xmin)); - } - - @Override - public double get (int i) { - return list[i]; - } - - @Override - public double offset (double value, int offset) { - int i = iadsorption(value) + offset * sgn(); - i = LMath.clamp(i, 0, length() - 1); - return list[i]; - } - - @Override - public int iadsorption (double value) { - int ileft = 0; - int iright = length() - 1; - int icenter = length() / 2; - while (true) { - if (list[icenter] < value) { - ileft = icenter; - } else if (list[icenter] > value) { - iright = icenter; - } else { - return icenter; - } - if (iright - ileft == 1) { - break; - } - icenter = (ileft + iright) / 2; - } - double emin = value - list[ileft], emax = list[iright] - value; - if (emin <= emax) { - return ileft; - } else { - return iright; - } - } - - @Override - public double adsorption (double value) { - return list[iadsorption(value)]; - } - - @Override - public double getNext (double value) { - return offset(value, 1); - } - - @Override - public double getLast (double value) { - return offset(value, -1); - } - - @Override - public int sgn () { - return sgn; - } - - @Override - public int length () { - return list.length; - } -} diff --git a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpRotSmoothDouble.java b/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpRotSmoothDouble.java deleted file mode 100644 index b197db4f..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpRotSmoothDouble.java +++ /dev/null @@ -1,78 +0,0 @@ -package net.leawind.mc.util.math.smoothvalue; - - -import net.leawind.mc.util.math.LMath; -import org.jetbrains.annotations.NotNull; - -public class ExpRotSmoothDouble extends ExpSmoothDouble { - private double cycle; - - /** - * @param cycle 周期 - */ - public ExpRotSmoothDouble (double cycle) { - super(); - setCycle(cycle); - } - - public static @NotNull ExpRotSmoothDouble createWithHalflife (double cycle, double halflife) { - ExpRotSmoothDouble v = new ExpRotSmoothDouble(cycle); - v.setHalflife(halflife); - return v; - } - - public double getCycle () { - return cycle; - } - - public void setCycle (double cycle) { - this.cycle = cycle; - } - - @Override - public void setTarget (double d) { - d = LMath.floorMod(d, cycle); - super.setTarget(d); - } - - @Override - public @NotNull Double get (double t) { - lastValue = LMath.floorMod(lastValue, cycle); - value = LMath.floorMod(value, cycle); - double delta = LMath.floorMod(value - lastValue, cycle); - if (delta > cycle / 2) { - delta -= cycle; - } - value = lastValue + delta; - return LMath.lerp(lastValue, value, t); - } - - @Override - protected void udpateWithOutSavingLastValue (double period) { - value = LMath.floorMod(value, cycle); - target = LMath.floorMod(target, cycle); - double delta = LMath.floorMod(target - value, cycle); - if (delta > cycle / 2) { - delta -= cycle; - } - target = value + delta; - value = LMath.lerp(value, target, 1 - Math.pow(smoothFactor, smoothFactorWeight * period)); - } - - @Override - public void set (@NotNull Double d) { - d = LMath.floorMod(d, cycle); - super.set(d); - } - - @Override - public void setHalflife (double halflife) { - super.setHalflife(halflife); - } - - @Override - public void setValue (double d) { - d = LMath.floorMod(d, cycle); - super.setValue(d); - } -} diff --git a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothDouble.java b/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothDouble.java deleted file mode 100644 index a8c71faa..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothDouble.java +++ /dev/null @@ -1,89 +0,0 @@ -package net.leawind.mc.util.math.smoothvalue; - - -import net.leawind.mc.util.math.LMath; -import org.jetbrains.annotations.NotNull; - -@SuppressWarnings("unused") -public class ExpSmoothDouble extends ExpSmoothValue { - public ExpSmoothDouble () { - super(0D, 1D, 0D, 0D, 0D); - } - - public static @NotNull ExpSmoothDouble createWithHalflife (double halflife) { - ExpSmoothDouble v = new ExpSmoothDouble(); - v.setHalflife(halflife); - return v; - } - - public void setTarget (double target) { - this.target = target; - } - - public void setSmoothFactor (double k, double t) { - this.smoothFactor = Math.pow(k, 1 / t); - } - - @Override - public void setTarget (@NotNull Double target) { - this.target = target; - } - - @Override - public @NotNull Double get (double t) { - return LMath.lerp(lastValue, value, t); - } - - @Override - protected void udpateWithOutSavingLastValue (double period) { - value = LMath.lerp(value, target, 1 - Math.pow(smoothFactor, smoothFactorWeight * period)); - } - - @Override - public void setValue (@NotNull Double d) { - value = d; - } - - @Override - public void set (@NotNull Double d) { - value = target = d; - } - - @Override - public void setSmoothFactor (@NotNull Double smoothFactor) { - this.smoothFactor = smoothFactor; - } - - @Override - public void setSmoothFactor (double smoothFactor) { - this.smoothFactor = smoothFactor; - } - - @Override - public void setMT (@NotNull Double multiplier, @NotNull Double time) { - if (multiplier < 0 || multiplier > 1) { - throw new IllegalArgumentException("Multiplier should in [0,1]: " + multiplier); - } else if (time < 0) { - throw new IllegalArgumentException("Invalid time, non-negative required, but got " + time); - } - setSmoothFactor(time == 0 ? 0: Math.pow(multiplier, 1 / time)); - } - - @Override - public void setHalflife (@NotNull Double halflife) { - setMT(0.5, halflife); - } - - @Override - public void setHalflife (double halflife) { - setMT(0.5, halflife); - } - - public void setSmoothFactorWeight (double weight) { - this.smoothFactorWeight = weight; - } - - public void setValue (double d) { - value = d; - } -} diff --git a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothRotation.java b/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothRotation.java deleted file mode 100644 index 57953f97..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothRotation.java +++ /dev/null @@ -1,114 +0,0 @@ -package net.leawind.mc.util.math.smoothvalue; - - -import net.leawind.mc.util.math.LMath; -import net.leawind.mc.util.math.vector.api.Vector2d; -import org.jetbrains.annotations.NotNull; - -/** - * 平滑旋转 - *

    - * 实体朝向通常用二维向量来描述,但要实现其朝向的平滑变化, 不能直接使用 {@link ExpSmoothVector2d} - *

    - * 因为其存在一些约束条件和特殊性质 - *

    - *

  • y:偏航角,范围:[0, 360]
  • - *
  • x:俯仰角,范围:[-90, 90]
  • - */ -public class ExpSmoothRotation { - private final ExpRotSmoothDouble y; - private final ExpSmoothDouble x; - - private ExpSmoothRotation () { - y = new ExpRotSmoothDouble(360); - x = new ExpSmoothDouble(); - } - - public static @NotNull ExpSmoothRotation createWithHalflife (double halflife) { - ExpSmoothRotation v = new ExpSmoothRotation(); - v.setHalflife(halflife); - return v; - } - - public void setHalflife (double halflife) { - y.setHalflife(halflife); - x.setHalflife(halflife); - } - - public void setMT (double multiplier, double time) { - y.setMT(multiplier, time); - x.setMT(multiplier, time); - } - - public void setSmoothFactor (double smoothFactor) { - y.setSmoothFactor(smoothFactor); - x.setSmoothFactor(smoothFactor); - } - - public void setTarget (@NotNull Vector2d rot) { - y.setTarget(rot.y()); - x.setTarget(LMath.clamp(rot.x(), -90, 90)); - } - - /** - * 记录旧的平滑值,然后更新平滑值 - *

    - * 对于 {@link ExpSmoothValue},理论上任何时候都可以更新, - *

    - * 但考虑到时间的精度有限,更新间隔不应过小。 - * - * @param period 经过的时间(s) - */ - public void update (double period) { - y.update(period); - x.update(period); - } - - /** - * 获取当前的平滑值 - *

    - * 如果使用 {@link ISmoothValue#update(double)} 进行更新的频率小于渲染频率, - *

    - * 则不建议在渲染中直接采用此方法获取平滑值,因为这可能造成不平滑的效果。 - *

    - * 应当使用 {@link ISmoothValue#get(double)} - */ - public @NotNull Vector2d get () { - return Vector2d.of(x.get(), y.get()); - } - - /** - * 旧值与当前平滑值的线性插值 - *

    - * 每次更新时,都会记录下当时的平滑值作为旧值,然后再计算新的平滑值。 - *

    - * 当更新频率小于渲染频率时,此方法十分有效。 - * - * @param t 自上次更新以来经过的时间占更新间隔的比例,用于线性插值。 - */ - public @NotNull Vector2d get (double t) { - return Vector2d.of(x.get(t), y.get(t)); - } - - public void set (@NotNull Vector2d v) { - y.set(v.y()); - x.set(LMath.clamp(v.x(), -90, 90)); - } - - public void setSmoothFactorWeight (double weight) { - y.setSmoothFactorWeight(weight); - x.setSmoothFactorWeight(weight); - } - - public void setValue (@NotNull Vector2d v) { - y.setValue(v.y()); - x.setValue(LMath.clamp(v.x(), -90, 90)); - } - - /** - * 获取上次更新前的平滑值(旧值) - */ - public @NotNull Vector2d getLast () { - return Vector2d.of(x.getLast(), y.getLast()); - } -} diff --git a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothValue.java b/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothValue.java deleted file mode 100644 index a9e8dcc4..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothValue.java +++ /dev/null @@ -1,95 +0,0 @@ -package net.leawind.mc.util.math.smoothvalue; - - -import org.jetbrains.annotations.NotNull; - -@SuppressWarnings("unused") -public abstract class ExpSmoothValue implements ISmoothValue { - /** - * 平滑系数,越大越平滑 - */ - public @NotNull T smoothFactor; - /** - * 平滑系数乘数,默认应为1 - */ - public @NotNull T smoothFactorWeight;// factor ^ weight - /** - * 目标值 - */ - public @NotNull T target; - /** - * 当前平滑的值 - */ - protected @NotNull T value; - /** - * 上次更新时的目标值 - */ - protected @NotNull T lastValue; - - protected ExpSmoothValue (@NotNull T smoothFactor, @NotNull T smoothFactorWeight, @NotNull T value, @NotNull T lastValue, @NotNull T target) { - this.smoothFactor = smoothFactor; - this.smoothFactorWeight = smoothFactorWeight; - this.value = value; - this.lastValue = lastValue; - this.target = target; - } - - final public void update (double period) { - saveLastValue(); - udpateWithOutSavingLastValue(period); - } - - @Override - final public @NotNull T get () { - return value; - } - - @Override - abstract public @NotNull T get (double t); - - @Override - final public @NotNull T getLast () { - return lastValue; - } - - /** - * 记录下更新前的平滑值(旧值),存储在 lastValue 中。 - *

    - * 应当在 update 方法中写入新值前调用 - */ - final protected void saveLastValue () { - lastValue = value; - } - - abstract protected void udpateWithOutSavingLastValue (double period); - - abstract public void setValue (@NotNull T value); - - /** - * 同时设置目标值和当前平滑值 - *

    - * 不改变旧值 - */ - abstract public void set (@NotNull T value); - - abstract public void setSmoothFactor (@NotNull T smoothFactor); - - abstract void setSmoothFactor (double smoothFactor); - - /** - * 根据以下规则设置平滑系数: - *

    - * 每隔 time 秒,value 变为原来的 multiplier 倍。 - */ - abstract void setMT (@NotNull T multiplier, @NotNull T time); - - /** - * 根据半衰期设置平滑系数 - */ - abstract void setHalflife (@NotNull T halflife); - - /** - * 根据半衰期设置平滑系数 - */ - abstract void setHalflife (double halflife); -} diff --git a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothVector2d.java b/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothVector2d.java deleted file mode 100644 index b71be0af..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothVector2d.java +++ /dev/null @@ -1,87 +0,0 @@ -package net.leawind.mc.util.math.smoothvalue; - - -import net.leawind.mc.util.math.vector.api.Vector2d; -import org.jetbrains.annotations.NotNull; - -@SuppressWarnings("unused") -public class ExpSmoothVector2d extends ExpSmoothValue { - public ExpSmoothVector2d () { - super(Vector2d.of(0), Vector2d.of(1), Vector2d.of(0), Vector2d.of(0), Vector2d.of(0)); - } - - public void setTarget (double x, double y) { - this.target.set(x, y); - } - - @Override - public void setTarget (@NotNull Vector2d target) { - this.target.set(target); - } - - @Override - public @NotNull Vector2d get (double t) { - return lastValue.copy().lerp(value, t); - } - - @Override - protected void udpateWithOutSavingLastValue (double period) { - Vector2d t = smoothFactor.copy().pow(smoothFactorWeight.copy().mul(period)).negate().add(1); - value = value.copy().lerp(target, t); - } - - @Override - public void setValue (@NotNull Vector2d v) { - value = v; - } - - @Override - public void set (@NotNull Vector2d v) { - value = target = v; - } - - @Override - public void setSmoothFactor (@NotNull Vector2d s) { - this.smoothFactor.set(s); - } - - @Override - public void setSmoothFactor (double smoothFactor) { - setSmoothFactor(smoothFactor, smoothFactor); - } - - public void setSmoothFactor (double x, double y) { - this.smoothFactor.set(x, y); - } - - @Override - public void setMT (@NotNull Vector2d multiplier, @NotNull Vector2d time) { - if (multiplier.x() < 0 || multiplier.x() > 1) { - throw new IllegalArgumentException("Multiplier.x should in [0,1]: " + multiplier.x()); - } else if (multiplier.y() < 0 || multiplier.y() > 1) { - throw new IllegalArgumentException("Multiplier.y should in [0,1]: " + multiplier.y()); - } else if (time.x() < 0 || time.y() < 0) { - throw new IllegalArgumentException("Invalid time, non-negative required, but got " + time); - } - this.smoothFactor.set(time.x() == 0 ? 0: Math.pow(multiplier.x(), 1 / time.x()),// - time.y() == 0 ? 0: Math.pow(multiplier.y(), 1 / time.y())); - } - - @Override - public void setHalflife (@NotNull Vector2d halflife) { - setMT(Vector2d.of(0.5), halflife); - } - - @Override - public void setHalflife (double halflife) { - setMT(Vector2d.of(0.5), Vector2d.of(halflife)); - } - - public void setSmoothFactorWeight (double x, double y) { - this.smoothFactorWeight.set(x, y); - } - - public void setValue (double x, double y) { - this.value.set(x, y); - } -} diff --git a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothVector3d.java b/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothVector3d.java deleted file mode 100644 index a6338d86..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ExpSmoothVector3d.java +++ /dev/null @@ -1,90 +0,0 @@ -package net.leawind.mc.util.math.smoothvalue; - - -import net.leawind.mc.util.math.vector.api.Vector3d; -import org.jetbrains.annotations.NotNull; - -@SuppressWarnings("unused") -public class ExpSmoothVector3d extends ExpSmoothValue { - public ExpSmoothVector3d () { - super(Vector3d.of(0), Vector3d.of(1), Vector3d.of(0), Vector3d.of(0), Vector3d.of(0)); - } - - public void setTarget (double x, double y, double z) { - this.target.set(x, y, z); - } - - public void setValue (double x, double y, double z) { - this.value.set(x, y, z); - } - - @Override - public void setTarget (@NotNull Vector3d target) { - this.target.set(target); - } - - @Override - public @NotNull Vector3d get (double t) { - return lastValue.copy().lerp(value, t); - } - - @Override - protected void udpateWithOutSavingLastValue (double period) { - Vector3d t = smoothFactor.copy().pow(smoothFactorWeight.copy().mul(period)).negate().add(1); - value = value.copy().lerp(target, t); - } - - @Override - public void setValue (@NotNull Vector3d v) { - value = v; - } - - @Override - public void set (@NotNull Vector3d v) { - value = target = v; - } - - @Override - public void setSmoothFactor (@NotNull Vector3d smoothFactor) { - this.smoothFactor.set(smoothFactor); - } - - @Override - public void setSmoothFactor (double d) { - setSmoothFactor(d, d, d); - } - - private void setSmoothFactor (double x, double y, double z) { - this.smoothFactor.set(x, y, z); - } - - @Override - public void setMT (@NotNull Vector3d multiplier, @NotNull Vector3d time) { - if (multiplier.x() < 0 || multiplier.x() > 1) { - throw new IllegalArgumentException("Multiplier.x should in [0,1]: " + multiplier.x()); - } else if (multiplier.y() < 0 || multiplier.y() > 1) { - throw new IllegalArgumentException("Multiplier.y should in [0,1]: " + multiplier.y()); - } else if (multiplier.z() < 0 || multiplier.z() > 1) { - throw new IllegalArgumentException("Multiplier.z should in [0,1]: " + multiplier.z()); - } else if (time.x() < 0 || time.y() < 0 || time.z() < 0) { - throw new IllegalArgumentException("Invalid time, non-negative required, but got " + time); - } - this.smoothFactor.set(time.x() == 0 ? 0: Math.pow(multiplier.x(), 1 / time.x()),// - time.y() == 0 ? 0: Math.pow(multiplier.y(), 1 / time.y()),// - time.z() == 0 ? 0: Math.pow(multiplier.z(), 1 / time.z())); - } - - @Override - public void setHalflife (@NotNull Vector3d halflife) { - setMT(Vector3d.of(0.5), halflife); - } - - @Override - public void setHalflife (double halflife) { - setMT(Vector3d.of(0.5), Vector3d.of(halflife)); - } - - public void setSmoothFactorWeight (double x, double y, double z) { - this.smoothFactorWeight.set(x, y, z); - } -} diff --git a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ISmoothValue.java b/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ISmoothValue.java deleted file mode 100644 index e79602db..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/smoothvalue/ISmoothValue.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.leawind.mc.util.math.smoothvalue; - - -import org.jetbrains.annotations.NotNull; - -@SuppressWarnings("unused") -public interface ISmoothValue { - /** - * 设置目标值 - *

    - * 任何时候都可能设置此值 - */ - void setTarget (@NotNull T endValue); - - /** - * 记录旧的平滑值,然后更新平滑值 - *

    - * 对于 {@link ExpSmoothValue},理论上任何时候都可以更新, - *

    - * 但考虑到时间的精度有限,更新间隔不应过小。 - * - * @param period 经过的时间(s) - */ - void update (double period); - - /** - * 获取当前的平滑值 - *

    - * 如果使用 {@link ISmoothValue#update(double)} 进行更新的频率小于渲染频率, - *

    - * 则不建议在渲染中直接采用此方法获取平滑值,因为这可能造成不平滑的效果。 - *

    - * 应当使用 {@link ISmoothValue#get(double)} - */ - @NotNull T get (); - - /** - * 旧值与当前平滑值的线性插值 - *

    - * 每次更新时,都会记录下当时的平滑值作为旧值,然后再计算新的平滑值。 - *

    - * 当更新频率小于渲染频率时,此方法十分有效。 - * - * @param t 自上次更新以来经过的时间占更新间隔的比例,用于线性插值。 - */ - @NotNull T get (double t); - - /** - * 获取上次更新前的平滑值(旧值) - */ - @NotNull T getLast (); -} diff --git a/common/src/main/java/net/leawind/mc/util/math/vector/api/Vector2d.java b/common/src/main/java/net/leawind/mc/util/math/vector/api/Vector2d.java deleted file mode 100644 index f5b8a5df..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/vector/api/Vector2d.java +++ /dev/null @@ -1,161 +0,0 @@ -package net.leawind.mc.util.math.vector.api; - - -import net.leawind.mc.util.math.vector.impl.Vector2dImpl; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -@SuppressWarnings("unused") -public interface Vector2d { - @Contract("-> new") - static @NotNull Vector2d of () { - return of(0); - } - - @Contract("_ -> new") - static @NotNull Vector2d of (double d) { - return of(d, d); - } - - @Contract("_,_ -> new") - static @NotNull Vector2d of (double x, double y) { - return new Vector2dImpl(x, y); - } - - @Contract("_ -> new") - static @NotNull Vector2d of (@NotNull Vector2d v) { - return of(v.x(), v.y()); - } - - double x (); - - double y (); - - void x (double x); - - void y (double y); - - @Contract("_ -> this") - Vector2d set (double d); - - @Contract("_ -> this") - Vector2d set (@NotNull Vector2d v); - - @Contract("_,_ -> this") - Vector2d set (double x, double y); - - @Contract("_,_ -> param2") - Vector2d add (@NotNull Vector2d v, @NotNull Vector2d dest); - - @Contract("_,_,_ -> param3") - Vector2d add (double x, double y, @NotNull Vector2d dest); - - @Contract("_ -> this") - Vector2d add (@NotNull Vector2d v); - - @Contract("_ -> this") - Vector2d add (double d); - - @Contract("_,_ -> this") - Vector2d add (double x, double y); - - @Contract("_,_ -> param2") - Vector2d sub (@NotNull Vector2d v, @NotNull Vector2d dest); - - Vector2d sub (double x, double y, @NotNull Vector2d dest); - - @Contract("_ -> this") - Vector2d sub (@NotNull Vector2d v); - - @Contract("_,_ -> this") - Vector2d sub (double x, double y); - - Vector2d mul (@NotNull Vector2d v, @NotNull Vector2d dest); - - Vector2d mul (double x, double y, @NotNull Vector2d dest); - - @Contract("_ -> this") - Vector2d mul (@NotNull Vector2d v); - - @Contract("_ -> this") - Vector2d mul (double d); - - @Contract("_,_ -> this") - Vector2d mul (double x, double y); - - Vector2d div (@NotNull Vector2d v, @NotNull Vector2d dest); - - Vector2d div (double x, double y, @NotNull Vector2d dest); - - @Contract("_ -> this") - Vector2d div (@NotNull Vector2d v); - - @Contract("_,_ -> this") - Vector2d div (double x, double y); - - Vector2d pow (@NotNull Vector2d v, @NotNull Vector2d dest); - - Vector2d pow (double d, @NotNull Vector2d dest); - - Vector2d pow (double x, double y, @NotNull Vector2d dest); - - @Contract("_ -> this") - Vector2d pow (@NotNull Vector2d v); - - @Contract("_ -> this") - Vector2d pow (double d); - - @Contract("_,_ -> this") - Vector2d pow (double x, double y); - - double length (); - - double lengthSquared (); - - double distance (@NotNull Vector2d v); - - double distance (double x, double y); - - double distanceSquared (double x, double y); - - @Contract("-> this") - Vector2d normalize (); - - @Contract("_ -> this") - Vector2d normalize (double length); - - @Contract("-> this") - Vector2d normalizeSafely (); - - @Contract("_ -> this") - Vector2d normalizeSafely (double length); - - @Contract("_ -> this") - Vector2d rotateTo (@NotNull Vector2d direction); - - @Contract("-> this") - Vector2d zero (); - - @Contract("-> this") - Vector2d negate (); - - double dot (@NotNull Vector2d v); - - @Contract("_,_ -> this") - Vector2d clamp (double min, double max); - - @Contract("_,_ -> this") - Vector2d clamp (@NotNull Vector2d min, @NotNull Vector2d max); - - @Contract("_,_ -> this") - Vector2d lerp (@NotNull Vector2d end, double t); - - @Contract("_,_ -> this") - Vector2d lerp (@NotNull Vector2d end, @NotNull Vector2d t); - - @Contract("-> this") - Vector2d absolute (); - - @Contract("-> new") - Vector2d copy (); -} diff --git a/common/src/main/java/net/leawind/mc/util/math/vector/api/Vector3d.java b/common/src/main/java/net/leawind/mc/util/math/vector/api/Vector3d.java deleted file mode 100644 index 2cb1ee8d..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/vector/api/Vector3d.java +++ /dev/null @@ -1,176 +0,0 @@ -package net.leawind.mc.util.math.vector.api; - - -import net.leawind.mc.util.math.vector.impl.Vector3dImpl; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -@SuppressWarnings("unused") -public interface Vector3d { - @Contract(value=" -> new", pure=true) - static @NotNull Vector3d of () { - return of(0); - } - - @Contract(value="_ -> new", pure=true) - static @NotNull Vector3d of (double d) { - return of(d, d, d); - } - - @Contract(value="_,_,_ -> new", pure=true) - static @NotNull Vector3d of (double x, double y, double z) { - return new Vector3dImpl(x, y, z); - } - - @Contract("_ -> new") - static @NotNull Vector3d of (@NotNull Vector3d v) { - return of(v.x(), v.y(), v.z()); - } - - double x (); - - double y (); - - double z (); - - void x (double x); - - void y (double y); - - void z (double z); - - @Contract("_ -> this") - Vector3d set (double d); - - @Contract("_,_,_ -> this") - Vector3d set (double x, double y, double z); - - @Contract("_ -> this") - Vector3d set (@NotNull Vector3d v); - - @Contract("_,_ -> param2") - Vector3d add (@NotNull Vector3d v, @NotNull Vector3d dest); - - @Contract("_,_,_,_ -> param4") - Vector3d add (double x, double y, double z, @NotNull Vector3d dest); - - @Contract("_ -> this") - Vector3d add (@NotNull Vector3d v); - - @Contract("_,_,_ -> this") - Vector3d add (double x, double y, double z); - - @Contract("_ -> this") - Vector3d add (double d); - - @Contract("_,_ -> param2") - Vector3d sub (@NotNull Vector3d v, @NotNull Vector3d dest); - - @Contract("_,_,_,_ -> param2") - Vector3d sub (double x, double y, double z, @NotNull Vector3d dest); - - @Contract("_ -> this") - Vector3d sub (@NotNull Vector3d v); - - @Contract("_,_,_ -> this") - Vector3d sub (double x, double y, double z); - - @Contract("_,_ -> param2") - Vector3d mul (@NotNull Vector3d v, @NotNull Vector3d dest); - - @Contract("_,_,_,_ -> param4") - Vector3d mul (double x, double y, double z, @NotNull Vector3d dest); - - @Contract("_ -> this") - Vector3d mul (@NotNull Vector3d v); - - @Contract("_,_,_ -> this") - Vector3d mul (double x, double y, double z); - - @Contract("_ -> this") - Vector3d mul (double d); - - @Contract("_,_ -> param2") - Vector3d div (@NotNull Vector3d v, @NotNull Vector3d dest); - - @Contract("_,_,_,_ -> param4") - Vector3d div (double x, double y, double z, @NotNull Vector3d dest); - - @Contract("_ -> this") - Vector3d div (@NotNull Vector3d v); - - @Contract("_,_,_ -> this") - Vector3d div (double x, double y, double z); - - @Contract("_ -> this") - Vector3d div (double d); - - @Contract("_,_ -> param2") - Vector3d pow (@NotNull Vector3d v, @NotNull Vector3d dest); - - @Contract("_,_,_,_ -> param4") - Vector3d pow (double x, double y, double z, @NotNull Vector3d dest); - - @Contract("_,_ -> param2") - Vector3d pow (double d, @NotNull Vector3d dest); - - @Contract("_ -> this") - Vector3d pow (@NotNull Vector3d v); - - @Contract("_,_,_ -> this") - Vector3d pow (double x, double y, double z); - - @Contract("_ -> this") - Vector3d pow (double d); - - double lengthSquared (); - - double distance (@NotNull Vector3d v); - - double distance (double x, double y, double z); - - double distanceSquared (double x, double y, double z); - - double length (); - - @Contract("-> this") - Vector3d normalize (); - - @Contract("-> this") - Vector3d normalizeSafely (); - - @Contract("_ -> this") - Vector3d normalizeSafely (double length); - - @Contract("_ -> this") - Vector3d rotateTo (@NotNull Vector3d direction); - - @Contract("_ -> this") - Vector3d normalize (double length); - - @Contract("-> this") - Vector3d zero (); - - @Contract("-> this") - Vector3d negate (); - - double dot (@NotNull Vector3d v); - - @Contract("_,_ -> this") - Vector3d clamp (double min, double max); - - @Contract("_,_ -> this") - Vector3d clamp (@NotNull Vector3d min, @NotNull Vector3d max); - - @Contract("_,_ -> this") - Vector3d lerp (@NotNull Vector3d end, double t); - - @Contract("_,_ -> this") - Vector3d lerp (@NotNull Vector3d end, @NotNull Vector3d t); - - @Contract("-> this") - Vector3d absolute (); - - @Contract("-> new") - Vector3d copy (); -} diff --git a/common/src/main/java/net/leawind/mc/util/math/vector/impl/Vector2dImpl.java b/common/src/main/java/net/leawind/mc/util/math/vector/impl/Vector2dImpl.java deleted file mode 100644 index 20be132b..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/vector/impl/Vector2dImpl.java +++ /dev/null @@ -1,356 +0,0 @@ -package net.leawind.mc.util.math.vector.impl; - - -import net.leawind.mc.util.math.vector.api.Vector2d; -import org.jetbrains.annotations.NotNull; - -public class Vector2dImpl implements Vector2d { - private double x; - private double y; - - public Vector2dImpl (double x, double y) { - this.x = x; - this.y = y; - } - - @Override - public int hashCode () { - int l = 31, r = 1; - long t; - t = Double.doubleToLongBits(x); - r = l * r + (int)(t ^ (t >>> 32)); - t = Double.doubleToLongBits(y()); - r = l * r + (int)(t ^ (t >>> 32)); - return r; - } - - @Override - public boolean equals (Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Vector2d other = (Vector2d)obj; - if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x())) { - return false; - } - return Double.doubleToLongBits(y) == Double.doubleToLongBits(other.y()); - } - - @Override - public String toString () { - return String.format("Vector2d(%f, %f)", x(), y()); - } - - @Override - public double x () { - return x; - } - - @Override - public double y () { - return y; - } - - @Override - public void x (double x) { - this.x = x; - } - - @Override - public void y (double y) { - this.y = y; - } - - @Override - public Vector2d set (double d) { - return set(d, d); - } - - @Override - public Vector2d set (@NotNull Vector2d v) { - return set(v.x(), v.y()); - } - - @Override - public Vector2d set (double x, double y) { - this.x = x; - this.y = y; - return this; - } - - @Override - public Vector2d add (@NotNull Vector2d v, @NotNull Vector2d dest) { - return add(v.x(), v.y(), dest); - } - - @Override - public Vector2d add (double x, double y, @NotNull Vector2d dest) { - dest.x(this.x + x); - dest.y(this.y + y); - return dest; - } - - @Override - public Vector2d add (@NotNull Vector2d v) { - return add(v.x(), v.y()); - } - - @Override - public Vector2d add (double d) { - return add(d, d); - } - - @Override - public Vector2d add (double x, double y) { - this.x = this.x + x; - this.y = this.y + y; - return this; - } - - @Override - public Vector2d sub (@NotNull Vector2d v, @NotNull Vector2d dest) { - return sub(v.x(), v.y(), dest); - } - - @Override - public Vector2d sub (double x, double y, @NotNull Vector2d dest) { - dest.x(this.x - x); - dest.y(this.y - y); - return dest; - } - - @Override - public Vector2d sub (@NotNull Vector2d v) { - return sub(v.x(), v.y()); - } - - @Override - public Vector2d sub (double x, double y) { - this.x -= x; - this.y -= y; - return this; - } - - @Override - public Vector2d mul (@NotNull Vector2d v, @NotNull Vector2d dest) { - return mul(v.x(), v.y(), dest); - } - - @Override - public Vector2d mul (double x, double y, @NotNull Vector2d dest) { - dest.x(this.x * x); - dest.y(this.y * y); - return dest; - } - - @Override - public Vector2d mul (@NotNull Vector2d v) { - return mul(v.x(), v.y()); - } - - @Override - public Vector2d mul (double d) { - return mul(d, d); - } - - @Override - public Vector2d mul (double x, double y) { - this.x *= x; - this.y *= y; - return this; - } - - @Override - public Vector2d div (@NotNull Vector2d v, @NotNull Vector2d dest) { - return div(v.x(), v.y(), dest); - } - - @Override - public Vector2d div (double x, double y, @NotNull Vector2d dest) { - dest.x(this.x / x); - dest.y(this.y / y); - return dest; - } - - @Override - public Vector2d div (@NotNull Vector2d v) { - return div(v.x(), v.y()); - } - - @Override - public Vector2d div (double x, double y) { - this.x /= x; - this.y /= y; - return this; - } - - @Override - public Vector2d pow (@NotNull Vector2d v, @NotNull Vector2d dest) { - return pow(v.x(), v.y(), dest); - } - - @Override - public Vector2d pow (double d, @NotNull Vector2d dest) { - return pow(d, d, dest); - } - - @Override - public Vector2d pow (double x, double y, @NotNull Vector2d dest) { - dest.x(Math.pow(this.x, x)); - dest.y(Math.pow(this.y, y)); - return dest; - } - - @Override - public Vector2d pow (@NotNull Vector2d v) { - return pow(v.x(), v.y()); - } - - @Override - public Vector2d pow (double d) { - return pow(d, d); - } - - @Override - public Vector2d pow (double x, double y) { - this.x = Math.pow(this.x, x); - this.y = Math.pow(this.y, y); - return this; - } - - @Override - public double length () { - return Math.sqrt(x * x + y * y); - } - - @Override - public double lengthSquared () { - return x * x + y * y; - } - - @Override - public double distance (@NotNull Vector2d v) { - return distance(v.x(), v.y()); - } - - @Override - public double distance (double x, double y) { - double dx = this.x - x; - double dy = this.y - y; - return Math.sqrt(dx * dx + dy * dy); - } - - @Override - public double distanceSquared (double x, double y) { - double dx = this.x - x; - double dy = this.y - y; - return dx * dx + dy * dy; - } - - @Override - public Vector2d normalize () { - double len = length(); - x /= len; - y /= len; - return this; - } - - @Override - public Vector2d normalize (double length) { - double len = length() / length; - x /= len; - y /= len; - return this; - } - - @Override - public Vector2d normalizeSafely () { - double len = length(); - if (len == 0) { - throw new IllegalStateException("Vector length is 0"); - } - x /= len; - y /= len; - return this; - } - - @Override - public Vector2d normalizeSafely (double length) { - double len = length() / length; - if (len == 0) { - throw new IllegalStateException("Vector length is 0"); - } - x /= len; - y /= len; - return this; - } - - @Override - public Vector2d rotateTo (@NotNull Vector2d direction) { - return direction.normalize(length()); - } - - @Override - public Vector2d zero () { - x = y = 0; - return this; - } - - @Override - public Vector2d negate () { - x = -x; - y = -y; - return this; - } - - @Override - public double dot (@NotNull Vector2d v) { - return x() * v.x() + y() * v.y(); - } - - @Override - public Vector2d clamp (double min, double max) { - x = Math.min(Math.max(x, min), max); - y = Math.min(Math.max(y, min), max); - return this; - } - - @Override - public Vector2d clamp (@NotNull Vector2d min, @NotNull Vector2d max) { - x = Math.min(Math.max(x, min.x()), max.x()); - y = Math.min(Math.max(y, min.y()), max.y()); - return this; - } - - @Override - public Vector2d lerp (@NotNull Vector2d end, double t) { - x = x + (end.x() - x) * t; - y = y + (end.y() - y) * t; - return this; - } - - @Override - public Vector2d lerp (@NotNull Vector2d end, @NotNull Vector2d t) { - x = x + (end.x() - x) * t.x(); - y = y + (end.y() - y) * t.y(); - return this; - } - - @Override - public Vector2d absolute () { - x = Math.abs(x); - y = Math.abs(y); - return this; - } - - @Override - public Vector2d copy () { - return Vector2d.of(x, y); - } -} diff --git a/common/src/main/java/net/leawind/mc/util/math/vector/impl/Vector3dImpl.java b/common/src/main/java/net/leawind/mc/util/math/vector/impl/Vector3dImpl.java deleted file mode 100644 index 640321c1..00000000 --- a/common/src/main/java/net/leawind/mc/util/math/vector/impl/Vector3dImpl.java +++ /dev/null @@ -1,402 +0,0 @@ -package net.leawind.mc.util.math.vector.impl; - - -import net.leawind.mc.util.math.vector.api.Vector3d; -import org.jetbrains.annotations.NotNull; - -public class Vector3dImpl implements Vector3d { - private double x; - private double y; - private double z; - - public Vector3dImpl (double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - } - - @Override - public int hashCode () { - final int l = 31; - int r = 1; - long t; - t = Double.doubleToLongBits(x()); - r = l * r + (int)(t ^ (t >>> 32)); - t = Double.doubleToLongBits(y()); - r = l * r + (int)(t ^ (t >>> 32)); - t = Double.doubleToLongBits(z()); - r = l * r + (int)(t ^ (t >>> 32)); - return r; - } - - @Override - public boolean equals (Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Vector3d other = (Vector3d)obj; - if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x())) { - return false; - } - if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y())) { - return false; - } - return Double.doubleToLongBits(z) == Double.doubleToLongBits(other.z()); - } - - @Override - public String toString () { - return String.format("Vector3d(%f, %f, %f)", x, y, z); - } - - @Override - public double x () { - return x; - } - - @Override - public double y () { - return y; - } - - @Override - public double z () { - return z; - } - - @Override - public void x (double x) { - this.x = x; - } - - @Override - public void y (double y) { - this.y = y; - } - - @Override - public void z (double z) { - this.z = z; - } - - @Override - public Vector3d set (double d) { - return set(d, d, d); - } - - @Override - public Vector3d set (double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - return this; - } - - @Override - public Vector3d set (@NotNull Vector3d v) { - return set(v.x(), v.y(), v.z()); - } - - @Override - public Vector3d add (@NotNull Vector3d v, @NotNull Vector3d dest) { - return add(v.x(), v.y(), v.z(), dest); - } - - @Override - public Vector3d add (double x, double y, double z, @NotNull Vector3d dest) { - dest.x(this.x + x); - dest.y(this.y + y); - dest.z(this.z + z); - return dest; - } - - @Override - public Vector3d add (@NotNull Vector3d v) { - return add(v.x(), v.y(), v.z()); - } - - @Override - public Vector3d add (double x, double y, double z) { - this.x = this.x + x; - this.y = this.y + y; - this.z = this.z + z; - return this; - } - - @Override - public Vector3d add (double d) { - return add(d, d, d); - } - - @Override - public Vector3d sub (@NotNull Vector3d v, @NotNull Vector3d dest) { - return sub(v.x(), v.y(), v.z(), dest); - } - - @Override - public Vector3d sub (double x, double y, double z, @NotNull Vector3d dest) { - dest.x(this.x - x); - dest.y(this.y - y); - dest.z(this.z - z); - return dest; - } - - @Override - public Vector3d sub (@NotNull Vector3d v) { - return sub(v.x(), v.y(), v.z()); - } - - @Override - public Vector3d sub (double x, double y, double z) { - this.x = this.x - x; - this.y = this.y - y; - this.z = this.z - z; - return this; - } - - @Override - public Vector3d mul (@NotNull Vector3d v, @NotNull Vector3d dest) { - return mul(v.x(), v.y(), v.z(), dest); - } - - @Override - public Vector3d mul (double x, double y, double z, @NotNull Vector3d dest) { - dest.x(this.x * x); - dest.y(this.y * y); - dest.z(this.z * z); - return dest; - } - - @Override - public Vector3d mul (@NotNull Vector3d v) { - return mul(v.x(), v.y(), v.z()); - } - - @Override - public Vector3d mul (double x, double y, double z) { - this.x = this.x * x; - this.y = this.y * y; - this.z = this.z * z; - return this; - } - - @Override - public Vector3d mul (double d) { - return mul(d, d, d); - } - - @Override - public Vector3d div (@NotNull Vector3d v, @NotNull Vector3d dest) { - return div(v.x(), v.y(), v.z(), dest); - } - - @Override - public Vector3d div (double x, double y, double z, @NotNull Vector3d dest) { - dest.x(this.x / x); - dest.y(this.y / y); - dest.z(this.z / z); - return dest; - } - - @Override - public Vector3d div (@NotNull Vector3d v) { - return div(v.x(), v.y(), v.z()); - } - - @Override - public Vector3d div (double x, double y, double z) { - this.x = this.x / x; - this.y = this.y / y; - this.z = this.z / z; - return this; - } - - @Override - public Vector3d div (double d) { - return div(d, d, d); - } - - @Override - public Vector3d pow (@NotNull Vector3d v, @NotNull Vector3d dest) { - return pow(v.x(), v.y(), v.z(), dest); - } - - @Override - public Vector3d pow (double x, double y, double z, @NotNull Vector3d dest) { - dest.x(Math.pow(this.x, x)); - dest.y(Math.pow(this.y, y)); - dest.z(Math.pow(this.z, z)); - return dest; - } - - @Override - public Vector3d pow (double d, @NotNull Vector3d dest) { - return pow(d, d, d, dest); - } - - @Override - public Vector3d pow (@NotNull Vector3d v) { - return pow(v.x(), v.y(), v.z()); - } - - @Override - public Vector3d pow (double x, double y, double z) { - this.x = Math.pow(this.x, x); - this.y = Math.pow(this.y, y); - this.z = Math.pow(this.z, z); - return this; - } - - @Override - public Vector3d pow (double d) { - return pow(d, d, d); - } - - @Override - public double lengthSquared () { - return x * x + y * y + z * z; - } - - @Override - public double distance (@NotNull Vector3d v) { - return distance(v.x(), v.y(), v.z()); - } - - @Override - public double distance (double x, double y, double z) { - double dx = this.x - x; - double dy = this.y - y; - double dz = this.z - z; - return Math.sqrt(dx * dx + dy * dy + dz * dz); - } - - @Override - public double distanceSquared (double x, double y, double z) { - double dx = this.x - x; - double dy = this.y - y; - double dz = this.z - z; - return dx * dx + dy * dy + dz * dz; - } - - @Override - public double length () { - return Math.sqrt(x * x + y * y + z * z); - } - - @Override - public Vector3d normalize () { - double len = length(); - x /= len; - y /= len; - z /= len; - return this; - } - - @Override - public Vector3d normalizeSafely () { - double len = length(); - if (len == 0) { - throw new IllegalStateException("Vector length is 0"); - } - x /= len; - y /= len; - z /= len; - return this; - } - - @Override - public Vector3d normalizeSafely (double length) { - double len = length() / length; - if (len == 0) { - throw new IllegalStateException("Vector length is 0"); - } - x /= len; - y /= len; - z /= len; - return this; - } - - @Override - public Vector3d rotateTo (@NotNull Vector3d direction) { - return direction.normalize(length()); - } - - @Override - public Vector3d normalize (double length) { - double a = length / length(); - x *= a; - y *= a; - z *= a; - return this; - } - - @Override - public Vector3d zero () { - x = y = z = 0; - return this; - } - - @Override - public Vector3d negate () { - x = -x; - y = -y; - z = -z; - return this; - } - - @Override - public double dot (@NotNull Vector3d v) { - return x * v.x() + y * v.y() + z * v.z(); - } - - @Override - public Vector3d clamp (double min, double max) { - x = Math.min(Math.max(x, min), max); - y = Math.min(Math.max(y, min), max); - z = Math.min(Math.max(z, min), max); - return this; - } - - @Override - public Vector3d clamp (@NotNull Vector3d min, @NotNull Vector3d max) { - x = Math.min(Math.max(x, min.x()), max.x()); - y = Math.min(Math.max(y, min.y()), max.y()); - z = Math.min(Math.max(z, min.z()), max.z()); - return this; - } - - @Override - public Vector3d lerp (@NotNull Vector3d end, double t) { - x += (end.x() - x) * t; - y += (end.y() - y) * t; - z += (end.z() - z) * t; - return this; - } - - @Override - public Vector3d lerp (@NotNull Vector3d end, @NotNull Vector3d t) { - x += (end.x() - x) * t.x(); - y += (end.y() - y) * t.y(); - z += (end.z() - z) * t.z(); - return this; - } - - @Override - public Vector3d absolute () { - x = Math.abs(x); - y = Math.abs(y); - z = Math.abs(z); - return this; - } - - @Override - public Vector3d copy () { - return Vector3d.of(x, y, z); - } -} diff --git a/common/src/main/java/net/leawind/mc/util/modkeymapping/ModKeyMapping.java b/common/src/main/java/net/leawind/mc/util/modkeymapping/ModKeyMapping.java deleted file mode 100644 index 8bbe7446..00000000 --- a/common/src/main/java/net/leawind/mc/util/modkeymapping/ModKeyMapping.java +++ /dev/null @@ -1,144 +0,0 @@ -package net.leawind.mc.util.modkeymapping; - - -import com.mojang.blaze3d.platform.InputConstants; -import dev.architectury.registry.client.keymappings.KeyMappingRegistry; -import net.minecraft.client.KeyMapping; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.util.HashMap; -import java.util.function.Supplier; - -/** - * 按键映射 - *

    - * 使用方法: - *

    - * 在模组初始化时,首先实例化所有按键映射,并绑定需要的的事件处理函数。 - *

    - * 最后调用 {@link ModKeyMapping#registerAll()}方法即可注册。 - *

    - * 示例: - *

    - * public class ModKeys{
    - *     public static final KEY_OPEN_CONFIG = ModKeyMapping.of("key.examplemod.open_config", InputConstants.UNKNOWN.getValue(), "key.categories.examplemod")
    - *     public static void init(){
    - *         ModKeyMapping.registerAll();
    - *     }
    - * }
    - * 
    - */ -@SuppressWarnings("unused") -public interface ModKeyMapping extends Comparable { - HashMap mappings = new HashMap<>(); - - /** - * 不设置默认按键 - *

    - * 需要用户自行设置按键 - * - * @param id 按键映射的标识符,用于可翻译文本 - * @param categoryKey 类别标识符,用于可翻译文本 - */ - @Contract("_,_ -> new") - static @NotNull ModKeyMapping of (@NotNull String id, @NotNull String categoryKey) { - return of(id, InputConstants.UNKNOWN.getValue(), categoryKey); - } - - /** - * @param id 按键映射的标识符,用于可翻译文本 - * @param defaultValue 默认按键 - * @param categoryKey 类别标识符,用于可翻译文本 - */ - @Contract("_,_,_ -> new") - static @NotNull ModKeyMapping of (@NotNull String id, int defaultValue, @NotNull String categoryKey) { - return new ModKeyMappingImpl(id, defaultValue, categoryKey); - } - - /** - * 使用 Architectury API 注册所有已实例化的按键映射 - */ - static void registerAll () { - mappings.values().forEach(KeyMappingRegistry::register); - } - - /** - * #44 - *

    - * 按键是否已按下 - */ - boolean isDown (); - - /** - * 长按时长 - *

    - * 按住一个按键足够长时间后触发长按事件 - * - * @param holdLength 长按时长,单位是 ms - */ - @Contract("_ -> this") - ModKeyMapping holdLength (long holdLength); - - /** - * 短按时长 - *

    - * 按下一个按键,并在足够短的时间内松开,就会触发短按事件。 - * - * @param pressLength 短按时长,单位是 ms - */ - @Contract("_ -> this") - ModKeyMapping pressLength (long pressLength); - - /** - * 当按键被按下时立即触发 - */ - @Contract("_ -> this") - ModKeyMapping onDown (@NotNull Runnable handler); - - /** - * 当按键被按下时立即触发 - * - * @param handler 事件处理函数。若其返回true,则不会触发后续的 onPress 或 onHold 事件 - */ - @Contract("_ -> this") - ModKeyMapping onDown (@NotNull Supplier handler); - - /** - * 当按键松开时立即触发,位于 onPress 之前 - */ - @Contract("_ -> this") - ModKeyMapping onUp (@NotNull Runnable handler); - - /** - * 当按键松开时立即触发,位于 onPress 之前 - * - * @param handler 事件处理函数。若其返回true,则不会触发后续的 onPress 事件 - */ - @Contract("_ -> this") - ModKeyMapping onUp (@NotNull Supplier handler); - - /** - * 按下一个按键后经过足够短的时间后抬起时触发 - */ - @Contract("_ -> this") - ModKeyMapping onPress (@NotNull Runnable handler); - - /** - * 按下一个按键后经过足够短的时间后抬起时触发 - */ - @Contract("_ -> this") - ModKeyMapping onPress (@NotNull Supplier handler); - - /** - * 当按住一个按键时间足够长时触发 - */ - @Contract("_ -> this") - ModKeyMapping onHold (@NotNull Runnable handler); - - /** - * 当按住一个按键时间足够长时触发 - */ - @Contract("_ -> this") - ModKeyMapping onHold (@NotNull Supplier handler); -} diff --git a/common/src/main/java/net/leawind/mc/util/modkeymapping/ModKeyMappingImpl.java b/common/src/main/java/net/leawind/mc/util/modkeymapping/ModKeyMappingImpl.java deleted file mode 100644 index f8453cdc..00000000 --- a/common/src/main/java/net/leawind/mc/util/modkeymapping/ModKeyMappingImpl.java +++ /dev/null @@ -1,146 +0,0 @@ -package net.leawind.mc.util.modkeymapping; - - -import net.minecraft.client.KeyMapping; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Timer; -import java.util.TimerTask; -import java.util.function.Supplier; - -@SuppressWarnings("unused") -public final class ModKeyMappingImpl extends KeyMapping implements ModKeyMapping { - private long holdLength = 300; - private long pressLength = 300; - private long keyDownTime = 0; - private @Nullable Timer timer = null; - private @Nullable Supplier ondown = null; - private @Nullable Supplier onup = null; - private @Nullable Supplier onhold = null; - private @Nullable Supplier onpress = null; - - /** - * @param id 按键映射的标识符,用于可翻译文本 - * @param defaultValue 默认按键 - * @param categoryKey 类别标识符,用于可翻译文本 - */ - public ModKeyMappingImpl (String id, int defaultValue, String categoryKey) { - super(id, defaultValue, categoryKey); - mappings.put(id, this); - } - - @Override - public boolean isDown () { - return super.isDown(); - } - - @Override - public ModKeyMappingImpl holdLength (long holdLength) { - this.holdLength = holdLength; - return this; - } - - @Override - public ModKeyMappingImpl pressLength (long pressLength) { - this.pressLength = pressLength; - return this; - } - - @Override - public ModKeyMappingImpl onDown (@NotNull Runnable handler) { - return onDown(() -> { - handler.run(); - return false; - }); - } - - @Override - public ModKeyMappingImpl onDown (@NotNull Supplier handler) { - ondown = handler; - return this; - } - - @Override - public ModKeyMappingImpl onUp (@NotNull Runnable handler) { - return onUp(() -> { - handler.run(); - return false; - }); - } - - @Override - public ModKeyMappingImpl onUp (@NotNull Supplier handler) { - onup = handler; - return this; - } - - @Override - public ModKeyMappingImpl onPress (@NotNull Runnable handler) { - return onPress(() -> { - handler.run(); - return false; - }); - } - - @Override - public ModKeyMappingImpl onPress (@NotNull Supplier handler) { - onpress = handler; - return this; - } - - @Override - public ModKeyMappingImpl onHold (@NotNull Runnable handler) { - return onHold(() -> { - handler.run(); - return false; - }); - } - - @Override - public ModKeyMappingImpl onHold (@NotNull Supplier handler) { - onhold = handler; - return this; - } - - @Override - public void setDown (boolean down) { - boolean wasDown = isDown(); - super.setDown(down); - long now = System.currentTimeMillis(); - if (!wasDown && down) { - // key down - if (handle(ondown)) { - return; - } - keyDownTime = now; - if (onhold != null) { - timer = new Timer(); - timer.schedule(new TimerTask() { - @Override - public void run () { - handle(onhold); - timer = null; - } - }, holdLength); - } - } else if (wasDown && !down) { - // key up - long sinceKeydown = now - keyDownTime; - if (handle(onup)) { - return; - } - if (sinceKeydown < pressLength) { - if (timer != null) { - timer.cancel(); - timer = null; - } - handle(onpress); - } - } - } - - private static boolean handle (@Nullable Supplier handler) { - return handler != null && handler.get(); - } -} diff --git a/common/src/main/resources/assets/minecraft/item_patterns/hold_to_aim/vanilla.json b/common/src/main/resources/assets/minecraft/item_patterns/hold_to_aim/vanilla.json index ed913595..4191ef01 100644 --- a/common/src/main/resources/assets/minecraft/item_patterns/hold_to_aim/vanilla.json +++ b/common/src/main/resources/assets/minecraft/item_patterns/hold_to_aim/vanilla.json @@ -1,4 +1,15 @@ [ + "minecraft:bucket", + "minecraft:water_bucket", + "minecraft:lava_bucket", + "minecraft:cod_bucket", + "minecraft:salmon_bucket", + "minecraft:tropical_fish_bucket", + "minecraft:pufferfish_bucket", + "minecraft:axolotl_bucket", + "minecraft:tadpole_bucket", + "minecraft:lily_pad", + "minecraft:frogspawn", "minecraft:crossbow{Charged:1b}", "minecraft:ender_pearl", "minecraft:snowball", diff --git a/common/src/main/resources/assets/minecraft/lang/en_us.json b/common/src/main/resources/assets/minecraft/lang/en_us.json index d4ff65fa..2fbdf863 100644 --- a/common/src/main/resources/assets/minecraft/lang/en_us.json +++ b/common/src/main/resources/assets/minecraft/lang/en_us.json @@ -1,64 +1,83 @@ { + "modmenu.nameTranslation.leawind_third_person": "Leawind's Third Person", + "modmenu.descriptionTranslation.leawind_third_person": "A practical, smooth, feature-rich third-person mod.", "leawind_third_person.text.title": "Leawind's Third Person", - "leawind_third_person.text.description": "Enhanced third person perspective", - "leawind_third_person.option_category.general": "General", - "leawind_third_person.option_category.general.desc": "general", + "leawind_third_person.text.description": "A practical, smooth, feature-rich third-person mod.", + "leawind_third_person.button.sync_to_aiming_mode": "Synchronize with aiming mode", + "leawind_third_person.button.sync_to_aiming_mode.text": "", + "leawind_third_person.button.sync_to_aiming_mode.desc": "Synchronize the settings from aiming mode。", + "leawind_third_person.button.sync_to_normal_mode": "Synchronize with normal mode", + "leawind_third_person.button.sync_to_normal_mode.text": "", + "leawind_third_person.button.sync_to_normal_mode.desc": "Synchronize the settings from normal mode。", + "leawind_third_person.option_category.common": "Common", + "leawind_third_person.option_category.common.desc": "Commonly used configs.", "leawind_third_person.option_category.camera_offset": "Camera Offset", - "leawind_third_person.option_category.camera_offset.desc": "Adjust parameters like camera offset. You don't need to manually adjust the options on this page, as you can hold Z in the game and use the mouse to adjust.", - "leawind_third_person.option_category.smooth_halflife": "Smoothness", - "leawind_third_person.option_category.smooth_halflife.desc": "Smoothness of certain values.", - "leawind_third_person.option_category.misc": "Misc", - "leawind_third_person.option_category.misc.desc": "Miscellaneous", - "leawind_third_person.option_category.aiming_check": "Aiming Check", - "leawind_third_person.option_category.aiming_check.desc": "Rules to determine if the current mode is aiming.", - "leawind_third_person.option_group.camera_distance_adjustment": "Camera Distance Adjustment", - "leawind_third_person.option_group.camera_distance_adjustment.desc": "Adjust the camera-to-player distance using the mouse wheel when holding specific keys in third-person mode. This setting determines the adjustment range.", + "leawind_third_person.option_category.camera_offset.desc": "Adjust parameters like camera offset.", + "leawind_third_person.option_category.smoothness": "Smoothness", + "leawind_third_person.option_category.smoothness.desc": "Adjust smoothness effect", + "leawind_third_person.option_category.item_predicates": "Item Predicates", + "leawind_third_person.option_category.item_predicates.desc": "Item predicates to match items. Reference to https://leawind.github.io/Third-Person/en-US/?search=Item+Predicate", + "leawind_third_person.option_category.other": "Other", + "leawind_third_person.option_category.other.desc": "Other configs not commonly used.", + "leawind_third_person.option_group.camera_distance_adjustment": "Camera Distance Factor Adjustment", + "leawind_third_person.option_group.camera_distance_adjustment.desc": "Adjust the camera-to-player distance factor using the mouse wheel when holding specific keys in third-person mode. This setting determines the adjustment range.", "leawind_third_person.option_group.normal_mode": "Normal Mode", - "leawind_third_person.option_group.normal_mode.desc": "When not aiming", + "leawind_third_person.option_group.normal_mode.desc": "When not aiming.", "leawind_third_person.option_group.aiming_mode": "Aiming Mode", "leawind_third_person.option_group.aiming_mode.desc": "When aiming with bow, crossbow, trident, etc.", "leawind_third_person.option_group.crosshair": "Crosshair", "leawind_third_person.option_group.crosshair.desc": "", - "leawind_third_person.option_group.player_rotation": "Player Rotation", + "leawind_third_person.option_group.player_rotation": "Player Entity Rotation", "leawind_third_person.option_group.player_rotation.desc": "", - "leawind_third_person.option_group.player_fade_out": "Player Fade Out", + "leawind_third_person.option_group.player_fade_out": "Player Entity Fade Out", "leawind_third_person.option_group.player_fade_out.desc": "Fade out the player in tight spaces to avoid obstructing the view.", - "leawind_third_person.option_group.adjusting_camera": "Adjusting Camera", + "leawind_third_person.option_group.adjusting_camera": "When adjusting Camera", "leawind_third_person.option_group.adjusting_camera.desc": "By default, holding Z allows you to use the mouse to adjust the camera's offset and distance.", "leawind_third_person.option_group.behavior_interacting": "Player Rotation During Interaction", "leawind_third_person.option_group.behavior_interacting.desc": "How the player rotates when pressing the interact key (attack|use|select).", - "leawind_third_person.option.is_mod_enable": "Is this mod enabled?", - "leawind_third_person.option.is_mod_enable.desc": "Disabling this will show the original third-person perspective, but you can still open the configuration screen with a key.", - "leawind_third_person.option.is_third_person_mode": "Is third-person mode enabled?", - "leawind_third_person.option.is_third_person_mode.desc": "Disabling this will switch to first-person mode.", + "leawind_third_person.option.is_mod_enabled": "Is this mod enabled?", + "leawind_third_person.option.is_mod_enabled.desc": "Disabling this will show the original third-person perspective, but you can still open the configuration screen with a key.", + "leawind_third_person.option.normal_rotate_mode": "Normal Rotate Mode", + "leawind_third_person.option.normal_rotate_mode.desc": "", + "leawind_third_person.option.normal_rotate_mode.interest_point": "Turn to Interest Point", + "leawind_third_person.option.normal_rotate_mode.camera_crosshair": "Turn to Camera Crosshair", + "leawind_third_person.option.normal_rotate_mode.moving_direction": "Turn to Moving Direction", + "leawind_third_person.option.normal_rotate_mode.parallel_with_camera": "Parallel With Camera", + "leawind_third_person.option.normal_rotate_mode.none": "None", "leawind_third_person.option.config_screen_api": "Config Screen API", "leawind_third_person.option.config_screen_api.desc": "", "leawind_third_person.option.center_offset_when_flying": "Automatically center when flying", "leawind_third_person.option.center_offset_when_flying.desc": "When the entity the camera is attached to is flying with elytra, do not use camera offset.", + "leawind_third_person.option.temp_first_person_in_narrow_space": "Enter first-person in narrow space", + "leawind_third_person.option.temp_first_person_in_narrow_space.desc": "Temporarily enter first person while player is in narrow space.", "leawind_third_person.option.player_fade_out_enabled": "Enable player fade-out", - "leawind_third_person.option.player_fade_out_enabled.desc": "When the camera is close to the player, make the player semi-transparent.", + "leawind_third_person.option.player_fade_out_enabled.desc": "When player blocks the sight, make the player semi-transparent.", + "leawind_third_person.option.gaze_opacity": "Gaze opacity", + "leawind_third_person.option.gaze_opacity.desc": "When crosshair on entity, set entity opacity to this value.", + "leawind_third_person.option.player_invisible_threshold": "Player invisible threshold", + "leawind_third_person.option.player_invisible_threshold.desc": "When camera is close enough, player becomes invisible.", "leawind_third_person.option.use_camera_pick_in_creative": "Use camera pick in creative mode", "leawind_third_person.option.use_camera_pick_in_creative.desc": "In creative mode, the player's line of sight will not be obstructed, and the crosshair indicates the interaction point.", "leawind_third_person.option.lock_camera_pitch_angle": "Lock camera pitch angle", "leawind_third_person.option.lock_camera_pitch_angle.desc": "After locking, it is impossible to rotate up and down.", - "leawind_third_person.option.player_rotate_with_camera_when_not_aiming": "Force player to rotate with the camera when not aiming", - "leawind_third_person.option.player_rotate_with_camera_when_not_aiming.desc": "Force the player's line of sight to remain parallel to the camera's line of sight when not aiming.", - "leawind_third_person.option.rotate_to_moving_direction": "Automatically turn to face the direction of movement when the player moves", - "leawind_third_person.option.rotate_to_moving_direction.desc": "Automatically turn to face the direction of movement when the player moves.", "leawind_third_person.option.auto_turn_body_drawing_a_bow": "Automatically turn sideways when drawing a bow", "leawind_third_person.option.auto_turn_body_drawing_a_bow.desc": "Automatically turn sideways when drawing a bow, with the hand holding the bow in front and the hand pulling the string behind.", "leawind_third_person.option.render_crosshair_when_aiming": "Display crosshair when aiming", "leawind_third_person.option.render_crosshair_when_aiming.desc": "Display crosshair in aiming mode.", "leawind_third_person.option.render_crosshair_when_not_aiming": "Display crosshair when not aiming", "leawind_third_person.option.render_crosshair_when_not_aiming.desc": "Display crosshair in non-aiming mode.", + "leawind_third_person.option.hide_crosshair_when_flying": "Hide crosshair when flying", + "leawind_third_person.option.hide_crosshair_when_flying.desc": "Hide crosshair when flying.", "leawind_third_person.option.available_distance_count": "Number of available distances", "leawind_third_person.option.available_distance_count.desc": "Use the mouse wheel to adjust the camera-to-player distance. This option represents the number of distance levels.", - "leawind_third_person.option.camera_distance_min": "Minimum distance", - "leawind_third_person.option.camera_distance_min.desc": "The minimum distance from the player when adjusting with the mouse wheel. The player entity becomes invisible when at the minimum distance (if this feature is enabled).", - "leawind_third_person.option.camera_distance_max": "Maximum distance", + "leawind_third_person.option.camera_distance_min": "Minimum Distance Factor", + "leawind_third_person.option.camera_distance_min.desc": "The minimum distance factor from the player when adjusting with the mouse wheel.", + "leawind_third_person.option.camera_distance_max": "Maximum Distance Factor", "leawind_third_person.option.camera_distance_max.desc": "The maximum distance from the player when adjusting with the mouse wheel.", "leawind_third_person.option.flying_smooth_halflife": "Smoothness for camera following player during flight", "leawind_third_person.option.flying_smooth_halflife.desc": "", + "leawind_third_person.option.t2f_transition_halflife": "Transition smooth factor", + "leawind_third_person.option.t2f_transition_halflife.desc": "Smoothness when transition from third-person to first-person.", "leawind_third_person.option.adjusting_camera_offset_smooth_halflife": "Smoothness for adjusting camera offset", "leawind_third_person.option.adjusting_camera_offset_smooth_halflife.desc": "Smoothness for changes in camera offset as the mouse moves while adjusting the camera.", "leawind_third_person.option.adjusting_distance_smooth_halflife": "Smoothness for adjusting camera distance", @@ -71,7 +90,9 @@ "leawind_third_person.option.camera_offset_smooth_halflife.desc": "", "leawind_third_person.option.distance_smooth_halflife": "Distance smoothness", "leawind_third_person.option.distance_smooth_halflife.desc": "Time for the camera to move away from the player.", - "leawind_third_person.option.max_distance": "Maximum distance from camera to player", + "leawind_third_person.option.aiming_fov_divisor": "FOV zoom factor when aiming", + "leawind_third_person.option.aiming_fov_divisor.desc": "FOV zoom factor when aiming. The distance between the camera and the player will also change accordingly.", + "leawind_third_person.option.max_distance": "Maximum Camera-Player Distance Factor", "leawind_third_person.option.max_distance.desc": "", "leawind_third_person.option.offset_x": "X Offset", "leawind_third_person.option.is_centered": "Player Centered", @@ -83,18 +104,26 @@ "leawind_third_person.option.offset_center.desc": "Long-pressing specific keys can center the view. When centered, the X offset is fixed at 0, and the player is in the middle of the screen. This option adjusts the Y offset at this time.", "leawind_third_person.option.camera_ray_trace_length": "Camera ray trace distance", "leawind_third_person.option.camera_ray_trace_length.desc": "When the targeted object exceeds this distance while aiming, the player's line of sight will be parallel to the camera's line of sight.", + "leawind_third_person.option.camera_distance_mode": "Camera distance mode", + "leawind_third_person.option.camera_distance_mode.desc": "You will find the difference when adjusting the camera offset.", + "leawind_third_person.option.camera_distance_mode.plane": "Plane distance", + "leawind_third_person.option.camera_distance_mode.straight": "Straight distance", + "leawind_third_person.option.rotate_center_height_offset": "Rotate center height offset", + "leawind_third_person.option.rotate_center_height_offset.desc": "Offset the rotation center height.", "leawind_third_person.option.enable_target_entity_predict": "Enable target entity prediction when aiming", "leawind_third_person.option.enable_target_entity_predict.desc": "When aiming, predict the target entity you want to shoot, calculate the player's rotation based on this, for better aiming at the target.", + "leawind_third_person.option.skip_vanilla_second_person_camera": "Skip vanilla second person", + "leawind_third_person.option.skip_vanilla_second_person_camera.desc": "Only first-person and third-person perspectives are retained", + "leawind_third_person.option.disable_third_person_bob_view": "Disable third person view bobbing", + "leawind_third_person.option.disable_third_person_bob_view.desc": "In third person, disable view bobbing", + "leawind_third_person.option.allow_double_tap_sprint": "Allow double-tap sprinting", + "leawind_third_person.option.allow_double_tap_sprint.desc": "In third person mode, allow the vanilla double-tap sprinting feature", "leawind_third_person.option.auto_rotate_interacting": "Automatically rotate when interacting", "leawind_third_person.option.auto_rotate_interacting.desc": "When pressing the interact key (attack|use|select), the player will automatically rotate.", - "leawind_third_person.option.rotate_interacting_type": "Rotation type", - "leawind_third_person.option.rotate_interacting_type.desc": "", - "leawind_third_person.option.rotate_interacting_type.turn_to_crosshair": "Turn to crosshair", - "leawind_third_person.option.rotate_interacting_type.turn_with_camera": "Turn in the same direction as the camera", - "leawind_third_person.option.turn_with_camera_when_enter_first_person": "Player turns to camera direction when entering first person", - "leawind_third_person.option.turn_with_camera_when_enter_first_person.desc": "When entering first-person view, make the player face in the same direction as the camera in the just-ended third-person perspective.", - "leawind_third_person.option.projectile_auto_aim": "Automatically aim when shooting", - "leawind_third_person.option.projectile_auto_aim.desc": "When aiming at a block or entity, automatically adjust the shooting angle to hit the target.", + "leawind_third_person.option.do_not_rotate_when_eating": "Do not rotate when eating", + "leawind_third_person.option.do_not_rotate_when_eating.desc": "", + "leawind_third_person.option.determine_aim_mode_by_animation": "Determine aiming mode by animation", + "leawind_third_person.option.determine_aim_mode_by_animation.desc": "When specific animation is played, automatically enter aiming mode.", "leawind_third_person.option.hold_to_aim_item_pattern_expressions": "Automatically aim when held", "leawind_third_person.option.hold_to_aim_item_pattern_expressions.desc": "When the player's left or right hand is holding an item that matches any rule, automatically enter aiming mode.", "leawind_third_person.option.use_to_aim_item_pattern_expressions": "Automatically aim when used", diff --git a/common/src/main/resources/assets/minecraft/lang/zh_cn.json b/common/src/main/resources/assets/minecraft/lang/zh_cn.json index 02a11fa6..99bea4cb 100644 --- a/common/src/main/resources/assets/minecraft/lang/zh_cn.json +++ b/common/src/main/resources/assets/minecraft/lang/zh_cn.json @@ -1,68 +1,87 @@ { + "modmenu.nameTranslation.leawind_third_person": "Leawind的第三人称", + "modmenu.descriptionTranslation.leawind_third_person": "一个实用、丝滑、功能丰富的第三人称模组", "leawind_third_person.text.title": "Leawind的第三人称", - "leawind_third_person.text.description": "提供更好的第三人称视角", - "leawind_third_person.option_category.general": "通用", - "leawind_third_person.option_category.general.desc": "一些常用的配置", + "leawind_third_person.text.description": "一个实用、丝滑、功能丰富的第三人称模组", + "leawind_third_person.button.sync_to_aiming_mode": "与瞄准模式同步", + "leawind_third_person.button.sync_to_aiming_mode.text": "", + "leawind_third_person.button.sync_to_aiming_mode.desc": "将瞄准模式的设置同步到此处", + "leawind_third_person.button.sync_to_normal_mode": "与正常模式同步", + "leawind_third_person.button.sync_to_normal_mode.text": "", + "leawind_third_person.button.sync_to_normal_mode.desc": "将正常模式的设置同步到此处", + "leawind_third_person.option_category.common": "常用", + "leawind_third_person.option_category.common.desc": "一些常用的配置。", "leawind_third_person.option_category.camera_offset": "相机偏移", - "leawind_third_person.option_category.camera_offset.desc": "调整玩家在屏幕上的位置等参数。!你不需要手动调整此页的选项们,因为可以在游戏中按住Z后用鼠标调整。", - "leawind_third_person.option_category.smooth_halflife": "平滑系数", - "leawind_third_person.option_category.smooth_halflife.desc": "一些值的平滑程度。", - "leawind_third_person.option_category.misc": "杂项", - "leawind_third_person.option_category.misc.desc": "", - "leawind_third_person.option_category.aiming_check": "瞄准检查", - "leawind_third_person.option_category.aiming_check.desc": "用于判断当前是否是瞄准模式的规则", + "leawind_third_person.option_category.camera_offset.desc": "调整玩家在屏幕上的位置等参数。", + "leawind_third_person.option_category.smoothness": "平滑", + "leawind_third_person.option_category.smoothness.desc": "调整平滑效果", + "leawind_third_person.option_category.item_predicates": "物品谓词", + "leawind_third_person.option_category.item_predicates.desc": "用于匹配物品的物品谓词,参考 https://leawind.github.io/Third-Person/zh-CN/Details/#物品谓词", + "leawind_third_person.option_category.other": "其他", + "leawind_third_person.option_category.other.desc": "", "leawind_third_person.option_group.camera_distance_adjustment": "相机距离调节", - "leawind_third_person.option_group.camera_distance_adjustment.desc": "第三人称下按住特定按键时可以用滚轮调整相机到玩家的距离。此处可以设置距离的调节范围。", + "leawind_third_person.option_group.camera_distance_adjustment.desc": "第三人称下按住特定按键时可以用滚轮调整相机到玩家的距离系数。此处可以设置距离的调节范围。", "leawind_third_person.option_group.normal_mode": "正常模式", - "leawind_third_person.option_group.normal_mode.desc": "当没有瞄准时", + "leawind_third_person.option_group.normal_mode.desc": "当没有瞄准时。", "leawind_third_person.option_group.aiming_mode": "瞄准模式", - "leawind_third_person.option_group.aiming_mode.desc": "当使用弓|弩|戟等瞄准时", + "leawind_third_person.option_group.aiming_mode.desc": "当使用弓|弩|戟等瞄准时。", "leawind_third_person.option_group.crosshair": "第三人称准星", "leawind_third_person.option_group.crosshair.desc": "", - "leawind_third_person.option_group.player_rotation": "玩家转动", + "leawind_third_person.option_group.player_rotation": "玩家实体转动", "leawind_third_person.option_group.player_rotation.desc": "", "leawind_third_person.option_group.player_fade_out": "玩家半透明", "leawind_third_person.option_group.player_fade_out.desc": "狭小空间内让玩家淡出,避免遮挡视线。", - "leawind_third_person.option_group.adjusting_camera": "调整相机", + "leawind_third_person.option_group.adjusting_camera": "调整相机时", "leawind_third_person.option_group.adjusting_camera.desc": "默认按住Z可以用鼠标调节相机的偏移量和远近。", "leawind_third_person.option_group.behavior_interacting": "交互时玩家转动方式", - "leawind_third_person.option_group.behavior_interacting.desc": "当按下交互键(攻击|使用|选取)时,玩家如何转动", - "leawind_third_person.option.is_mod_enable": "本模组是否启用", - "leawind_third_person.option.is_mod_enable.desc": "禁用后将显示原版第三人称视角,但仍可以按键打开配置屏幕", - "leawind_third_person.option.is_third_person_mode": "是否启用第三人称模式", - "leawind_third_person.option.is_third_person_mode.desc": "禁用则变成第一人称", + "leawind_third_person.option_group.behavior_interacting.desc": "当按下交互键(攻击|使用|选取)时,玩家如何转动。", + "leawind_third_person.option.is_mod_enabled": "本模组是否启用", + "leawind_third_person.option.is_mod_enabled.desc": "禁用后将显示原版第三人称视角,但仍可以按键打开配置屏幕。", + "leawind_third_person.option.normal_rotate_mode": "正常旋转模式", + "leawind_third_person.option.normal_rotate_mode.desc": "", + "leawind_third_person.option.normal_rotate_mode.interest_point": "转向兴趣点", + "leawind_third_person.option.normal_rotate_mode.camera_crosshair": "转向相机准星", + "leawind_third_person.option.normal_rotate_mode.moving_direction": "转向移动方向", + "leawind_third_person.option.normal_rotate_mode.parallel_with_camera": "保持与相机平行", + "leawind_third_person.option.normal_rotate_mode.none": "保持不动", "leawind_third_person.option.config_screen_api": "配置屏幕API", "leawind_third_person.option.config_screen_api.desc": "", "leawind_third_person.option.center_offset_when_flying": "飞行时自动居中", - "leawind_third_person.option.center_offset_when_flying.desc": "当相机附着的实体正在使用鞘翅飞行时,不使用相机偏移量", + "leawind_third_person.option.center_offset_when_flying.desc": "当相机附着的实体正在使用鞘翅飞行时,不使用相机偏移量。", + "leawind_third_person.option.temp_first_person_in_narrow_space": "狭窄空间暂时第一人称", + "leawind_third_person.option.temp_first_person_in_narrow_space.desc": "当玩家处于狭窄空间中时,暂时进入第一人称。", "leawind_third_person.option.player_fade_out_enabled": "启用玩家半透明", - "leawind_third_person.option.player_fade_out_enabled.desc": "当相机靠近玩家时,让玩家变得半透明", + "leawind_third_person.option.player_fade_out_enabled.desc": "当玩家实体阻挡视野时,让其变得半透明。", + "leawind_third_person.option.gaze_opacity": "注视透明度", + "leawind_third_person.option.gaze_opacity.desc": "当准星放在相机实体上时,将相机实体的不透明度降低到这个值。", + "leawind_third_person.option.player_invisible_threshold": "玩家不可见距离阈值", + "leawind_third_person.option.player_invisible_threshold.desc": "当相机离玩家足够近时,玩家变得不可见。", "leawind_third_person.option.use_camera_pick_in_creative": "创造模式下使用相机pick", - "leawind_third_person.option.use_camera_pick_in_creative.desc": "创造模式下玩家视线不会被遮挡,准星指哪就交互哪", + "leawind_third_person.option.use_camera_pick_in_creative.desc": "创造模式下玩家视线不会被遮挡,准星指哪就交互哪。", "leawind_third_person.option.lock_camera_pitch_angle": "锁定相机的俯仰角", - "leawind_third_person.option.lock_camera_pitch_angle.desc": "锁定后无法上下转动", - "leawind_third_person.option.player_rotate_with_camera_when_not_aiming": "未瞄准时强制玩家跟随相机转动", - "leawind_third_person.option.player_rotate_with_camera_when_not_aiming.desc": "未瞄准时强制玩家视线和相机视线保持平行", - "leawind_third_person.option.rotate_to_moving_direction": "玩家移动时自动转向前进的方向", - "leawind_third_person.option.rotate_to_moving_direction.desc": "玩家移动时自动转向前进的方向", + "leawind_third_person.option.lock_camera_pitch_angle.desc": "锁定后无法上下转动。", "leawind_third_person.option.auto_turn_body_drawing_a_bow": "拉弓时自动侧身", - "leawind_third_person.option.auto_turn_body_drawing_a_bow.desc": "拉弓时自动侧身,让持弓的手在前,拉弦的手在后", + "leawind_third_person.option.auto_turn_body_drawing_a_bow.desc": "拉弓时自动侧身,让持弓的手在前,拉弦的手在后。", "leawind_third_person.option.render_crosshair_when_aiming": "瞄准时显示准星", - "leawind_third_person.option.render_crosshair_when_aiming.desc": "在瞄准状态下显示准星", + "leawind_third_person.option.render_crosshair_when_aiming.desc": "在瞄准状态下显示准星。", "leawind_third_person.option.render_crosshair_when_not_aiming": "未瞄准时显示准星", - "leawind_third_person.option.render_crosshair_when_not_aiming.desc": "在未瞄准状态下显示准星", + "leawind_third_person.option.render_crosshair_when_not_aiming.desc": "在未瞄准状态下显示准星。", + "leawind_third_person.option.hide_crosshair_when_flying": "飞行时隐藏准星", + "leawind_third_person.option.hide_crosshair_when_flying.desc": "飞行时隐藏准星。", "leawind_third_person.option.available_distance_count": "可选的距离数量", - "leawind_third_person.option.available_distance_count.desc": "使用鼠标滚轮可以调整相机到玩家距离,该选项的值是距离的档位数量", - "leawind_third_person.option.camera_distance_min": "距离下限", - "leawind_third_person.option.camera_distance_min.desc": "使用滚轮调整时相机到玩家的最小距离。当处于最小距离时玩家实体会不可见(如果开启该功能)", - "leawind_third_person.option.camera_distance_max": "距离上限", - "leawind_third_person.option.camera_distance_max.desc": "使用滚轮调整时相机到玩家的最大距离", + "leawind_third_person.option.available_distance_count.desc": "使用鼠标滚轮可以调整相机到玩家距离,该选项的值是距离的档位数量。", + "leawind_third_person.option.camera_distance_min": "距离系数下限", + "leawind_third_person.option.camera_distance_min.desc": "使用滚轮调整时相机到玩家的最小距离系数。", + "leawind_third_person.option.camera_distance_max": "距离上限系数", + "leawind_third_person.option.camera_distance_max.desc": "使用滚轮调整时相机到玩家的最大距离系数。", "leawind_third_person.option.flying_smooth_halflife": "飞行时相机跟随玩家的平滑系数", "leawind_third_person.option.flying_smooth_halflife.desc": "", + "leawind_third_person.option.t2f_transition_halflife": "过渡平滑系数", + "leawind_third_person.option.t2f_transition_halflife.desc": "从第三人称平滑过渡到第一人称时的平滑系数。", "leawind_third_person.option.adjusting_camera_offset_smooth_halflife": "调整相机偏移量的平滑系数", - "leawind_third_person.option.adjusting_camera_offset_smooth_halflife.desc": "调整相机时,相机偏移量随鼠标移动而变化的平滑系数", + "leawind_third_person.option.adjusting_camera_offset_smooth_halflife.desc": "调整相机时,相机偏移量随鼠标移动而变化的平滑系数。", "leawind_third_person.option.adjusting_distance_smooth_halflife": "调整相机距离的平滑系数", - "leawind_third_person.option.adjusting_distance_smooth_halflife.desc": "调整相机距离时,相机到玩家距离变化的平滑系数", + "leawind_third_person.option.adjusting_distance_smooth_halflife.desc": "调整相机距离时,相机到玩家距离变化的平滑系数。", "leawind_third_person.option.smooth_halflife_horizon": "水平平滑系数", "leawind_third_person.option.smooth_halflife_horizon.desc": "", "leawind_third_person.option.smooth_halflife_vertical": "垂直平滑系数", @@ -70,37 +89,47 @@ "leawind_third_person.option.camera_offset_smooth_halflife": "相机偏移平滑系数", "leawind_third_person.option.camera_offset_smooth_halflife.desc": "", "leawind_third_person.option.distance_smooth_halflife": "距离平滑系数", - "leawind_third_person.option.distance_smooth_halflife.desc": "相机远离玩家的时间", - "leawind_third_person.option.max_distance": "相机到玩家的最大距离", + "leawind_third_person.option.distance_smooth_halflife.desc": "相机远离玩家的时间。", + "leawind_third_person.option.aiming_fov_divisor": "瞄准时视野缩放倍率", + "leawind_third_person.option.aiming_fov_divisor.desc": "瞄准时视野范围(FOV)缩放倍率,相机与玩家间的距离会随之变化。", + "leawind_third_person.option.max_distance": "相机到玩家的最大距离系数", "leawind_third_person.option.max_distance.desc": "", "leawind_third_person.option.offset_x": "X偏移", "leawind_third_person.option.is_centered": "玩家居中", - "leawind_third_person.option.is_centered.desc": "玩家是否位于屏幕中央", + "leawind_third_person.option.is_centered.desc": "玩家是否位于屏幕中央。", "leawind_third_person.option.offset_x.desc": "", "leawind_third_person.option.offset_y": "Y偏移", "leawind_third_person.option.offset_y.desc": "", "leawind_third_person.option.offset_center": "居中时的Y偏移", "leawind_third_person.option.offset_center.desc": "长按特定按键可以居中,居中状态下X偏移固定为0,玩家处于屏幕中间。本选项可以调整此时的Y偏移。", "leawind_third_person.option.camera_ray_trace_length": "相机视线探测距离", - "leawind_third_person.option.camera_ray_trace_length.desc": "当瞄准的物体超过这个距离时,玩家视线会和相机视线平行", + "leawind_third_person.option.camera_ray_trace_length.desc": "当瞄准的物体超过这个距离时,玩家视线会和相机视线平行。", + "leawind_third_person.option.camera_distance_mode": "相机距离模式", + "leawind_third_person.option.camera_distance_mode.desc": "你将在调整相机偏移量时发现区别。", + "leawind_third_person.option.camera_distance_mode.plane": "平面距离", + "leawind_third_person.option.camera_distance_mode.straight": "直线距离", + "leawind_third_person.option.rotate_center_height_offset": "旋转中心高度偏移量", + "leawind_third_person.option.rotate_center_height_offset.desc": "将相机的旋转中心上下偏移一段距离", "leawind_third_person.option.enable_target_entity_predict": "瞄准时启用目标实体预测", - "leawind_third_person.option.enable_target_entity_predict.desc": "瞄准时,预测想要射击的目标实体,据此计算玩家朝向,以便更好地瞄准目标。", + "leawind_third_person.option.enable_target_entity_predict.desc": "瞄准时,自动预测想要射击的目标实体,据此计算玩家朝向,以便更好地瞄准目标。", + "leawind_third_person.option.skip_vanilla_second_person_camera": "跳过原版第二人称", + "leawind_third_person.option.skip_vanilla_second_person_camera.desc": "仅保留第一人称和第三人称视角", + "leawind_third_person.option.disable_third_person_bob_view": "禁用第三人称视角摇晃", + "leawind_third_person.option.disable_third_person_bob_view.desc": "第三人称下禁用视角摇晃", + "leawind_third_person.option.allow_double_tap_sprint": "允许双击疾跑", + "leawind_third_person.option.allow_double_tap_sprint.desc": "第三人称下允许原版的双击方向键疾跑功能", "leawind_third_person.option.auto_rotate_interacting": "交互时自动转身", - "leawind_third_person.option.auto_rotate_interacting.desc": "当按下交互键(攻击|使用|选取)时,玩家会自动转身", - "leawind_third_person.option.rotate_interacting_type": "转身方式", - "leawind_third_person.option.rotate_interacting_type.desc": "", - "leawind_third_person.option.rotate_interacting_type.turn_to_crosshair": "转向准星", - "leawind_third_person.option.rotate_interacting_type.turn_with_camera": "与相机朝向相同", - "leawind_third_person.option.turn_with_camera_when_enter_first_person": "进入第一人称时玩家转向相机朝向", - "leawind_third_person.option.turn_with_camera_when_enter_first_person.desc": "进入第一人称视角时,让玩家面向刚刚结束的第三人称视角中相机的相同方向。", - "leawind_third_person.option.projectile_auto_aim": "射击时自动瞄准", - "leawind_third_person.option.projectile_auto_aim.desc": "瞄准方块或实体时,自动调整射击角度以击中该目标", + "leawind_third_person.option.auto_rotate_interacting.desc": "当按下交互键(攻击|使用|选取)时,玩家会自动转身。", + "leawind_third_person.option.do_not_rotate_when_eating": "吃东西时不转身", + "leawind_third_person.option.do_not_rotate_when_eating.desc": "", + "leawind_third_person.option.determine_aim_mode_by_animation": "通过动画判断瞄准模式", + "leawind_third_person.option.determine_aim_mode_by_animation.desc": "当相机实体播放特定动画时,自动进入瞄准模式。", "leawind_third_person.option.hold_to_aim_item_pattern_expressions": "手持时自动瞄准", - "leawind_third_person.option.hold_to_aim_item_pattern_expressions.desc": "当玩家左手或右手拿着的物品符合任意一条规则时,自动进入瞄准模式", + "leawind_third_person.option.hold_to_aim_item_pattern_expressions.desc": "当玩家左手或右手拿着的物品符合任意一条规则时,自动进入瞄准模式。", "leawind_third_person.option.use_to_aim_item_pattern_expressions": "使用时自动瞄准", - "leawind_third_person.option.use_to_aim_item_pattern_expressions.desc": "当玩家正在使用的物品符合任意一条规则时,自动进入瞄准模式", + "leawind_third_person.option.use_to_aim_item_pattern_expressions.desc": "当玩家正在使用的物品符合任意一条规则时,自动进入瞄准模式。", "leawind_third_person.option.use_to_first_person_pattern_expressions": "使用时暂时进入第一人称", - "leawind_third_person.option.use_to_first_person_pattern_expressions.desc": "当玩家正在使用的物品符合任意一条规则时,暂时进入第一人称视角", + "leawind_third_person.option.use_to_first_person_pattern_expressions.desc": "当玩家正在使用的物品符合任意一条规则时,暂时进入第一人称视角。", "key.categories.leawind_third_person": "Leawind的第三人称", "key.leawind_third_person.open_config_menu": "打开配置菜单", "key.leawind_third_person.toggle_mod_enable": "切换模组是否启用", diff --git a/common/src/main/resources/leawind_third_person-common.mixins.json b/common/src/main/resources/leawind_third_person-common.mixins.json index a48f7460..f2a4d35e 100644 --- a/common/src/main/resources/leawind_third_person-common.mixins.json +++ b/common/src/main/resources/leawind_third_person-common.mixins.json @@ -1,16 +1,18 @@ { "required": true, - "package": "net.leawind.mc.thirdperson.mixin", + "package": "com.github.leawind.thirdperson.mixin", "compatibilityLevel": "JAVA_17", "minVersion": "0.8", "client": [ "CameraInvoker", - "LocalPlayerInvoker", "ClientLevelInvoker", + "GameRendererInvoker", + "RenderTypeInvoker", + "CameraTypeMixin", "LevelRendererMixin", + "LocalPlayerMixin", "ModelPartCubeMixin", "RenderTypeMixin", - "CameraMixin", "MinecraftMixin", "GuiMixin", "KeyboardInputMixin", diff --git a/common/src/main/resources/leawind_third_person.accesswidener b/common/src/main/resources/leawind_third_person.accesswidener index 13268c32..236e6b15 100644 --- a/common/src/main/resources/leawind_third_person.accesswidener +++ b/common/src/main/resources/leawind_third_person.accesswidener @@ -1 +1 @@ -accessWidener v2 named \ No newline at end of file +accessWidener v2 named diff --git a/common/src/test/java/com/github/leawind/thirdperson/util/math/decisionmap/test/DecisionMapTest.java b/common/src/test/java/com/github/leawind/thirdperson/util/math/decisionmap/test/DecisionMapTest.java new file mode 100644 index 00000000..36d7b5f2 --- /dev/null +++ b/common/src/test/java/com/github/leawind/thirdperson/util/math/decisionmap/test/DecisionMapTest.java @@ -0,0 +1,93 @@ +package com.github.leawind.thirdperson.util.math.decisionmap.test; + +import com.github.leawind.thirdperson.util.math.decisionmap.DecisionMap; +import java.util.Random; +import org.junit.jupiter.api.Test; + +public class DecisionMapTest { + boolean isSunny = false; + boolean isWindy = false; + double temperature = 25; + + @Test + public void testBuilder() { + final var DEFAULT = "Default operation: I wanna go to the park"; + final var SUNNY = "It is sunny, I don't go out"; + final var TOO_HOT = "Too hot, stay at home"; + final var PERFECT = "Perfect, Let's go!"; + + var builder = DecisionMap.builder(); + + builder.factor(0, "sunny", () -> isSunny); + builder.factor(1, "windy", () -> isWindy); + builder.factor(2, "hot", () -> temperature > 33); + builder.factor(3, "cold", () -> temperature < 16); + + // 默认情况 + builder.whenDefault(() -> DEFAULT); + // 当 sunny == true 时 + builder.when(0, true, () -> SUNNY); + // 太热时 + builder.when(2, true, () -> TOO_HOT); + // 一种特定情况 + builder.whenAll(0b0000, () -> PERFECT); + + var map = builder.build(); + + System.out.println(map.toDescriptionWithCases(true)); + + { + isSunny = false; + isWindy = true; + temperature = 23; + assert map.updateAll().make().equals(DEFAULT); + } + { + isSunny = true; + isWindy = false; + temperature = 23; + assert map.updateAll().make().equals(SUNNY); + } + { + isSunny = true; + isWindy = true; + temperature = 45; + assert map.updateAll().make().equals(TOO_HOT); + } + { + isSunny = false; + isWindy = false; + temperature = 23; + assert map.updateAll().make().equals(PERFECT); + } + } + + @Test + public void performanceTest() { + final var factorCount = 24; + final var filterMask = (1 << factorCount) - 1; + final var factors = new boolean[factorCount]; + + var builder = DecisionMap.builder(); + for (int i = 0; i < factorCount; i++) { + final int finalI = i; + builder.factor(String.format("Factor[%d]", i), () -> factors[finalI]); + } + builder.whenDefault(() -> "Default"); + + var random = new Random(12138); + for (int i = 0; i < 16; i++) { + final String message = String.format("Message[%d]", i); + + var mask = filterMask & random.nextInt(); + var values = mask & random.nextInt(); + + builder.when(mask, values, () -> message); + } + + var map = builder.build(); + System.out.println(map.toDescription()); + map.updateAll(); + map.make(); + } +} diff --git a/fabric/build.gradle b/fabric/build.gradle index bd8cbfe4..7cac013b 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -1,8 +1,6 @@ plugins { id "com.github.johnrengelman.shadow" version "7.1.2" - id "com.modrinth.minotaur" version "2.+" } - architectury { platformSetupLoomIde() fabric() @@ -18,16 +16,32 @@ configurations { developmentFabric.extendsFrom common } dependencies { + minecraft "com.mojang:minecraft:${minecraft_version}" + mappings loom.officialMojangMappings() modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}" - modApi "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}" - common(project(path: ":common", configuration: "namedElements")) { transitive false } - shadowCommon(project(path: ":common", configuration: "transformProductionFabric")) { transitive false } + modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}" + common(project(path: ":common", configuration: "namedElements")) { + transitive false + } + shadowCommon(project(path: ":common", configuration: "transformProductionFabric")) { + transitive false + } + + include modImplementation("org.joml:joml:${joml_version}") + // Architectury-api - modApi "dev.architectury:architectury-fabric:${architectury_version}" + modImplementation "dev.architectury:architectury-fabric:${architectury_version}" + // MixinExtras + include implementation(annotationProcessor("io.github.llamalad7:mixinextras-fabric:${fabric_mixin_extras_version}")) // Cloth Config API - modApi("me.shedaniel.cloth:cloth-config-fabric:${cloth_config_api_version}") { exclude(group: "net.fabricmc.fabric-api") } + modImplementation("me.shedaniel.cloth:cloth-config-fabric:${cloth_config_api_version}") { + exclude group: "net.fabricmc.fabric-api" + exclude module: 'modmenu' + } + // YACL + // modImplementation "dev.isxander.yacl:yet-another-config-lib-fabric:${yacl_mc_version}" // ModMenu - modApi("com.terraformersmc:modmenu:${modmenu_version}") { transitive(false) } + modImplementation "com.terraformersmc:modmenu:${modmenu_version}" } shadowJar { exclude "architectury.common.json" @@ -36,16 +50,18 @@ shadowJar { } remapJar { injectAccessWidener = true - input.set shadowJar.archiveFile + inputFile.set shadowJar.archiveFile dependsOn shadowJar } sourcesJar { - def commonSources = project(":common").sourcesJar + Task commonSources = project(":common").sourcesJar dependsOn commonSources - from commonSources.archiveFile.map { zipTree(it) } + from commonSources.archiveFile.map { + zipTree(it) + } } base { - archivesName = rootProject.archiveFileNameOfPlatform("fabric") + archivesName = rootProject.archiveFileNameOfLoader("fabric") version = "" } components.java { @@ -54,36 +70,6 @@ components.java { } } publishing { - publications { - mavenFabric(MavenPublication) { - artifactId = mod_id + "-" + project.name - from components.java - } - } - - repositories { - } -} - - -// https://github.com/modrinth/minotaur/blob/a151c425fb128cd5384242f25b6fbb0a1d18e325/README.md -modrinth { - debugMode = Boolean.parseBoolean(publish_debug_mode) - - token = System.getenv("MODRINTH_TOKEN") - - projectId = modrinth_project_id - versionNumber = mod_version - versionType = mod_version_type - versionName = "${mod_version}-mc${minecraft_version}-fabric" - changelog = readChangeLog() - - uploadFile = remapJar - autoAddDependsOn = false - dependencies { - required.project "fabric-api" - required.project "architectury-api" - optional.project "cloth-config" - optional.project "modmenu" - } + publications {} + repositories {} } diff --git a/fabric/src/main/java/com/github/leawind/thirdperson/fabric/ModMenuEntry.java b/fabric/src/main/java/com/github/leawind/thirdperson/fabric/ModMenuEntry.java new file mode 100644 index 00000000..b3e11da2 --- /dev/null +++ b/fabric/src/main/java/com/github/leawind/thirdperson/fabric/ModMenuEntry.java @@ -0,0 +1,14 @@ +package com.github.leawind.thirdperson.fabric; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; + +/** Modmenu 入口 */ +@SuppressWarnings("unused") +public class ModMenuEntry implements ModMenuApi { + @Override + public ConfigScreenFactory getModConfigScreenFactory() { + return ThirdPerson.CONFIG_MANAGER::getConfigScreen; + } +} diff --git a/fabric/src/main/java/com/github/leawind/thirdperson/fabric/ThirdPersonFabric.java b/fabric/src/main/java/com/github/leawind/thirdperson/fabric/ThirdPersonFabric.java new file mode 100644 index 00000000..a982ebdb --- /dev/null +++ b/fabric/src/main/java/com/github/leawind/thirdperson/fabric/ThirdPersonFabric.java @@ -0,0 +1,11 @@ +package com.github.leawind.thirdperson.fabric; + +import com.github.leawind.thirdperson.ThirdPerson; +import net.fabricmc.api.ClientModInitializer; + +@SuppressWarnings("unused") +public final class ThirdPersonFabric implements ClientModInitializer { + public void onInitializeClient() { + ThirdPerson.init(); + } +} diff --git a/fabric/src/main/java/com/github/leawind/thirdperson/fabric/mixin/CameraMixin.java b/fabric/src/main/java/com/github/leawind/thirdperson/fabric/mixin/CameraMixin.java new file mode 100644 index 00000000..e2692cff --- /dev/null +++ b/fabric/src/main/java/com/github/leawind/thirdperson/fabric/mixin/CameraMixin.java @@ -0,0 +1,49 @@ +package com.github.leawind.thirdperson.fabric.mixin; + +import com.github.leawind.thirdperson.api.base.GameEvents; +import com.github.leawind.thirdperson.api.client.event.ThirdPersonCameraSetupEvent; +import com.github.leawind.thirdperson.mixin.CameraInvoker; +import net.minecraft.client.Camera; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.BlockGetter; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = Camera.class, priority = 2000) +public class CameraMixin { + /** + * setup 方法中第三人称下移动相机之前 + * + *

    setup 方法位于真正渲染画面之前。 + * + *

    GameRender#render -> GameRender#renderLevel -> Camera#setup + */ + @Inject( + method = "setup", + at = + @At( + value = "INVOKE", + target = "Lnet/minecraft/client/Camera;move(DDD)V", + shift = At.Shift.BEFORE), + cancellable = true) + private void preMoveCamera( + BlockGetter level, + Entity attachedEntity, + boolean detached, + boolean reversedView, + float partialTick, + CallbackInfo ci) { + if (GameEvents.thirdPersonCameraSetup != null) { + var event = new ThirdPersonCameraSetupEvent(partialTick); + GameEvents.thirdPersonCameraSetup.accept(event); + if (event.set()) { + var camera = (Camera) (Object) this; + ((CameraInvoker) camera).invokeSetPosition(event.pos); + ((CameraInvoker) camera).invokeSetRotation(event.yRot, event.xRot); + ci.cancel(); + } + } + } +} diff --git a/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ExpectPlatformExampleImpl.java b/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ExpectPlatformExampleImpl.java deleted file mode 100644 index 8e503ad4..00000000 --- a/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ExpectPlatformExampleImpl.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.leawind.mc.thirdperson.fabric; - - -public class ExpectPlatformExampleImpl { - public static String getMessage () { - return "This is Fabric"; - } -} diff --git a/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ModMenuEntry.java b/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ModMenuEntry.java deleted file mode 100644 index ddc746a8..00000000 --- a/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ModMenuEntry.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.leawind.mc.thirdperson.fabric; - - -import com.terraformersmc.modmenu.api.ConfigScreenFactory; -import com.terraformersmc.modmenu.api.ModMenuApi; -import net.leawind.mc.thirdperson.ThirdPerson; - -/** - * Modmenu 入口 - */ -@SuppressWarnings("unused") -public class ModMenuEntry implements ModMenuApi { - @Override - public ConfigScreenFactory getModConfigScreenFactory () { - return ThirdPerson.CONFIG_MANAGER::getConfigScreen; - } -} diff --git a/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ThirdPersonFabric.java b/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ThirdPersonFabric.java deleted file mode 100644 index e36487b7..00000000 --- a/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ThirdPersonFabric.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.leawind.mc.thirdperson.fabric; - - -import net.fabricmc.api.ClientModInitializer; -import net.leawind.mc.thirdperson.ThirdPerson; - -@SuppressWarnings("unused") -public final class ThirdPersonFabric implements ClientModInitializer { - public void onInitializeClient () { - ThirdPerson.init(); - } -} diff --git a/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ThirdPersonResourcesImpl.java b/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ThirdPersonResourcesImpl.java deleted file mode 100644 index 4786f3ac..00000000 --- a/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/ThirdPersonResourcesImpl.java +++ /dev/null @@ -1,21 +0,0 @@ -package net.leawind.mc.thirdperson.fabric; - - -import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; -import net.fabricmc.fabric.api.resource.ResourceManagerHelper; -import net.leawind.mc.thirdperson.ThirdPersonResources; -import net.leawind.mc.thirdperson.fabric.resources.IdentifiableItemPatternManager; -import net.leawind.mc.util.annotations.VersionSensitive; -import net.minecraft.server.packs.PackType; - -/** - * @see ThirdPersonResources - */ -@SuppressWarnings("unused") -public class ThirdPersonResourcesImpl { - @VersionSensitive("Fabric register reload listener for custom resource") - public static void register () { - ThirdPersonResources.itemPatternManager = new IdentifiableItemPatternManager(); - ResourceManagerHelper.get(PackType.CLIENT_RESOURCES).registerReloadListener((IdentifiableResourceReloadListener)ThirdPersonResources.itemPatternManager); - } -} diff --git a/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/resources/IdentifiableItemPatternManager.java b/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/resources/IdentifiableItemPatternManager.java deleted file mode 100644 index 480a9db2..00000000 --- a/fabric/src/main/java/net/leawind/mc/thirdperson/fabric/resources/IdentifiableItemPatternManager.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.leawind.mc.thirdperson.fabric.resources; - - -import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; -import net.leawind.mc.thirdperson.ThirdPersonConstants; -import net.leawind.mc.thirdperson.resources.ItemPatternManager; -import net.minecraft.resources.ResourceLocation; - -public class IdentifiableItemPatternManager extends ItemPatternManager implements IdentifiableResourceReloadListener { - @Override - public ResourceLocation getFabricId () { - return new ResourceLocation(String.format("%s:reload_%s", ThirdPersonConstants.MOD_ID, ItemPatternManager.ID)); - } -} diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 5e7838e6..479b41ed 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "id": "${mod_id}", + "id": "leawind_third_person", "version": "${mod_version}", "name": "${mod_name}", "description": "${mod_description}", @@ -10,7 +10,7 @@ "contact": { "homepage": "${mod_url_home}", "issues": "${mod_url_issue}", - "sources": "${mod_url_home}", + "sources": "${mod_url_src}", "email": "leawind@yeah.net" }, "license": "${mod_license}", @@ -18,33 +18,34 @@ "environment": "client", "entrypoints": { "client": [ - "${mod_group_id}.fabric.ThirdPersonFabric" + "com.github.leawind.thirdperson.fabric.ThirdPersonFabric" ], "modmenu": [ - "${mod_group_id}.fabric.ModMenuEntry" + "com.github.leawind.thirdperson.fabric.ModMenuEntry" ] }, "mixins": [ - "${mod_id}.mixins.json", - "${mod_id}-common.mixins.json" + "leawind_third_person.mixins.json", + "leawind_third_person-common.mixins.json" ], "depends": { "fabric": "*", - "minecraft": ">=${minecraft_version}", - "architectury": ">=${architectury_version}" + "minecraft": "<=${minecraft_version}", + "architectury": "*" }, "recommends": { - "cloth-config2": ">=${cloth_config_api_version}", - "modmenu": ">=${modmenu_version}" + "cloth-config2": "*", + "modmenu": "*" }, "custom": { "modmenu": { "links": { - "BuyMeACoffee": "https://www.buymeacoffee.com/leawind", "Modrinth": "https://modrinth.com/mod/leawind-third-person", "CurseForge": "https://www.curseforge.com/minecraft/mc-mods/leawind-third-person", + "BuyMeACoffee": "https://www.buymeacoffee.com/leawind", + "Donate": "https://leawind.github.io/Third-Person/en-US/donate?autolang", "MCMOD": "https://www.mcmod.cn/class/12699.html", - "Afdian": "https://afdian.net/a/Leawind" + "Afdian": "https://afdian.com/a/Leawind" } }, "modmenu:clientsideOnly": true diff --git a/fabric/src/main/resources/leawind_third_person.mixins.json b/fabric/src/main/resources/leawind_third_person.mixins.json index 105c6a23..9dfa3e6b 100644 --- a/fabric/src/main/resources/leawind_third_person.mixins.json +++ b/fabric/src/main/resources/leawind_third_person.mixins.json @@ -1,9 +1,11 @@ { "required": true, - "package": "net.leawind.mc.thirdperson.fabric.mixin", + "package": "com.github.leawind.thirdperson.fabric.mixin", "compatibilityLevel": "JAVA_17", "minVersion": "0.8", - "client": [], + "client": [ + "CameraMixin" + ], "mixins": [], "injectors": { "defaultRequire": 1 diff --git a/fabric/src/main/resources/pack.mcmeta b/fabric/src/main/resources/pack.mcmeta deleted file mode 100644 index d860360e..00000000 --- a/fabric/src/main/resources/pack.mcmeta +++ /dev/null @@ -1,6 +0,0 @@ -{ - "pack": { - "description": "${mod_name} Resource Pack", - "pack_format": ${resource_pack_format} - } -} diff --git a/forge/build.gradle b/forge/build.gradle index 6d792c50..2946cd82 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -1,8 +1,6 @@ plugins { id "com.github.johnrengelman.shadow" version "7.1.2" - id "com.modrinth.minotaur" version "2.+" } - architectury { platformSetupLoomIde() forge() @@ -24,13 +22,27 @@ configurations { developmentForge.extendsFrom common } dependencies { + minecraft "com.mojang:minecraft:${minecraft_version}" + mappings loom.officialMojangMappings() forge "net.minecraftforge:forge:${forge_version}" - common(project(path: ":common", configuration: "namedElements")) { transitive false } - shadowCommon(project(path: ":common", configuration: "transformProductionForge")) { transitive = false } + common(project(path: ":common", configuration: "namedElements")) { + transitive false + } + shadowCommon(project(path: ":common", configuration: "transformProductionForge")) { + transitive false + } + + include modImplementation("org.joml:joml:${joml_version}") + // Architectury-api modApi "dev.architectury:architectury-forge:${architectury_version}" + // MixinExtras + compileOnly annotationProcessor("io.github.llamalad7:mixinextras-common:${forge_mixin_extras_version}") + include implementation("io.github.llamalad7:mixinextras-forge:${forge_mixin_extras_version}") // Cloth Config API modApi "me.shedaniel.cloth:cloth-config-forge:${cloth_config_api_version}" + // YACL + // modImplementation "dev.isxander.yacl:yet-another-config-lib-forge:${yacl_mc_version}" } shadowJar { exclude "fabric.mod.json" @@ -39,18 +51,18 @@ shadowJar { archiveClassifier = "dev-shadow" } remapJar { - input.set shadowJar.archiveFile + inputFile.set shadowJar.archiveFile dependsOn shadowJar } sourcesJar { - def commonSources = project(":common").sourcesJar + Task commonSources = project(":common").sourcesJar dependsOn commonSources from commonSources.archiveFile.map { zipTree(it) } } base { - archivesName = rootProject.archiveFileNameOfPlatform("forge") + archivesName = rootProject.archiveFileNameOfLoader("forge") version = "" } components.java { @@ -59,33 +71,6 @@ components.java { } } publishing { - publications { - mavenForge(MavenPublication) { - artifactId = mod_id + "-" + project.name - from components.java - } - } - - repositories { - } -} - -// https://github.com/modrinth/minotaur/blob/a151c425fb128cd5384242f25b6fbb0a1d18e325/README.md -modrinth { - debugMode = Boolean.parseBoolean(publish_debug_mode) - - token = System.getenv("MODRINTH_TOKEN") - - projectId = modrinth_project_id - versionNumber = mod_version - versionType = mod_version_type - versionName = "${mod_version}-mc${minecraft_version}-forge" - changelog = readChangeLog() - - uploadFile = remapJar - autoAddDependsOn = false - dependencies { - required.project "architectury-api" - optional.project "cloth-config" - } + publications {} + repositories {} } diff --git a/forge/src/main/java/com/github/leawind/thirdperson/forge/ThirdPersonEventsForge.java b/forge/src/main/java/com/github/leawind/thirdperson/forge/ThirdPersonEventsForge.java new file mode 100644 index 00000000..ad16725f --- /dev/null +++ b/forge/src/main/java/com/github/leawind/thirdperson/forge/ThirdPersonEventsForge.java @@ -0,0 +1,29 @@ +package com.github.leawind.thirdperson.forge; + +import com.github.leawind.thirdperson.api.base.GameEvents; +import com.github.leawind.thirdperson.api.client.event.ThirdPersonCameraSetupEvent; +import com.github.leawind.thirdperson.mixin.CameraInvoker; +import net.minecraftforge.client.event.ViewportEvent; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +public class ThirdPersonEventsForge { + /** 低优先级意味着在其他模组之后执行,可以覆盖其他模组对相机位置、朝向所做的修改。 */ + @SuppressWarnings("unused") + @SubscribeEvent(priority = EventPriority.LOW) + public static void cameraSetupEvent(ViewportEvent.ComputeCameraAngles event) { + if (GameEvents.thirdPersonCameraSetup != null) { + var camera = event.getCamera(); + var evt = new ThirdPersonCameraSetupEvent((float) event.getPartialTick()); + GameEvents.thirdPersonCameraSetup.accept(evt); + if (evt.set()) { + ((CameraInvoker) camera).invokeSetPosition(evt.pos); + event.setYaw(evt.yRot); + event.setPitch(evt.xRot); + // Forge does not update rotation, forwards, up, left + // So need to invoke vanilla `setRotation` here + ((CameraInvoker) camera).invokeSetRotation(evt.yRot, evt.xRot); + } + } + } +} diff --git a/forge/src/main/java/com/github/leawind/thirdperson/forge/ThirdPersonForge.java b/forge/src/main/java/com/github/leawind/thirdperson/forge/ThirdPersonForge.java new file mode 100644 index 00000000..49c495fe --- /dev/null +++ b/forge/src/main/java/com/github/leawind/thirdperson/forge/ThirdPersonForge.java @@ -0,0 +1,27 @@ +package com.github.leawind.thirdperson.forge; + +import com.github.leawind.thirdperson.ThirdPerson; +import com.github.leawind.thirdperson.ThirdPersonConstants; +import dev.architectury.platform.forge.EventBuses; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; + +@SuppressWarnings("unused") +@Mod(ThirdPersonConstants.MOD_ID) +public final class ThirdPersonForge { + public ThirdPersonForge() { + // 仅在客户端运行 + DistExecutor.unsafeRunWhenOn( + Dist.CLIENT, + () -> + () -> { + EventBuses.registerModEventBus( + ThirdPersonConstants.MOD_ID, FMLJavaModLoadingContext.get().getModEventBus()); + ThirdPerson.init(); + MinecraftForge.EVENT_BUS.register(ThirdPersonEventsForge.class); + }); + } +} diff --git a/forge/src/main/java/net/leawind/mc/thirdperson/forge/ExpectPlatformExampleImpl.java b/forge/src/main/java/net/leawind/mc/thirdperson/forge/ExpectPlatformExampleImpl.java deleted file mode 100644 index d021f5a5..00000000 --- a/forge/src/main/java/net/leawind/mc/thirdperson/forge/ExpectPlatformExampleImpl.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.leawind.mc.thirdperson.forge; - - -public class ExpectPlatformExampleImpl { - public static String getMessage () { - return "This is Forge"; - } -} diff --git a/forge/src/main/java/net/leawind/mc/thirdperson/forge/ThirdPersonForge.java b/forge/src/main/java/net/leawind/mc/thirdperson/forge/ThirdPersonForge.java deleted file mode 100644 index 8f6900db..00000000 --- a/forge/src/main/java/net/leawind/mc/thirdperson/forge/ThirdPersonForge.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.leawind.mc.thirdperson.forge; - - -import dev.architectury.platform.forge.EventBuses; -import net.leawind.mc.thirdperson.ThirdPerson; -import net.leawind.mc.thirdperson.ThirdPersonConstants; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.client.ConfigScreenHandler; -import net.minecraftforge.fml.DistExecutor; -import net.minecraftforge.fml.ModLoadingContext; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; - -@Mod(ThirdPersonConstants.MOD_ID) -public final class ThirdPersonForge { - public ThirdPersonForge () { - // 仅在客户端运行 - DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { - EventBuses.registerModEventBus(ThirdPersonConstants.MOD_ID, FMLJavaModLoadingContext.get().getModEventBus()); - ThirdPerson.init(); - // 配置屏幕 - if (ThirdPerson.CONFIG_MANAGER.isScreenAvailable()) { - ModLoadingContext.get().registerExtensionPoint(ConfigScreenHandler.ConfigScreenFactory.class, () -> new ConfigScreenHandler.ConfigScreenFactory((mc, screen) -> ThirdPerson.CONFIG_MANAGER.getConfigScreen(screen))); - } - }); - } -} diff --git a/forge/src/main/java/net/leawind/mc/thirdperson/forge/ThirdPersonResourcesImpl.java b/forge/src/main/java/net/leawind/mc/thirdperson/forge/ThirdPersonResourcesImpl.java deleted file mode 100644 index 0eb5bfc3..00000000 --- a/forge/src/main/java/net/leawind/mc/thirdperson/forge/ThirdPersonResourcesImpl.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.leawind.mc.thirdperson.forge; - - -import net.leawind.mc.thirdperson.ThirdPersonResources; -import net.leawind.mc.thirdperson.resources.ItemPatternManager; -import net.minecraft.client.Minecraft; -import net.minecraft.server.packs.resources.ReloadableResourceManager; -import net.minecraft.server.packs.resources.ResourceManager; - -/** - * @see ThirdPersonResources - */ -@SuppressWarnings("unused") -public class ThirdPersonResourcesImpl { - public static void register () { - ResourceManager resourceManager = Minecraft.getInstance().getResourceManager(); - if (resourceManager instanceof ReloadableResourceManager reloadableResourceManager) { - ThirdPersonResources.itemPatternManager = new ItemPatternManager(); - reloadableResourceManager.registerReloadListener(ThirdPersonResources.itemPatternManager); - } - } -} diff --git a/forge/src/main/resources/META-INF/mods.toml b/forge/src/main/resources/META-INF/mods.toml index dabcb56e..9361754c 100644 --- a/forge/src/main/resources/META-INF/mods.toml +++ b/forge/src/main/resources/META-INF/mods.toml @@ -1,11 +1,11 @@ modLoader = "javafml" -loaderVersion = "[43,)" +loaderVersion = "*" license = "${mod_license}" -showAsResourcePack = true +showAsResourcePack = false issueTrackerURL = "${mod_url_issue}" [[mods]] -modId = "${mod_id}" +modId = "leawind_third_person" version = "${mod_version}" displayName = "${mod_name}" description = "${mod_description}" @@ -13,34 +13,34 @@ authors = "${mod_authors}" logoFile = "TODO" displayURL = "${mod_url_home}" -[[dependencies."${mod_id}"]] +[[dependencies.leawind_third_person]] modId = "forge" mandatory = true -versionRange = "[43,)" +versionRange = "*" ordering = "NONE" side = "CLIENT" referraUrl = "https://files.minecraftforge.net/" -[[dependencies."${mod_id}"]] +[[dependencies.leawind_third_person]] modId = "minecraft" mandatory = true -versionRange = "[${minecraft_version},)" +versionRange = "(,${minecraft_version}]" ordering = "NONE" side = "CLIENT" referraUrl = "https://www.minecraft.net/" -[[dependencies."${mod_id}"]] +[[dependencies.leawind_third_person]] modId = "architectury" mandatory = true -versionRange = "[${architectury_version},)" +versionRange = "*" ordering = "AFTER" side = "CLIENT" referraUrl = "https://modrinth.com/mod/architectury-api" -[[dependencies."${mod_id}"]] +[[dependencies.leawind_third_person]] modId = "cloth_config" mandatory = false -versionRange = "[${cloth_config_api_version},)" +versionRange = "*" ordering = "NONE" side = "CLIENT" referraUrl = "https://modrinth.com/mod/cloth-config" diff --git a/forge/src/main/resources/leawind_third_person.mixins.json b/forge/src/main/resources/leawind_third_person.mixins.json index 19567db8..27e6e87a 100644 --- a/forge/src/main/resources/leawind_third_person.mixins.json +++ b/forge/src/main/resources/leawind_third_person.mixins.json @@ -1,6 +1,6 @@ { "required": true, - "package": "net.leawind.mc.thirdperson.forge.mixin", + "package": "com.github.leawind.thirdperson.forge.mixin", "compatibilityLevel": "JAVA_17", "minVersion": "0.8", "client": [], diff --git a/forge/src/main/resources/pack.mcmeta b/forge/src/main/resources/pack.mcmeta index d860360e..b9cdcebf 100644 --- a/forge/src/main/resources/pack.mcmeta +++ b/forge/src/main/resources/pack.mcmeta @@ -1,6 +1,6 @@ { - "pack": { - "description": "${mod_name} Resource Pack", - "pack_format": ${resource_pack_format} - } -} + "pack": { + "description": "${mod_name} Resource Pack", + "pack_format": 10 + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index bca88bc7..cb6716e6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,50 +1,70 @@ -org.gradle.jvmargs = -Xmx8G -org.gradle.parallel = true -org.gradle.daemon = true -java_version = 17 - -mod_id = leawind_third_person -mod_version = 2.0.8-alpha -#mod_version = 2.0.7 -# release | beta | alpha -mod_version_type = release -mod_group_id = net.leawind.mc.thirdperson -mod_name = Leawind's Third Person -mod_description = A practical, smooth, feature-rich third-person mod. -mod_license = The MIT License (MIT) -mod_icon = assets/leawind_third_person/icon.png -mod_authors = Leawind -mod_url_home = https://leawind.github.io/Third-Person/en-US/?autolang -mod_url_issue = https://github.com/LEAWIND/Third-Person/issue -mod_url_src = https://github.com/LEAWIND/Third-Person - -# Common -minecraft_version = 1.19.2 +org.gradle.jvmargs = -Xmx8G +org.gradle.parallel = true +org.gradle.daemon = true +java_version = 17 + +# Mod meta data +mod_id = leawind_third_person +mod_version = 2.2.0 +mod_version_type = release +mod_group_id = com.github.leawind.thirdperson +mod_name = Leawind's Third Person +mod_description = A practical, smooth, feature-rich third-person mod. +mod_license = The MIT License (MIT) +mod_icon = assets/leawind_third_person/icon.png +mod_authors = Leawind +mod_url_home = https://leawind.github.io/Third-Person/en-US/?autolang +mod_url_issue = https://github.com/Leawind/Third-Person/issues +mod_url_src = https://github.com/Leawind/Third-Person + +# Minecraft version +minecraft_version = 1.19.2 +minecraft_version_min = 1.19 +minecraft_version_max = 1.19.2 +#splited by /[^\d.]+/ +minecraft_version_list = 1.19, 1.19.1, 1.19.2, 1.19.3 + +enabled_loaders = fabric,forge + #https://minecraft.wiki/w/Pack_format -resource_pack_format = 9 +resource_pack_format = 10 + #https://modrinth.com/mod/architectury-api/versions -enabled_platforms = fabric,forge -architectury_version = 6.6.92 +architectury_version = 6.6.92 -# Fabric #https://fabricmc.net/develop/ -# latest: 0.15.7/0.77.0, template: 0.14.23/0.76.1 - -fabric_loader_version = 0.15.7 -fabric_api_version = 0.77.0+1.19.2 +fabric_loader_version = 0.16.9 +fabric_api_version = 0.77.0+1.19.2 -# Forge #https://files.minecraftforge.net/net/minecraftforge/forge/ -# latest: 43.3.8, template: 43.3.2 -forge_version = 1.19.2-43.3.8 +forge_version = 1.19.2-43.4.0 # Dependencies + +#https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api +#https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine +junit_jupiter_version = 5.11.3 + +#https://mvnrepository.com/artifact/org.joml/joml +joml_version = 1.10.5 + +#MixinExtras +fabric_mixin_extras_version = 0.4.1 +forge_mixin_extras_version = 0.4.1 + #https://github.com/TerraformersMC/ModMenu/releases #https://modrinth.com/mod/modmenu/versions -modmenu_version = 4.2.0-beta.2 +modmenu_version = 4.1.2 + #https://modrinth.com/mod/cloth-config/versions -cloth_config_api_version = 8.3.115 +cloth_config_api_version = 8.3.134 + +#https://modrinth.com/mod/yacl/versions +#yacl_mc_version = 2.2.0+1.19.4 +#yacl_version = 2.2.0 # Publishing -modrinth_project_id = leawind-third-person -publish_debug_mode = true + +changelog_file = changelog.md +curseforge_project_id = 930880 +modrinth_project_id = S3D3QF0M diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4a9b32c9..74a1c23c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase = GRADLE_USER_HOME distributionPath = wrapper/dists -distributionUrl = https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl = https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout = 10000 zipStoreBase = GRADLE_USER_HOME zipStorePath = wrapper/dists diff --git a/settings.gradle b/settings.gradle index 5e7aabf6..a86bb50c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,7 +9,9 @@ pluginManagement { gradlePluginPortal() } } + +rootProject.name = mod_name + include("common") include("fabric") include("forge") -rootProject.name = mod_name