├── changelog.md ├── logo.xcf ├── logo-black.png ├── logo-transparent.png ├── _notes └── notebook │ ├── Concept.one │ ├── TODO list.one │ └── Notizbuch öffnen.onetoc2 ├── src └── main │ ├── resources │ ├── logo.png │ ├── META-INF │ │ ├── accesstransformer.cfg │ │ ├── MANIFEST.MF │ │ └── mods.toml │ ├── pack.mcmeta │ ├── multihitboxlib.mixins.json │ └── data │ │ ├── multihitboxlib │ │ └── multihitboxlib │ │ │ └── hitbox_profiles │ │ │ └── anjanath.json │ │ └── minecraft │ │ └── multihitboxlib │ │ └── hitbox_profiles │ │ └── creeper.json │ └── java │ └── de │ └── dertoaster │ └── multihitboxlib │ ├── init │ ├── MHLibEntities.java │ ├── MHLibHitboxTypes.java │ ├── MHLibDatapackLoaders.java │ └── MHLibPackets.java │ ├── entity │ ├── hitbox │ │ ├── type │ │ │ ├── implementation │ │ │ │ ├── SphereHitboxType.java │ │ │ │ ├── OrientableBBHitboxType.java │ │ │ │ ├── OrientableSpheroidHitboxType.java │ │ │ │ └── AABBHitboxType.java │ │ │ └── IHitboxType.java │ │ ├── AssetEnforcementConfig.java │ │ ├── MainHitboxConfig.java │ │ ├── SubPartConfig.java │ │ └── HitboxProfile.java │ ├── EntityDimensionsOrientable.java │ ├── IOrientableHitbox.java │ ├── OrientableBox.java │ └── OrientableMHLibPartEntity.java │ ├── api │ ├── IMHLibSizeCallback.java │ ├── ICustomHitboxProfileSupplier.java │ ├── network │ │ ├── IMessageHandler.java │ │ ├── IMessage.java │ │ ├── AbstractPacket.java │ │ ├── AbstractSPacketHandlerCodecWrappingPacket.java │ │ ├── AbstractSPacketSyncDatapackContent.java │ │ ├── AbstractPacketHandler.java │ │ ├── AbstractSPacketHandlerSyncDatapackContent.java │ │ └── AbstractSPacketCodecWrappingPacket.java │ ├── IModifiableMultipartEntity.java │ ├── event │ │ ├── server │ │ │ ├── SynchAssetFinderRegistrationEvent.java │ │ │ └── AssetEnforcementManagerRegistrationEvent.java │ │ ├── AbstractRegistrationEvent.java │ │ └── client │ │ │ └── PartRendererRegistrationEvent.java │ ├── glibplus │ │ ├── IExtendedGeoAnimatableEntity.java │ │ ├── WrappedAnimationController.java │ │ └── IRawAnimation.java │ ├── alibplus │ │ ├── IExtendedGeoAnimatableEntity.java │ │ ├── WrappedAnimationController.java │ │ └── IRawAnimation.java │ ├── IMHLibExtendedRenderLayer.java │ ├── IMHLibFieldAccessor.java │ └── DatapackRegistry.java │ ├── mixin │ ├── accessor │ │ └── AccessorEntityRenderer.java │ ├── minecraft │ │ ├── MixinMinecraft.java │ │ └── client │ │ │ └── MixinEntityRenderDispatcher.java │ ├── entity │ │ ├── MixinServerEntity.java │ │ └── MixinLivingEntity.java │ ├── azurelib │ │ ├── MixinGeoRenderer.java │ │ ├── MixinGeoReplacedEntityRenderer.java │ │ └── MixinGeoEntityRenderer.java │ ├── geckolib │ │ ├── MixinGeoRenderer.java │ │ ├── MixinGeoEntityRenderer.java │ │ └── MixinGeoReplacedEntityRenderer.java │ └── MHLibPlugin.java │ ├── util │ ├── UtilityCodecs.java │ ├── ClientOnlyMethods.java │ ├── LazyLoadField.java │ ├── LazyLoadFieldFunction.java │ ├── BoneInformation.java │ └── CompressionUtil.java │ ├── assetsynch │ ├── data │ │ ├── SynchDataContainer.java │ │ ├── SynchDataManagerData.java │ │ └── SynchEntryData.java │ ├── assetfinders │ │ ├── AbstractAssetFinder.java │ │ └── HitboxProfileAssetFinder.java │ ├── impl │ │ ├── MHLibEnforcementManager.java │ │ ├── AlibAnimationEnforcementManager.java │ │ ├── GlibAnimationEnforcementManager.java │ │ ├── AlibModelEnforcementManager.java │ │ ├── GlibModelEnforcementManager.java │ │ └── TextureEnforcementManager.java │ ├── DiskSaveRunner.java │ ├── pack │ │ └── AssetSynchPackFinder.java │ ├── client │ │ └── TextureClientLogic.java │ └── AbstractNInOneEntriesEnforcementManager.java │ ├── network │ ├── client │ │ ├── assetsync │ │ │ ├── CPacketRequestSynch.java │ │ │ └── SPacketHandlerSynchAssets.java │ │ ├── SPacketHandlerSetMaster.java │ │ ├── SPacketHandlerUpdateMultipart.java │ │ ├── SPacketHandlerFunctionalAnimProgress.java │ │ ├── SPacketHandlerFunctionalAnimProgressAL.java │ │ └── CPacketBoneInformation.java │ └── server │ │ ├── assetsync │ │ ├── CPacketHandlerRequestSynch.java │ │ └── SPacketSynchAssets.java │ │ ├── CPacketHandlerBoneInformation.java │ │ ├── SPacketFunctionalAnimProgress.java │ │ ├── SPacketSetMaster.java │ │ └── SPacketUpdateMultipart.java │ ├── GameEventHandler.java │ ├── example │ ├── CommonListener.java │ ├── entity │ │ ├── Anjanath.java │ │ └── AnjanathALib.java │ └── init │ │ └── MHLibExampleEntities.java │ ├── EntityEventHandler.java │ ├── Constants.java │ ├── ModEventHandler.java │ ├── client │ ├── azurelib │ │ ├── AzurelibEntityRenderEventHandler.java │ │ └── renderlayer │ │ │ └── AzurelibBoneInformationCollectorLayer.java │ ├── geckolib │ │ ├── GeckolibEntityRenderEventHandler.java │ │ └── renderlayer │ │ │ └── GeckolibBoneInformationCollectorLayer.java │ ├── IBoneInformationCollectorLayerCommonLogic.java │ ├── EntityRenderEventHandlerCommonLogic.java │ └── MHLibClient.java │ └── MHLibMod.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitattributes ├── .gitignore ├── settings.gradle ├── gradle.properties ├── gradlew.bat └── README.md /changelog.md: -------------------------------------------------------------------------------- 1 | v1.5.0 2 | 3 | - Updated against latest AzureLib and Geckolib. -------------------------------------------------------------------------------- /logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerToaster98/MultiHitBoxLib/HEAD/logo.xcf -------------------------------------------------------------------------------- /logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerToaster98/MultiHitBoxLib/HEAD/logo-black.png -------------------------------------------------------------------------------- /logo-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerToaster98/MultiHitBoxLib/HEAD/logo-transparent.png -------------------------------------------------------------------------------- /_notes/notebook/Concept.one: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerToaster98/MultiHitBoxLib/HEAD/_notes/notebook/Concept.one -------------------------------------------------------------------------------- /src/main/resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerToaster98/MultiHitBoxLib/HEAD/src/main/resources/logo.png -------------------------------------------------------------------------------- /_notes/notebook/TODO list.one: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerToaster98/MultiHitBoxLib/HEAD/_notes/notebook/TODO list.one -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerToaster98/MultiHitBoxLib/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /_notes/notebook/Notizbuch öffnen.onetoc2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerToaster98/MultiHitBoxLib/HEAD/_notes/notebook/Notizbuch öffnen.onetoc2 -------------------------------------------------------------------------------- /src/main/resources/META-INF/accesstransformer.cfg: -------------------------------------------------------------------------------- 1 | # This uses the searge names!! 2 | public net.minecraft.world.entity.Entity f_19843_ # ENTITY_COUNTER -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/init/MHLibEntities.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.init; 2 | 3 | public class MHLibEntities { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": { 4 | "text": "${mod_name} Resources" 5 | }, 6 | "forge:server_data_pack_format": 12, 7 | "pack_format": 13 8 | } 9 | } -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/hitbox/type/implementation/SphereHitboxType.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity.hitbox.type.implementation; 2 | 3 | public class SphereHitboxType { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Disable autocrlf on generated files, they always generate with LF 2 | # Add any extra files or paths here to make git stop saying they 3 | # are changed when only line endings change. 4 | src/generated/**/.cache/cache text eol=lf 5 | src/generated/**/*.json text eol=lf 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .settings 5 | .metadata 6 | .classpath 7 | .project 8 | 9 | # idea 10 | out 11 | *.ipr 12 | *.iws 13 | *.iml 14 | .idea 15 | 16 | # gradle 17 | build 18 | .gradle 19 | 20 | # other 21 | eclipse 22 | run 23 | 24 | # Files from Forge MDK 25 | forge*changelog.txt 26 | .factorypath 27 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/IMHLibSizeCallback.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api; 2 | 3 | import net.minecraft.world.entity.AgeableMob; 4 | import net.minecraft.world.entity.Entity; 5 | 6 | public interface IMHLibSizeCallback { 7 | 8 | double mhlibGetEntitySizeScale(T entity); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/ICustomHitboxProfileSupplier.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api; 2 | 3 | import java.util.Optional; 4 | 5 | import de.dertoaster.multihitboxlib.entity.hitbox.HitboxProfile; 6 | 7 | public interface ICustomHitboxProfileSupplier { 8 | 9 | public Optional getHitboxProfile(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/network/IMessageHandler.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.network; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import net.minecraftforge.network.NetworkEvent; 6 | 7 | public interface IMessageHandler { 8 | 9 | public void handlePacket(T packet, Supplier context); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/network/IMessage.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.network; 2 | 3 | import net.minecraft.network.FriendlyByteBuf; 4 | 5 | public interface IMessage { 6 | 7 | public Class getPacketClass(); 8 | 9 | public S fromBytes(FriendlyByteBuf buffer); 10 | public void toBytes(S packet, FriendlyByteBuf buffer); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/IModifiableMultipartEntity.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api; 2 | 3 | import net.minecraft.world.entity.Entity; 4 | 5 | @Deprecated(forRemoval = true) 6 | /* 7 | Implement the IMultipartEntity interface directly and override what you need! 8 | */ 9 | public interface IModifiableMultipartEntity extends IMultipartEntity { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/EntityDimensionsOrientable.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity; 2 | 3 | import net.minecraft.world.entity.EntityDimensions; 4 | 5 | public class EntityDimensionsOrientable extends EntityDimensions { 6 | 7 | public EntityDimensionsOrientable(float pWidth, float pHeight, boolean pFixed) { 8 | super(pWidth, pHeight, pFixed); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/IOrientableHitbox.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity; 2 | 3 | import net.minecraft.world.phys.Vec3; 4 | 5 | public interface IOrientableHitbox { 6 | 7 | public float getRotationX(); 8 | public float getRotationY(); 9 | public float getRotationZ(); 10 | 11 | // Return the radii fo the box here! 12 | public Vec3 getCenterOffset(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Specification-Title: Multihitbox Library 3 | Specification-Vendor: DerToaster, Meme Man 4 | Specification-Version: 1.7.0 5 | Implementation-Title: Multihitbox Library 6 | Implementation-Version: 1.7.0 7 | Implementation-Vendor: DerToaster, Meme Man 8 | Implementation-Timestamp: 2024-09-28T11:30:36+0200 9 | MixinConfigs: multihitboxlib.mixins.json 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/hitbox/type/implementation/OrientableBBHitboxType.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity.hitbox.type.implementation; 2 | 3 | public class OrientableBBHitboxType { 4 | 5 | // Same as normal cube 6 | // Subtract center loc from rotation 7 | // Rotate loc by rotation of cube 8 | // Check if loc is inside the constraints of the cube by simple checking for coords 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/network/AbstractPacket.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.network; 2 | 3 | import net.minecraft.network.FriendlyByteBuf; 4 | 5 | public abstract class AbstractPacket implements IMessage { 6 | 7 | public IMessage cast() { 8 | return (IMessage)this; 9 | } 10 | 11 | @Override 12 | public abstract T fromBytes(FriendlyByteBuf buffer); 13 | 14 | @Override 15 | public abstract void toBytes(T packet, FriendlyByteBuf buffer); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/mixin/accessor/AccessorEntityRenderer.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.mixin.accessor; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | import org.spongepowered.asm.mixin.gen.Accessor; 5 | 6 | import net.minecraft.client.renderer.entity.EntityRenderDispatcher; 7 | import net.minecraft.client.renderer.entity.EntityRenderer; 8 | 9 | @Mixin(EntityRenderer.class) 10 | public interface AccessorEntityRenderer { 11 | 12 | @Accessor("entityRenderDispatcher") 13 | public EntityRenderDispatcher getEntityRenderDispatcher(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | 5 | maven { 6 | name = 'MinecraftForge' 7 | url = 'https://maven.minecraftforge.net/' 8 | } 9 | maven { 10 | name = 'Parchment MC' 11 | url = 'https://maven.parchmentmc.org' 12 | } 13 | maven { 14 | name = 'Spongepowered Mixin' 15 | url = 'https://repo.spongepowered.org/maven' 16 | } 17 | } 18 | } 19 | 20 | plugins { 21 | id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' 22 | } -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/hitbox/type/implementation/OrientableSpheroidHitboxType.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity.hitbox.type.implementation; 2 | 3 | public class OrientableSpheroidHitboxType { 4 | 5 | // Point is within sphere if... 6 | // - distance to center is <= max-rad of sphere 7 | // - if distance is <= min-rad of sphere => definitely inside 8 | // - Otherwise: subtract center loc from point and rotate point by the rotation of the spheroid around the center 9 | // If (X^2 / a^2) + (Y^2 / b^2) + (Z^2 / c^2) <= 1, then the point is on or in the sphere 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/OrientableBox.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity; 2 | 3 | import net.minecraft.world.phys.AABB; 4 | 5 | public class OrientableBox extends AABB { 6 | 7 | protected final double rotX; 8 | protected final double rotY; 9 | protected final double rotZ; 10 | 11 | public OrientableBox(double pX1, double pY1, double pZ1, double pX2, double pY2, double pZ2, double rotX, double rotY, double rotZ) { 12 | super(pX1, pY1, pZ1, pX2, pY2, pZ2); 13 | this.rotX = rotX; 14 | this.rotY = rotY; 15 | this.rotZ = rotZ; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/util/UtilityCodecs.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.util; 2 | 3 | import java.util.List; 4 | 5 | import com.mojang.serialization.Codec; 6 | 7 | import net.minecraft.Util; 8 | import net.minecraft.world.phys.Vec2; 9 | 10 | public class UtilityCodecs { 11 | 12 | public static final Codec VEC2_CODEC = Codec.FLOAT.listOf().comapFlatMap((instance) -> { 13 | return Util.fixedSize(instance, 2).map((p_231081_) -> { 14 | return new Vec2(p_231081_.get(0), p_231081_.get(1)); 15 | }); 16 | }, (instance) -> { 17 | return List.of(instance.x, instance.y); 18 | }); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/data/SynchDataContainer.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.data; 2 | 3 | import java.util.List; 4 | 5 | import com.mojang.serialization.Codec; 6 | import com.mojang.serialization.codecs.RecordCodecBuilder; 7 | 8 | public record SynchDataContainer( 9 | List payload 10 | ) { 11 | 12 | public static final Codec CODEC = RecordCodecBuilder.create(instance -> { 13 | return instance.group( 14 | SynchDataManagerData.CODEC.listOf().fieldOf("payload").forGetter(SynchDataContainer::payload) 15 | ).apply(instance, SynchDataContainer::new); 16 | }); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/hitbox/type/IHitboxType.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity.hitbox.type; 2 | 3 | import com.mojang.serialization.Codec; 4 | 5 | import de.dertoaster.multihitboxlib.entity.MHLibPartEntity; 6 | import de.dertoaster.multihitboxlib.entity.hitbox.SubPartConfig; 7 | import net.minecraft.world.entity.Entity; 8 | import net.minecraft.world.phys.Vec3; 9 | 10 | public interface IHitboxType { 11 | 12 | public default Vec3 getBaseRotation() { 13 | return Vec3.ZERO; 14 | } 15 | 16 | public Codec getType(); 17 | 18 | public MHLibPartEntity createPartEntity(final SubPartConfig config, final T parent, final int partNumber); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/multihitboxlib.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "de.dertoaster.multihitboxlib.mixin", 4 | "compatibilityLevel": "JAVA_17", 5 | "plugin": "de.dertoaster.multihitboxlib.mixin.MHLibPlugin", 6 | "refmap": "multihitboxlib.refmap.json", 7 | "mixins": [ 8 | "entity.MixinLivingEntity", 9 | "entity.MixinServerEntity", 10 | "geckolib.MixinGeoEntityRenderer", 11 | "geckolib.MixinGeoReplacedEntityRenderer", 12 | "geckolib.MixinGeoRenderer", 13 | "azurelib.MixinGeoEntityRenderer", 14 | "azurelib.MixinGeoReplacedEntityRenderer", 15 | "azurelib.MixinGeoRenderer" 16 | ], 17 | "client": [ 18 | "accessor.AccessorEntityRenderer", 19 | "minecraft.MixinMinecraft" 20 | ], 21 | "minVersion": "0.8" 22 | } -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/event/server/SynchAssetFinderRegistrationEvent.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.event.server; 2 | 3 | import java.util.Map; 4 | 5 | import de.dertoaster.multihitboxlib.api.event.AbstractRegistrationEvent; 6 | import de.dertoaster.multihitboxlib.assetsynch.assetfinders.AbstractAssetFinder; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraftforge.fml.event.IModBusEvent; 9 | 10 | public class SynchAssetFinderRegistrationEvent extends AbstractRegistrationEvent implements IModBusEvent { 11 | 12 | public SynchAssetFinderRegistrationEvent(Map map) { 13 | super(map); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/client/assetsync/CPacketRequestSynch.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.client.assetsync; 2 | 3 | import de.dertoaster.multihitboxlib.api.network.AbstractPacket; 4 | import net.minecraft.network.FriendlyByteBuf; 5 | 6 | public class CPacketRequestSynch extends AbstractPacket { 7 | 8 | @Override 9 | public Class getPacketClass() { 10 | return CPacketRequestSynch.class; 11 | } 12 | 13 | @Override 14 | public CPacketRequestSynch fromBytes(FriendlyByteBuf buffer) { 15 | return new CPacketRequestSynch(); 16 | } 17 | 18 | @Override 19 | public void toBytes(CPacketRequestSynch packet, FriendlyByteBuf buffer) { 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/event/server/AssetEnforcementManagerRegistrationEvent.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.event.server; 2 | 3 | import java.util.Map; 4 | 5 | import de.dertoaster.multihitboxlib.api.event.AbstractRegistrationEvent; 6 | import de.dertoaster.multihitboxlib.assetsynch.AbstractAssetEnforcementManager; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraftforge.fml.event.IModBusEvent; 9 | 10 | public class AssetEnforcementManagerRegistrationEvent extends AbstractRegistrationEvent implements IModBusEvent { 11 | 12 | public AssetEnforcementManagerRegistrationEvent(Map map) { 13 | super(map); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/assetfinders/AbstractAssetFinder.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.assetfinders; 2 | 3 | import java.util.Set; 4 | import java.util.function.Function; 5 | 6 | import net.minecraft.core.RegistryAccess; 7 | import net.minecraft.resources.ResourceLocation; 8 | 9 | public abstract class AbstractAssetFinder implements Function> { 10 | 11 | private ResourceLocation id; 12 | 13 | public ResourceLocation getId() { 14 | return this.id; 15 | } 16 | 17 | void setId(final ResourceLocation id) { 18 | if (this.id == null) { 19 | this.id = id; 20 | } 21 | } 22 | 23 | public Set get(RegistryAccess registryAccess) { 24 | return apply(registryAccess); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/event/AbstractRegistrationEvent.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.event; 2 | 3 | import java.util.Map; 4 | 5 | import net.minecraftforge.eventbus.api.Event; 6 | 7 | public class AbstractRegistrationEvent extends Event { 8 | 9 | private final Map REGISTRATION_MAP; 10 | 11 | public AbstractRegistrationEvent(Map map) { 12 | this.REGISTRATION_MAP = map; 13 | } 14 | 15 | public boolean tryAdd(K id, final V value) { 16 | if (id == null || value == null) { 17 | return false; 18 | } 19 | if (REGISTRATION_MAP.containsKey(id)) { 20 | return false; 21 | } 22 | REGISTRATION_MAP.put(id, value); 23 | return true; 24 | } 25 | 26 | @Override 27 | public boolean isCancelable() { 28 | return false; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/GameEventHandler.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib; 2 | 3 | import de.dertoaster.multihitboxlib.assetsynch.AssetEnforcement; 4 | import net.minecraft.server.level.ServerPlayer; 5 | import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; 6 | import net.minecraftforge.eventbus.api.SubscribeEvent; 7 | import net.minecraftforge.fml.common.Mod.EventBusSubscriber; 8 | import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus; 9 | 10 | @EventBusSubscriber(modid = Constants.MODID, bus = Bus.FORGE) 11 | public class GameEventHandler { 12 | 13 | @SubscribeEvent 14 | public static void onPlayerJoinServer(PlayerLoggedInEvent event) { 15 | if (event.getEntity() instanceof ServerPlayer sp && sp != null) { 16 | AssetEnforcement.sendSynchData(sp); 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/data/SynchDataManagerData.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.data; 2 | 3 | import java.util.List; 4 | 5 | import com.mojang.serialization.Codec; 6 | import com.mojang.serialization.codecs.RecordCodecBuilder; 7 | 8 | import net.minecraft.resources.ResourceLocation; 9 | 10 | public record SynchDataManagerData( 11 | ResourceLocation manager, 12 | List payload 13 | ) { 14 | 15 | public static final Codec CODEC = RecordCodecBuilder.create(instance -> { 16 | return instance.group( 17 | ResourceLocation.CODEC.fieldOf("manager").forGetter(SynchDataManagerData::manager), 18 | SynchEntryData.CODEC.listOf().fieldOf("payload").forGetter(SynchDataManagerData::payload) 19 | ).apply(instance, SynchDataManagerData::new); 20 | }); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/OrientableMHLibPartEntity.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity; 2 | 3 | import de.dertoaster.multihitboxlib.entity.hitbox.SubPartConfig; 4 | import net.minecraft.world.entity.Entity; 5 | import net.minecraft.world.entity.EntityDimensions; 6 | import net.minecraft.world.phys.AABB; 7 | import net.minecraft.world.phys.Vec3; 8 | 9 | public class OrientableMHLibPartEntity extends MHLibPartEntity { 10 | public OrientableMHLibPartEntity(T parent, SubPartConfig properties, EntityDimensions baseSize, Vec3 basePosition, Vec3 pivot) { 11 | super(parent, properties, baseSize, basePosition, pivot); 12 | } 13 | 14 | @Override 15 | protected AABB makeBoundingBox() { 16 | // TODO: Create orientable BB 17 | return super.makeBoundingBox(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/server/assetsync/CPacketHandlerRequestSynch.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.server.assetsync; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import de.dertoaster.multihitboxlib.api.network.AbstractPacketHandler; 6 | import de.dertoaster.multihitboxlib.assetsynch.AssetEnforcement; 7 | import de.dertoaster.multihitboxlib.network.client.assetsync.CPacketRequestSynch; 8 | import net.minecraft.server.level.ServerPlayer; 9 | import net.minecraft.world.entity.player.Player; 10 | import net.minecraft.world.level.Level; 11 | import net.minecraftforge.network.NetworkEvent.Context; 12 | 13 | public class CPacketHandlerRequestSynch extends AbstractPacketHandler { 14 | 15 | @Override 16 | protected void execHandlePacket(CPacketRequestSynch packet, Supplier context, Level world, Player player) { 17 | if (player instanceof ServerPlayer sp) { 18 | AssetEnforcement.sendSynchData(sp); 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/event/client/PartRendererRegistrationEvent.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.event.client; 2 | 3 | import java.util.Map; 4 | import java.util.function.Function; 5 | 6 | import de.dertoaster.multihitboxlib.api.event.AbstractRegistrationEvent; 7 | import de.dertoaster.multihitboxlib.entity.MHLibPartEntity; 8 | import net.minecraft.client.renderer.entity.EntityRenderDispatcher; 9 | import net.minecraft.client.renderer.entity.EntityRenderer; 10 | import net.minecraftforge.fml.event.IModBusEvent; 11 | 12 | public class PartRendererRegistrationEvent extends AbstractRegistrationEvent>, Function>>> implements IModBusEvent { 13 | 14 | public PartRendererRegistrationEvent(Map>, Function>>> map) { 15 | super(map); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/mixin/minecraft/MixinMinecraft.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.mixin.minecraft; 2 | 3 | import org.apache.commons.lang3.ArrayUtils; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.injection.At; 6 | import org.spongepowered.asm.mixin.injection.ModifyArg; 7 | 8 | import de.dertoaster.multihitboxlib.assetsynch.pack.AssetSynchPackFinder; 9 | import net.minecraft.client.Minecraft; 10 | import net.minecraft.server.packs.repository.RepositorySource; 11 | 12 | @Mixin(Minecraft.class) 13 | public class MixinMinecraft { 14 | 15 | @ModifyArg( 16 | method = "", 17 | at = @At(value = "INVOKE", target = "Lnet/minecraft/server/packs/repository/PackRepository;([Lnet/minecraft/server/packs/repository/RepositorySource;)V"), 18 | index = 0 19 | ) 20 | private RepositorySource[] mhlibAddPackfinder(RepositorySource[] arg) { 21 | return ArrayUtils.addAll(arg, AssetSynchPackFinder.instance()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/hitbox/AssetEnforcementConfig.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity.hitbox; 2 | 3 | import java.util.List; 4 | 5 | import com.mojang.serialization.Codec; 6 | import com.mojang.serialization.codecs.RecordCodecBuilder; 7 | 8 | import net.minecraft.resources.ResourceLocation; 9 | 10 | public record AssetEnforcementConfig( 11 | List models, 12 | List animations, 13 | List textures 14 | ) { 15 | 16 | public static final Codec CODEC = RecordCodecBuilder.create(instance -> { 17 | return instance.group( 18 | ResourceLocation.CODEC.listOf().fieldOf("models").forGetter(AssetEnforcementConfig::models), 19 | ResourceLocation.CODEC.listOf().fieldOf("animations").forGetter(AssetEnforcementConfig::animations), 20 | ResourceLocation.CODEC.listOf().fieldOf("textures").forGetter(AssetEnforcementConfig::textures) 21 | ).apply(instance, AssetEnforcementConfig::new); 22 | }); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/client/assetsync/SPacketHandlerSynchAssets.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.client.assetsync; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import de.dertoaster.multihitboxlib.api.network.AbstractSPacketHandlerCodecWrappingPacket; 6 | import de.dertoaster.multihitboxlib.assetsynch.AssetEnforcement; 7 | import de.dertoaster.multihitboxlib.assetsynch.data.SynchDataContainer; 8 | import de.dertoaster.multihitboxlib.network.server.assetsync.SPacketSynchAssets; 9 | import net.minecraft.world.entity.player.Player; 10 | import net.minecraft.world.level.Level; 11 | import net.minecraftforge.network.NetworkEvent.Context; 12 | 13 | public class SPacketHandlerSynchAssets extends AbstractSPacketHandlerCodecWrappingPacket{ 14 | 15 | @Override 16 | protected void execHandlePacket(SPacketSynchAssets packet, Supplier context, Level world, Player sender) { 17 | AssetEnforcement.handlePacketData(packet.getData()); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/util/ClientOnlyMethods.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.util; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.client.server.IntegratedServer; 5 | import net.minecraft.world.entity.player.Player; 6 | import net.minecraft.world.level.Level; 7 | import net.minecraftforge.api.distmarker.Dist; 8 | import net.minecraftforge.api.distmarker.OnlyIn; 9 | 10 | public class ClientOnlyMethods { 11 | 12 | @OnlyIn(Dist.CLIENT) 13 | public static Player getClientPlayer() { 14 | return Minecraft.getInstance().player; 15 | } 16 | 17 | @OnlyIn(Dist.CLIENT) 18 | public static Level getWorld() { 19 | return Minecraft.getInstance().level; 20 | } 21 | 22 | @OnlyIn(Dist.CLIENT) 23 | public static boolean isCurrentPlayerOwnerIfIntegratedServer() { 24 | Player p = getClientPlayer(); 25 | IntegratedServer integratedServer = Minecraft.getInstance().getSingleplayerServer(); 26 | return p != null && integratedServer != null && integratedServer.isSingleplayerOwner(p.getGameProfile()); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/util/LazyLoadField.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.util; 2 | 3 | import com.google.common.base.Supplier; 4 | 5 | public class LazyLoadField implements Supplier { 6 | 7 | private T value = null; 8 | private int lifetime = -1; 9 | private long lastSet = System.currentTimeMillis(); 10 | private final Supplier function; 11 | 12 | public LazyLoadField(final Supplier function) { 13 | this.function = function; 14 | } 15 | 16 | public LazyLoadField(final Supplier function, final int lifetime) { 17 | this(function); 18 | this.lifetime = lifetime; 19 | } 20 | 21 | public void reset() { 22 | this.value = null; 23 | } 24 | 25 | @Override 26 | public T get() { 27 | if (this.lifetime > 0) { 28 | if (System.currentTimeMillis() - this.lastSet >= this.lifetime) { 29 | this.lastSet = System.currentTimeMillis(); 30 | this.value = this.function.get(); 31 | } 32 | } 33 | if (this.value == null) { 34 | this.value = this.function.get(); 35 | } 36 | return this.value; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/hitbox/MainHitboxConfig.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity.hitbox; 2 | 3 | import com.mojang.serialization.Codec; 4 | import com.mojang.serialization.codecs.RecordCodecBuilder; 5 | 6 | import de.dertoaster.multihitboxlib.util.UtilityCodecs; 7 | import net.minecraft.world.phys.Vec2; 8 | 9 | public record MainHitboxConfig( 10 | boolean collidable, 11 | boolean canReceiveDamage, 12 | double damageModifier, 13 | Vec2 baseSize 14 | ) { 15 | 16 | public static final Codec CODEC = RecordCodecBuilder.create(instance -> { 17 | return instance.group( 18 | Codec.BOOL.fieldOf("collidable").forGetter(MainHitboxConfig::collidable), 19 | Codec.BOOL.fieldOf("canReceiveDamage").forGetter(MainHitboxConfig::canReceiveDamage), 20 | Codec.DOUBLE.optionalFieldOf("damageModifier", 1.0D).forGetter(MainHitboxConfig::damageModifier), 21 | UtilityCodecs.VEC2_CODEC.optionalFieldOf("size", Vec2.ZERO).forGetter(MainHitboxConfig::baseSize) 22 | ).apply(instance, MainHitboxConfig::new); 23 | 24 | }); 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/data/SynchEntryData.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.data; 2 | 3 | import java.util.List; 4 | 5 | import com.mojang.serialization.Codec; 6 | import com.mojang.serialization.codecs.RecordCodecBuilder; 7 | 8 | import net.minecraft.resources.ResourceLocation; 9 | 10 | public record SynchEntryData( 11 | ResourceLocation id, 12 | List payload 13 | ) { 14 | 15 | public static final Codec CODEC = RecordCodecBuilder.create(instance -> { 16 | return instance.group( 17 | ResourceLocation.CODEC.fieldOf("id").forGetter(SynchEntryData::id), 18 | Codec.BYTE.listOf().fieldOf("payload").forGetter(SynchEntryData::payload) 19 | ).apply(instance, SynchEntryData::new); 20 | }); 21 | 22 | public boolean validate() { 23 | return this.id != null && !this.id.toString().isEmpty() && this.payload != null && !this.payload.isEmpty(); 24 | } 25 | 26 | public byte[] getPayLoadArray() { 27 | byte[] result = new byte[this.payload.size()]; 28 | for (int i = 0; i < this.payload.size(); i++) { 29 | result[i] = this.payload.get(i); 30 | } 31 | return result; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/example/CommonListener.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.example; 2 | 3 | import de.dertoaster.multihitboxlib.Constants; 4 | import de.dertoaster.multihitboxlib.MHLibMod; 5 | import de.dertoaster.multihitboxlib.example.entity.Anjanath; 6 | import de.dertoaster.multihitboxlib.example.entity.AnjanathALib; 7 | import de.dertoaster.multihitboxlib.example.init.MHLibExampleEntities; 8 | import net.minecraftforge.event.entity.EntityAttributeCreationEvent; 9 | import net.minecraftforge.eventbus.api.SubscribeEvent; 10 | import net.minecraftforge.fml.common.Mod.EventBusSubscriber; 11 | import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus; 12 | 13 | @EventBusSubscriber(modid = Constants.MODID, bus = Bus.MOD) 14 | public class CommonListener { 15 | 16 | @SubscribeEvent 17 | public static void initializeAttributes(EntityAttributeCreationEvent event) { 18 | if (!MHLibMod.shouldRegisterExamples()) { 19 | return; 20 | } 21 | 22 | event.put(MHLibExampleEntities.ANJANATH.get(), Anjanath.createAttributes().build()); 23 | event.put(MHLibExampleEntities.ANJANATH_AL.get(), AnjanathALib.createAttributes().build()); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/util/LazyLoadFieldFunction.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.util; 2 | 3 | import java.util.function.Function; 4 | 5 | public class LazyLoadFieldFunction implements Function { 6 | 7 | private T value = null; 8 | private int lifetime = -1; 9 | private long lastSet = System.currentTimeMillis(); 10 | private final Function function; 11 | 12 | public LazyLoadFieldFunction(final Function function) { 13 | this.function = function; 14 | } 15 | 16 | public LazyLoadFieldFunction(final Function function, final int lifetime) { 17 | this(function); 18 | this.lifetime = lifetime; 19 | } 20 | 21 | public void reset() { 22 | this.value = null; 23 | } 24 | 25 | @Override 26 | public T apply(A argument) { 27 | if (this.lifetime > 0) { 28 | if (System.currentTimeMillis() - this.lastSet >= this.lifetime) { 29 | this.lastSet = System.currentTimeMillis(); 30 | this.value = this.function.apply(argument); 31 | } 32 | } 33 | if (this.value == null) { 34 | this.value = this.function.apply(argument); 35 | } 36 | return this.value; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/util/BoneInformation.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.util; 2 | 3 | import com.mojang.serialization.Codec; 4 | import com.mojang.serialization.codecs.RecordCodecBuilder; 5 | 6 | import net.minecraft.world.phys.Vec3; 7 | 8 | public record BoneInformation(String name, boolean hidden, Vec3 worldPos, Vec3 scale, Vec3 rotation) { 9 | 10 | public static final Vec3 DEFAULT_SCALING = new Vec3(1, 1, 1); 11 | 12 | public static Codec CODEC = RecordCodecBuilder.create(instance -> { 13 | return instance.group( 14 | Codec.STRING.fieldOf("bone").forGetter(BoneInformation::name), 15 | Codec.BOOL.fieldOf("hidden").forGetter(BoneInformation::hidden), 16 | Vec3.CODEC.fieldOf("position").forGetter(BoneInformation::worldPos), 17 | Vec3.CODEC.optionalFieldOf("scaling", DEFAULT_SCALING).forGetter(BoneInformation::scale), 18 | Vec3.CODEC.optionalFieldOf("rotation", Vec3.ZERO).forGetter(BoneInformation::rotation) 19 | ).apply(instance, BoneInformation::new); 20 | }); 21 | 22 | public BoneInformation scale(double scaling) { 23 | return new BoneInformation( 24 | name(), 25 | hidden(), 26 | worldPos(), 27 | scale.scale(scaling), 28 | rotation() 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/mixin/entity/MixinServerEntity.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.mixin.entity; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | import org.spongepowered.asm.mixin.Shadow; 5 | import org.spongepowered.asm.mixin.injection.At; 6 | import org.spongepowered.asm.mixin.injection.Inject; 7 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 8 | 9 | import de.dertoaster.multihitboxlib.api.IMultipartEntity; 10 | import de.dertoaster.multihitboxlib.init.MHLibPackets; 11 | import de.dertoaster.multihitboxlib.network.server.SPacketUpdateMultipart; 12 | import net.minecraft.server.level.ServerEntity; 13 | import net.minecraft.world.entity.Entity; 14 | import net.minecraftforge.network.PacketDistributor; 15 | 16 | @Mixin(ServerEntity.class) 17 | public abstract class MixinServerEntity { 18 | 19 | @Shadow 20 | private Entity entity; 21 | 22 | @Inject( 23 | method = "sendDirtyEntityData()V", 24 | at = @At("HEAD") 25 | ) 26 | private void mixinSendDirtyEntityData(CallbackInfo co) { 27 | if (this.entity.isMultipartEntity() && this.entity instanceof IMultipartEntity ime) { 28 | SPacketUpdateMultipart updatePacket = new SPacketUpdateMultipart(this.entity); 29 | MHLibPackets.MHLIB_NETWORK.send(PacketDistributor.TRACKING_ENTITY.with(() -> this.entity), updatePacket); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/hitbox/SubPartConfig.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity.hitbox; 2 | 3 | import com.mojang.serialization.Codec; 4 | import com.mojang.serialization.codecs.RecordCodecBuilder; 5 | 6 | import de.dertoaster.multihitboxlib.entity.hitbox.type.IHitboxType; 7 | import de.dertoaster.multihitboxlib.init.MHLibHitboxTypes; 8 | 9 | public record SubPartConfig( 10 | String name, 11 | boolean collidable, 12 | boolean canReceiveDamage, 13 | float damageModifier, 14 | double maxDeviationFromServer, 15 | IHitboxType hitboxType 16 | ) { 17 | 18 | public static final Codec CODEC = RecordCodecBuilder.create(instance -> { 19 | return instance.group( 20 | Codec.STRING.fieldOf("name").forGetter(SubPartConfig::name), 21 | Codec.BOOL.fieldOf("collidable").forGetter(SubPartConfig::collidable), 22 | Codec.BOOL.fieldOf("can-receive-damage").forGetter(SubPartConfig::canReceiveDamage), 23 | Codec.FLOAT.optionalFieldOf("damage-modifier", 1.0F).forGetter(SubPartConfig::damageModifier), 24 | Codec.DOUBLE.optionalFieldOf("max-deviation-from-server", 0.0D).forGetter(SubPartConfig::maxDeviationFromServer), 25 | MHLibHitboxTypes.HITBOX_TYPE_DISPATCHER.dispatchedCodec().fieldOf("box").forGetter(SubPartConfig::hitboxType) 26 | ).apply(instance, SubPartConfig::new); 27 | 28 | }); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/data/multihitboxlib/multihitboxlib/hitbox_profiles/anjanath.json: -------------------------------------------------------------------------------- 1 | { 2 | "synched-assets": { 3 | "models": [ 4 | ], 5 | "animations": [ 6 | ], 7 | "textures": [ 8 | ] 9 | }, 10 | "sync-with-model": true, 11 | "part-update-steps": 3, 12 | "trust-client": true, 13 | "synched-part-update-steps": 1, 14 | "synched-bones": [ 15 | "hbLeftLeg", 16 | "hbLeftKnee" 17 | ], 18 | "main-hitbox": { 19 | "collidable": false, 20 | "canReceiveDamage": false, 21 | "size": [ 22 | 7, 23 | 5.5 24 | ] 25 | }, 26 | "parts": [ 27 | { 28 | "name": "hbLeftKnee", 29 | "collidable": true, 30 | "can-receive-damage": true, 31 | "damage-modifier": 0.5, 32 | "box": { 33 | "type": "multihitboxlib:aabb", 34 | "size": [ 35 | 1, 36 | 2 37 | ], 38 | "position": [ 39 | -1, 40 | 0, 41 | 1.5 42 | ], 43 | "pivot-offset": [ 44 | 0, 45 | 0, 46 | 0 47 | ] 48 | } 49 | }, 50 | { 51 | "name": "hbLeftLeg", 52 | "collidable": true, 53 | "can-receive-damage": true, 54 | "damage-modifier": 0.5, 55 | "box": { 56 | "type": "multihitboxlib:aabb", 57 | "size": [ 58 | 1.25, 59 | 2 60 | ], 61 | "position": [ 62 | -1, 63 | 2.1, 64 | 1.25 65 | ], 66 | "pivot-offset": [ 67 | 0, 68 | 0, 69 | 0 70 | ] 71 | } 72 | } 73 | ] 74 | } -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/server/assetsync/SPacketSynchAssets.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.server.assetsync; 2 | 3 | import com.mojang.serialization.Codec; 4 | import com.mojang.serialization.DataResult; 5 | 6 | import de.dertoaster.multihitboxlib.api.network.AbstractSPacketCodecWrappingPacket; 7 | import de.dertoaster.multihitboxlib.assetsynch.data.SynchDataContainer; 8 | 9 | public class SPacketSynchAssets extends AbstractSPacketCodecWrappingPacket { 10 | 11 | public SPacketSynchAssets() { 12 | super(); 13 | } 14 | 15 | public SPacketSynchAssets(SynchDataContainer data) { 16 | super(data); 17 | } 18 | 19 | @Override 20 | public Class getPacketClass() { 21 | return SPacketSynchAssets.class; 22 | } 23 | 24 | @Override 25 | protected Codec codec() { 26 | return SynchDataContainer.CODEC; 27 | } 28 | 29 | @Override 30 | protected SPacketSynchAssets createPacket(DataResult dr) { 31 | SynchDataContainer sdc = dr.getOrThrow(false, (s) -> { 32 | //TODO 33 | 34 | }); 35 | if (sdc != null) { 36 | return new SPacketSynchAssets(sdc); 37 | } 38 | return null; 39 | } 40 | 41 | @Override 42 | protected SPacketSynchAssets createPacket(SynchDataContainer data) { 43 | return new SPacketSynchAssets(data); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/glibplus/IExtendedGeoAnimatableEntity.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.glibplus; 2 | 3 | 4 | import org.jetbrains.annotations.Nullable; 5 | import software.bernie.geckolib.animatable.GeoEntity; 6 | 7 | /** 8 | * 9 | */ 10 | public interface IExtendedGeoAnimatableEntity extends IExtendedGeoAnimatable, GeoEntity { 11 | 12 | /** 13 | * Overridden modified version of the superclass method for handling custom Geckolib+ behaviour. Do not override. 14 | * You should use {@link #triggerAnim(String)} and its overloaded variants instead of this. 15 | * 16 | * @param controllerName The name of the controller name the animation belongs to, or null to do an inefficient lazy search. 17 | * @param animName The name of animation to trigger. This needs to have been registered with the controller via {@link software.bernie.geckolib.core.animation.AnimationController#triggerableAnim AnimationController.triggerableAnim}. 18 | */ 19 | @Override 20 | default void triggerAnim(@Nullable String controllerName, String animName) { 21 | GeoEntity.super.triggerAnim(controllerName, animName); 22 | } 23 | 24 | /** 25 | * 26 | * @param animName 27 | */ 28 | default void triggerAnim(String animName) { 29 | 30 | } 31 | 32 | default void triggerAnim(IRawAnimation targetAnim) { 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/alibplus/IExtendedGeoAnimatableEntity.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.alibplus; 2 | 3 | import mod.azure.azurelib.animatable.GeoEntity; 4 | import mod.azure.azurelib.core.animation.AnimationController; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | /** 8 | * 9 | */ 10 | public interface IExtendedGeoAnimatableEntity extends IExtendedGeoAnimatable, GeoEntity { 11 | 12 | /** 13 | * Overridden modified version of the superclass method for handling custom Geckolib+ behaviour. Do not override. 14 | * You should use {@link #triggerAnim(String)} and its overloaded variants instead of this. 15 | * 16 | * @param controllerName The name of the controller name the animation belongs to, or null to do an inefficient lazy search. 17 | * @param animName The name of animation to trigger. This needs to have been registered with the controller via {@link AnimationController#triggerableAnim AnimationController.triggerableAnim}. 18 | */ 19 | @Override 20 | default void triggerAnim(@Nullable String controllerName, String animName) { 21 | GeoEntity.super.triggerAnim(controllerName, animName); 22 | } 23 | 24 | /** 25 | * 26 | * @param animName 27 | */ 28 | default void triggerAnim(String animName) { 29 | 30 | } 31 | 32 | default void triggerAnim(IRawAnimation targetAnim) { 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/impl/MHLibEnforcementManager.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.impl; 2 | 3 | import java.io.File; 4 | import java.util.Optional; 5 | 6 | import javax.annotation.Nonnull; 7 | 8 | import de.dertoaster.multihitboxlib.Constants; 9 | import de.dertoaster.multihitboxlib.assetsynch.AbstractAssetEnforcementManager; 10 | import net.minecraft.resources.ResourceLocation; 11 | import net.minecraftforge.fml.ModList; 12 | 13 | public abstract class MHLibEnforcementManager extends AbstractAssetEnforcementManager { 14 | 15 | @Override 16 | protected Optional encodeData(ResourceLocation id) { 17 | // Looks in the config directories first! 18 | File location = this.getFileForId(id); 19 | if (!location.exists() || !location.isFile()) { 20 | // We couldn't find anything in the config, let's check the mod jar... 21 | // TODO: Implement, but this is hacky, so watch out! Also if you found it, write the file to disk 22 | return Optional.empty(); 23 | } 24 | return Optional.ofNullable(encodeToBytes(location.toPath())); 25 | } 26 | 27 | @Nonnull 28 | @Override 29 | protected File createServerDirectory() { 30 | return new File(Constants.MHLIB_ASSET_DIR, this.getSubDirectoryName()); 31 | } 32 | 33 | @Nonnull 34 | @Override 35 | protected File createSynchDirectory() { 36 | return new File(Constants.MHLIB_SYNC_DIR, this.getSubDirectoryName()); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/client/SPacketHandlerSetMaster.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.client; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import de.dertoaster.multihitboxlib.api.IMultipartEntity; 6 | import de.dertoaster.multihitboxlib.api.network.AbstractPacketHandler; 7 | import de.dertoaster.multihitboxlib.network.server.SPacketSetMaster; 8 | import net.minecraft.client.multiplayer.ClientLevel; 9 | import net.minecraft.client.player.AbstractClientPlayer; 10 | import net.minecraft.world.entity.Entity; 11 | import net.minecraft.world.entity.player.Player; 12 | import net.minecraft.world.level.Level; 13 | import net.minecraftforge.network.NetworkEvent.Context; 14 | 15 | public class SPacketHandlerSetMaster extends AbstractPacketHandler { 16 | 17 | @Override 18 | protected void execHandlePacket(SPacketSetMaster packet, Supplier context, Level world, Player player) { 19 | if (!(world instanceof ClientLevel || player instanceof AbstractClientPlayer)) { 20 | // Illegal side, ignore 21 | return; 22 | } 23 | final int entityID = packet.getEntityID(); 24 | 25 | final Entity entity = world.getEntity(entityID); 26 | if(entity != null && entity instanceof IMultipartEntity imp) { 27 | // Entity does not want synching, so we won't sync any data to it 28 | if(!imp.syncWithModel()) { 29 | return; 30 | } 31 | imp.processSetMasterPacket(packet); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/mixin/azurelib/MixinGeoRenderer.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.mixin.azurelib; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | import de.dertoaster.multihitboxlib.api.IMHLibExtendedRenderLayer; 12 | import mod.azure.azurelib.renderer.GeoRenderer; 13 | 14 | @Mixin(value = GeoRenderer.class, priority = Integer.MAX_VALUE - 1) 15 | public interface MixinGeoRenderer { 16 | 17 | @Unique 18 | default void _mhlib_callLayers(final Consumer runPerLayer) { 19 | GeoRenderer self = (GeoRenderer) this; 20 | for (Object layerGeo : self.getRenderLayers()) { 21 | if (layerGeo instanceof IMHLibExtendedRenderLayer mhlibExtension) { 22 | runPerLayer.accept(mhlibExtension); 23 | } 24 | } 25 | } 26 | 27 | @Inject(method = "renderRecursively", at = @At("HEAD"), remap = false) 28 | default void mixinRenderRecursivelyStart(CallbackInfo ci) { 29 | this._mhlib_callLayers(IMHLibExtendedRenderLayer::onRenderRecursivelyStart); 30 | } 31 | 32 | @Inject(method = "renderRecursively", at = @At("TAIL"), remap = false) 33 | default void mixinRenderRecursivelyEnd(CallbackInfo ci) { 34 | this._mhlib_callLayers(IMHLibExtendedRenderLayer::onRenderRecursivelyEnd); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/mixin/geckolib/MixinGeoRenderer.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.mixin.geckolib; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | import de.dertoaster.multihitboxlib.api.IMHLibExtendedRenderLayer; 12 | import software.bernie.geckolib.renderer.GeoRenderer; 13 | 14 | @Mixin(value = GeoRenderer.class, priority = Integer.MAX_VALUE - 1) 15 | public interface MixinGeoRenderer { 16 | 17 | @Unique 18 | default void _mhlib_callLayers(final Consumer runPerLayer) { 19 | GeoRenderer self = (GeoRenderer) this; 20 | for (Object layerGeo : self.getRenderLayers()) { 21 | if (layerGeo instanceof IMHLibExtendedRenderLayer mhlibExtension) { 22 | runPerLayer.accept(mhlibExtension); 23 | } 24 | } 25 | } 26 | 27 | @Inject(method = "renderRecursively", at = @At("HEAD"), remap = false) 28 | default void mixinRenderRecursivelyStart(CallbackInfo ci) { 29 | this._mhlib_callLayers(IMHLibExtendedRenderLayer::onRenderRecursivelyStart); 30 | } 31 | 32 | @Inject(method = "renderRecursively", at = @At("TAIL"), remap = false) 33 | default void mixinRenderRecursivelyEnd(CallbackInfo ci) { 34 | this._mhlib_callLayers(IMHLibExtendedRenderLayer::onRenderRecursivelyEnd); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/hitbox/HitboxProfile.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity.hitbox; 2 | 3 | import java.util.List; 4 | 5 | import com.mojang.serialization.Codec; 6 | import com.mojang.serialization.codecs.RecordCodecBuilder; 7 | 8 | public record HitboxProfile( 9 | AssetEnforcementConfig assetConfig, 10 | boolean syncToModel, 11 | boolean trustClient, 12 | int partUpdateSteps, 13 | int synchedPartUpdateSteps, 14 | List synchedBones, 15 | MainHitboxConfig mainHitboxConfig, 16 | List partConfigs 17 | ) { 18 | 19 | public static final Codec CODEC = RecordCodecBuilder.create(instance -> { 20 | return instance.group( 21 | AssetEnforcementConfig.CODEC.fieldOf("synched-assets").forGetter(HitboxProfile::assetConfig), 22 | Codec.BOOL.fieldOf("sync-with-model").forGetter(HitboxProfile::syncToModel), 23 | Codec.BOOL.optionalFieldOf("trust-client", false).forGetter(HitboxProfile::trustClient), 24 | Codec.INT.optionalFieldOf("part-update-steps", 3).forGetter(HitboxProfile::partUpdateSteps), 25 | Codec.INT.optionalFieldOf("synched-part-update-steps", 1).forGetter(HitboxProfile::synchedPartUpdateSteps), 26 | Codec.STRING.listOf().fieldOf("synched-bones").forGetter(HitboxProfile::synchedBones), 27 | MainHitboxConfig.CODEC.fieldOf("main-hitbox").forGetter(HitboxProfile::mainHitboxConfig), 28 | SubPartConfig.CODEC.listOf().fieldOf("parts").forGetter(HitboxProfile::partConfigs) 29 | 30 | ).apply(instance, HitboxProfile::new); 31 | }); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/IMHLibExtendedRenderLayer.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api; 2 | 3 | import org.joml.Vector3d; 4 | import net.minecraft.util.Tuple; 5 | 6 | public interface IMHLibExtendedRenderLayer { 7 | 8 | void pushToStack(Vector3d scaling, Vector3d rotation); 9 | 10 | Tuple popStack(); 11 | 12 | Vector3d getCurrentScaling(); 13 | Vector3d getCurrentRotation(); 14 | 15 | void applyCurrentValues(Vector3d scaling, Vector3d rotation); 16 | 17 | void resetStack(); 18 | 19 | static final Vector3d DEFAULT_SCALING = new Vector3d(1, 1, 1); 20 | static final Vector3d DEFAULT_ROTATION = new Vector3d(0,0,0); 21 | 22 | default void resetCurrentValues() { 23 | applyCurrentValues(null, null); 24 | } 25 | 26 | default void onPostRender() { 27 | this.resetStack(); 28 | this.resetCurrentValues(); 29 | } 30 | 31 | default void onPreRender() { 32 | this.resetStack(); 33 | this.applyCurrentValues(DEFAULT_SCALING, DEFAULT_ROTATION); 34 | } 35 | 36 | default void onRenderRecursivelyStart() { 37 | // Object needs to be cloned! Otherwise we will always modify the same thing 38 | final Vector3d currentRot = new Vector3d(this.getCurrentRotation()); 39 | final Vector3d currentScale = new Vector3d(this.getCurrentScaling()); 40 | if (currentRot != null && currentScale != null) { 41 | this.pushToStack(currentScale, currentRot); 42 | } 43 | 44 | } 45 | 46 | default void onRenderRecursivelyEnd() { 47 | Tuple tuple = this.popStack(); 48 | this.applyCurrentValues(tuple.getA(), tuple.getB()); 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/init/MHLibHitboxTypes.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.init; 2 | 3 | import com.mojang.serialization.Codec; 4 | 5 | import commoble.databuddy.codec.RegistryDispatcher; 6 | import de.dertoaster.multihitboxlib.MHLibMod; 7 | import de.dertoaster.multihitboxlib.entity.hitbox.type.IHitboxType; 8 | import de.dertoaster.multihitboxlib.entity.hitbox.type.implementation.AABBHitboxType; 9 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 10 | import net.minecraftforge.registries.RegistryObject; 11 | 12 | public class MHLibHitboxTypes { 13 | 14 | public static void init() { 15 | 16 | } 17 | 18 | public static final RegistryDispatcher HITBOX_TYPE_DISPATCHER = RegistryDispatcher.makeDispatchForgeRegistry( 19 | FMLJavaModLoadingContext.get().getModEventBus(), 20 | MHLibMod.prefix("registry/dispatcher/hitboxtype"), 21 | rule -> rule.getType(), 22 | builder -> {} 23 | ); 24 | 25 | public static final RegistryObject> AABB = HITBOX_TYPE_DISPATCHER.registry().register("aabb", () -> AABBHitboxType.CODEC); 26 | //public static final RegistryObject> SPHERE = HITBOX_TYPE_DISPATCHER.registry().register("sphere", () -> SphereHitboxType.CODEC); 27 | //public static final RegistryObject> ORIENTABLE_SPHEROID = HITBOX_TYPE_DISPATCHER.registry().register("orientable_spheroid", () -> OrientableSpheroidHitboxType.CODEC); 28 | //public static final RegistryObject> OBB = HITBOX_TYPE_DISPATCHER.registry().register("orientable_bb", () -> OrientableBBHitboxType.CODEC); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/network/AbstractSPacketHandlerCodecWrappingPacket.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.network; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import de.dertoaster.multihitboxlib.util.ClientOnlyMethods; 6 | import net.minecraft.network.protocol.game.ServerPacketListener; 7 | import net.minecraft.world.entity.player.Player; 8 | import net.minecraft.world.level.Level; 9 | import net.minecraftforge.network.NetworkEvent.Context; 10 | 11 | public abstract class AbstractSPacketHandlerCodecWrappingPacket> implements IMessageHandler

{ 12 | 13 | public IMessageHandler

cast() { 14 | return (IMessageHandler

)this; 15 | } 16 | 17 | @Override 18 | public void handlePacket(P packet, Supplier context) { 19 | context.get().enqueueWork(() -> { 20 | Player sender = null; 21 | Level world = null; 22 | if(context.get().getNetworkManager().getPacketListener() instanceof ServerPacketListener) { 23 | sender = context.get().getSender(); 24 | if(sender != null) { 25 | world = sender.level(); 26 | } 27 | } 28 | //if(context.get().getNetworkManager().getPacketListener() instanceof ClientPacketListener) { 29 | //Otherwise it must be client? 30 | else { 31 | sender = ClientOnlyMethods.getClientPlayer(); 32 | world = ClientOnlyMethods.getWorld(); 33 | } 34 | 35 | 36 | this.execHandlePacket(packet, context, world, sender); 37 | }); 38 | context.get().setPacketHandled(true); 39 | } 40 | 41 | protected abstract void execHandlePacket(P packet, Supplier context, Level world, Player sender); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/impl/AlibAnimationEnforcementManager.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.impl; 2 | 3 | import java.util.Optional; 4 | 5 | import com.google.gson.JsonObject; 6 | 7 | import de.dertoaster.multihitboxlib.Constants; 8 | import de.dertoaster.multihitboxlib.MHLibMod; 9 | import mod.azure.azurelib.cache.AzureLibCache; 10 | import mod.azure.azurelib.loading.object.BakedAnimations; 11 | import mod.azure.azurelib.util.JsonUtil; 12 | import net.minecraft.resources.ResourceLocation; 13 | import net.minecraft.util.GsonHelper; 14 | 15 | public class AlibAnimationEnforcementManager extends MHLibEnforcementManager { 16 | 17 | @Override 18 | protected boolean receiveAndLoadInternally(ResourceLocation id, byte[] data) { 19 | if (AzureLibCache.getBakedAnimations().containsKey(id)) 20 | MHLibMod.LOGGER.debug("Overriding azurelib animation with id <" + id.toString() + ">"); 21 | 22 | Optional optAnimations = this.createBakedAnimations(data); 23 | 24 | optAnimations.ifPresent(asset -> AzureLibCache.getBakedAnimations().put(id, asset)); 25 | return optAnimations.isPresent(); 26 | } 27 | 28 | private Optional createBakedAnimations(byte[] data) { 29 | String stringData = new String(data); 30 | JsonObject jo = GsonHelper.fromJson(JsonUtil.GEO_GSON, stringData, JsonObject.class); 31 | if (jo != null) { 32 | return Optional.of(JsonUtil.GEO_GSON.fromJson(jo, BakedAnimations.class)); 33 | } 34 | return Optional.empty(); 35 | } 36 | 37 | @Override 38 | public String getSubDirectoryName() { 39 | return "animations/" + Constants.Dependencies.AZURELIB_MODID; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/init/MHLibDatapackLoaders.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.init; 2 | 3 | import de.dertoaster.multihitboxlib.Constants; 4 | import de.dertoaster.multihitboxlib.MHLibMod; 5 | import de.dertoaster.multihitboxlib.api.DatapackRegistry; 6 | import de.dertoaster.multihitboxlib.entity.hitbox.HitboxProfile; 7 | import net.minecraft.core.RegistryAccess; 8 | import net.minecraft.resources.ResourceLocation; 9 | import net.minecraft.world.entity.EntityType; 10 | import net.minecraftforge.eventbus.api.SubscribeEvent; 11 | import net.minecraftforge.fml.common.Mod; 12 | import net.minecraftforge.registries.DataPackRegistryEvent.NewRegistry; 13 | import net.minecraftforge.registries.ForgeRegistries; 14 | 15 | import java.util.Optional; 16 | 17 | @Mod.EventBusSubscriber(modid = Constants.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) 18 | public class MHLibDatapackLoaders { 19 | 20 | public static final DatapackRegistry HITBOX_PROFILE_REGISTRY = new DatapackRegistry<>(MHLibMod.prefix("hitbox_profiles"), HitboxProfile.CODEC); 21 | 22 | @SubscribeEvent 23 | public static void onRegistryRegistration(NewRegistry event) { 24 | HITBOX_PROFILE_REGISTRY.registerSynchable(event); 25 | } 26 | 27 | public static Optional getHitboxProfile(ResourceLocation entityID, RegistryAccess registryAccess) { 28 | return Optional.ofNullable(HITBOX_PROFILE_REGISTRY.get(entityID, registryAccess)); 29 | } 30 | 31 | public static Optional getHitboxProfile(EntityType entityType, RegistryAccess registryAccess) { 32 | return getHitboxProfile(ForgeRegistries.ENTITY_TYPES.getKey(entityType), registryAccess); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/server/CPacketHandlerBoneInformation.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.server; 2 | 3 | import java.util.UUID; 4 | import java.util.function.Supplier; 5 | 6 | import de.dertoaster.multihitboxlib.api.IMultipartEntity; 7 | import de.dertoaster.multihitboxlib.api.network.AbstractPacketHandler; 8 | import de.dertoaster.multihitboxlib.network.client.CPacketBoneInformation; 9 | import net.minecraft.server.level.ServerLevel; 10 | import net.minecraft.server.level.ServerPlayer; 11 | import net.minecraft.world.entity.Entity; 12 | import net.minecraft.world.entity.player.Player; 13 | import net.minecraft.world.level.Level; 14 | import net.minecraftforge.network.NetworkEvent.Context; 15 | 16 | public class CPacketHandlerBoneInformation extends AbstractPacketHandler { 17 | 18 | @Override 19 | protected void execHandlePacket(CPacketBoneInformation packet, Supplier context, Level world, Player player) { 20 | if (!(world instanceof ServerLevel && player instanceof ServerPlayer)) { 21 | // Illegal side, ignore 22 | return; 23 | } 24 | final UUID senderID = player.getUUID(); 25 | final int entityID = packet.getEntityID(); 26 | 27 | final Entity entity = world.getEntity(entityID); 28 | if(entity != null && entity instanceof IMultipartEntity imp) { 29 | // Entity does not want synching, so we won't sync any data to it 30 | if(!imp.syncWithModel()) { 31 | return; 32 | } 33 | if(!senderID.equals(imp.getMasterUUID())) { 34 | // UUIDs do not match => warn 35 | return; 36 | } 37 | imp.processBoneInformation(packet.getBoneInformation()); 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/impl/GlibAnimationEnforcementManager.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.impl; 2 | 3 | import java.util.Optional; 4 | 5 | import com.google.gson.JsonObject; 6 | 7 | import de.dertoaster.multihitboxlib.Constants; 8 | import de.dertoaster.multihitboxlib.MHLibMod; 9 | import net.minecraft.resources.ResourceLocation; 10 | import net.minecraft.util.GsonHelper; 11 | import software.bernie.geckolib.cache.GeckoLibCache; 12 | import software.bernie.geckolib.loading.object.BakedAnimations; 13 | import software.bernie.geckolib.util.JsonUtil; 14 | 15 | public class GlibAnimationEnforcementManager extends MHLibEnforcementManager { 16 | 17 | @Override 18 | protected boolean receiveAndLoadInternally(ResourceLocation id, byte[] data) { 19 | if (GeckoLibCache.getBakedAnimations().containsKey(id)) 20 | MHLibMod.LOGGER.debug("Overriding geckolib animation with id <" + id.toString() + ">"); 21 | 22 | Optional optAnimations = this.createBakedAnimations(data); 23 | 24 | optAnimations.ifPresent(asset -> GeckoLibCache.getBakedAnimations().put(id, asset)); 25 | return optAnimations.isPresent(); 26 | } 27 | 28 | private Optional createBakedAnimations(byte[] data) { 29 | String stringData = new String(data); 30 | JsonObject jo = GsonHelper.fromJson(JsonUtil.GEO_GSON, stringData, JsonObject.class); 31 | if (jo != null) { 32 | return Optional.of(JsonUtil.GEO_GSON.fromJson(jo, BakedAnimations.class)); 33 | } 34 | return Optional.empty(); 35 | } 36 | 37 | @Override 38 | public String getSubDirectoryName() { 39 | return "animations/" + Constants.Dependencies.GECKOLIB_MODID; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/assetfinders/HitboxProfileAssetFinder.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.assetfinders; 2 | 3 | import java.util.HashSet; 4 | import java.util.Objects; 5 | import java.util.Set; 6 | import java.util.function.Predicate; 7 | import java.util.stream.Collectors; 8 | 9 | import de.dertoaster.multihitboxlib.entity.hitbox.AssetEnforcementConfig; 10 | import de.dertoaster.multihitboxlib.entity.hitbox.HitboxProfile; 11 | import de.dertoaster.multihitboxlib.init.MHLibDatapackLoaders; 12 | import net.minecraft.core.RegistryAccess; 13 | import net.minecraft.resources.ResourceLocation; 14 | 15 | public class HitboxProfileAssetFinder extends AbstractAssetFinder { 16 | 17 | private static final Predicate RS_CHECK_PREDICATE = rs -> !rs.toString().isBlank() && !rs.getNamespace().isBlank() && !rs.getPath().isBlank(); 18 | 19 | @Override 20 | public Set apply(RegistryAccess registryAccess) { 21 | Set result = new HashSet<>(); 22 | for (HitboxProfile hp : MHLibDatapackLoaders.HITBOX_PROFILE_REGISTRY.values(registryAccess)) { 23 | if (hp == null) { 24 | continue; 25 | } 26 | AssetEnforcementConfig aec = hp.assetConfig(); 27 | if (aec == null) { 28 | continue; 29 | } 30 | result.addAll(aec.animations().stream().filter(Objects::nonNull).filter(RS_CHECK_PREDICATE).collect(Collectors.toList())); 31 | result.addAll(aec.models().stream().filter(Objects::nonNull).filter(RS_CHECK_PREDICATE).collect(Collectors.toList())); 32 | result.addAll(aec.textures().stream().filter(Objects::nonNull).filter(RS_CHECK_PREDICATE).collect(Collectors.toList())); 33 | } 34 | return result; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/DiskSaveRunner.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | import org.apache.commons.io.FileUtils; 9 | 10 | import net.minecraft.client.Minecraft; 11 | import net.minecraft.resources.ResourceLocation; 12 | import net.minecraft.util.Tuple; 13 | import net.minecraftforge.fml.loading.FMLEnvironment; 14 | 15 | public class DiskSaveRunner extends HashSet> implements Runnable, Set> { 16 | 17 | private static final long serialVersionUID = 6685706771120439838L; 18 | 19 | private final AbstractAssetEnforcementManager manager; 20 | private final boolean deleteDirectory; 21 | 22 | public DiskSaveRunner(final AbstractAssetEnforcementManager managerIn, boolean deleteBeforeSaving) { 23 | this.manager = managerIn; 24 | this.deleteDirectory = deleteBeforeSaving; 25 | } 26 | 27 | @Override 28 | public void run() { 29 | if (this.deleteDirectory) { 30 | if (FMLEnvironment.dist.isClient()) { 31 | File dir = this.manager.getSidedDirectory(); 32 | if (dir != null && dir.exists()) { 33 | try { 34 | FileUtils.deleteDirectory(dir); 35 | } catch (IOException e) { 36 | //TODO: Log 37 | e.printStackTrace(); 38 | } 39 | } 40 | } 41 | } 42 | 43 | for(Tuple tuple : this) { 44 | final ResourceLocation id = tuple.getA(); 45 | final byte[] data = tuple.getB(); 46 | 47 | if (!this.manager.writeFile(id, data)) { 48 | //TODO: Log 49 | } 50 | } 51 | 52 | Minecraft.getInstance().reloadResourcePacks(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle 2 | org.gradle.jvmargs=-Xmx8G 3 | org.gradle.daemon=false 4 | 5 | # Base 6 | modid=multihitboxlib 7 | mod_name=Multihitbox Library 8 | mod_group=de.dertoaster.multihitboxlib 9 | mod_loader=javafml 10 | mod_license=All Rights Reserved 11 | mod_description=Library for advanced multihitbox mobs. Can sync hitboxes to Geckolib/AzureLib bones. 12 | mod_credits=DerToaster, Meme Man, 19__ (for helping with interface mixins) 13 | mod_issueTrackerURL=https://github.com/DerToaster98/MultiHitBoxLib/issues 14 | mappings_channel=parchment 15 | modmuss50_mod_publish_version=0.4.0 16 | curseforge_id=899090 17 | modrinth_id=zxK3GsTY 18 | 19 | mod_version=1.8.1 20 | loader_version=[47,) 21 | 22 | mappings_version=2023.09.03-1.20.1 23 | 24 | mc_version=1.20.1 25 | forge_version=47.2.0 26 | 27 | mc_version_range=[1.20.1,1.20.2) 28 | forge_version_range=[47,) 29 | 30 | forgegradle_version=[6.0,6.2) 31 | parchmentmc_version=1.+ 32 | 33 | mixin_version=0.8.5 34 | mixingradle_version=0.7-SNAPSHOT 35 | 36 | mixinbooster_version=0.1.1 37 | mixinbooster_version_range=[0.1.0+1.20.1,) 38 | 39 | # Dependencies (Embedded) 40 | databuddy_branch=databuddy-1.20.1 41 | databuddy_version=4.0.0.0 42 | 43 | # Dependencies (Optional) 44 | geckolib_version=1.20.1:4.4.7 45 | azurelib_version=1.20.1:2.0.37 46 | 47 | geckolib_version_range=[4.2,) 48 | azurelib_version_range=[2.0.37,) 49 | 50 | jei_version=15.2.0.27 51 | jeed_version=4599236 52 | 53 | jei_version_range=[15.2.0.27,) 54 | jeed_version_range=[1.20-2.1.4,) 55 | 56 | catalogue_version=4590890 57 | gmmo_version=4658132 58 | 59 | catalogue_version_range=[1.7.1,) 60 | gmmo_version_range=[2.2.1,) 61 | 62 | jade_version=4711195 63 | appleskin_version=mc1.20.1-2.5.0 64 | 65 | jade_version_range=[11.5.1,) 66 | appleskin_version_range=[mc1.20.1-2.5.0,) -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/alibplus/WrappedAnimationController.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.alibplus; 2 | 3 | import mod.azure.azurelib.core.animation.AnimationController; 4 | 5 | /** 6 | * 7 | * 8 | * @param Instances/sublclasses of {@link IExtendedGeoAnimatable}. 9 | */ 10 | public class WrappedAnimationController { 11 | protected final E animatable; 12 | protected final AnimationController baseController; 13 | protected final String controllerName; 14 | protected double animTransitionInterval; 15 | protected double curAnimLength = 0; 16 | protected double curAnimProgress = 0; 17 | protected double curTransitionProgress = 0; 18 | protected IRawAnimation curRawAnim; 19 | 20 | public WrappedAnimationController(E animatable, AnimationController baseController, double animTransitionInterval) { 21 | this.animatable = animatable; 22 | this.baseController = baseController; 23 | this.controllerName = baseController.getName(); 24 | this.animTransitionInterval = animTransitionInterval; 25 | } 26 | 27 | public WrappedAnimationController(E animatable, AnimationController baseController) { 28 | this(animatable, baseController, 1); 29 | } 30 | 31 | public E getAnimatable() { 32 | return animatable; 33 | } 34 | 35 | public AnimationController getBaseController() { 36 | return baseController; 37 | } 38 | 39 | public String getName() { 40 | return controllerName; 41 | } 42 | 43 | public void tick() { 44 | 45 | } 46 | 47 | protected double getSyncedProgress() { 48 | return 0; 49 | } 50 | 51 | public static IRawAnimation none() { 52 | return null; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/example/entity/Anjanath.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.example.entity; 2 | 3 | import net.minecraft.world.entity.EntityType; 4 | import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder; 5 | import net.minecraft.world.entity.ai.attributes.Attributes; 6 | import net.minecraft.world.entity.monster.Monster; 7 | import net.minecraft.world.level.Level; 8 | import software.bernie.geckolib.animatable.GeoEntity; 9 | import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache; 10 | import software.bernie.geckolib.core.animation.AnimatableManager.ControllerRegistrar; 11 | import software.bernie.geckolib.core.animation.RawAnimation; 12 | import software.bernie.geckolib.util.GeckoLibUtil; 13 | 14 | public class Anjanath extends Monster implements GeoEntity { 15 | 16 | private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this); 17 | 18 | private static final RawAnimation ANIM_WALK = RawAnimation.begin().thenPlay("walk"); 19 | private static final RawAnimation ANIM_IDLE = RawAnimation.begin().thenPlay("idle"); 20 | 21 | public Anjanath(EntityType pEntityType, Level pLevel) { 22 | super(pEntityType, pLevel); 23 | 24 | this.setMaxUpStep(2.0F); 25 | } 26 | 27 | public static Builder createAttributes() { 28 | return Monster.createMobAttributes() 29 | .add(Attributes.FOLLOW_RANGE, 16) 30 | .add(Attributes.MAX_HEALTH, 1) 31 | .add(Attributes.MOVEMENT_SPEED, 0.25f) 32 | .add(Attributes.ATTACK_DAMAGE, 5) 33 | .add(Attributes.ATTACK_KNOCKBACK, 0.1); 34 | } 35 | 36 | @Override 37 | public AnimatableInstanceCache getAnimatableInstanceCache() { 38 | return cache; 39 | } 40 | 41 | @Override 42 | public void registerControllers(ControllerRegistrar arg0) { 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/example/entity/AnjanathALib.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.example.entity; 2 | 3 | import mod.azure.azurelib.animatable.GeoEntity; 4 | import mod.azure.azurelib.core.animatable.instance.AnimatableInstanceCache; 5 | import mod.azure.azurelib.core.animation.AnimatableManager; 6 | import mod.azure.azurelib.core.animation.RawAnimation; 7 | import mod.azure.azurelib.util.AzureLibUtil; 8 | import net.minecraft.world.entity.EntityType; 9 | import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder; 10 | import net.minecraft.world.entity.ai.attributes.Attributes; 11 | import net.minecraft.world.entity.monster.Monster; 12 | import net.minecraft.world.level.Level; 13 | 14 | public class AnjanathALib extends Monster implements GeoEntity { 15 | 16 | private final AnimatableInstanceCache cache = AzureLibUtil.createInstanceCache(this); 17 | 18 | private static final RawAnimation ANIM_WALK = RawAnimation.begin().thenPlay("walk"); 19 | private static final RawAnimation ANIM_IDLE = RawAnimation.begin().thenPlay("idle"); 20 | 21 | public AnjanathALib(EntityType pEntityType, Level pLevel) { 22 | super(pEntityType, pLevel); 23 | } 24 | 25 | @Override 26 | public float maxUpStep() { 27 | return 2.0f; 28 | } 29 | 30 | public static Builder createAttributes() { 31 | return Monster.createMobAttributes() 32 | .add(Attributes.FOLLOW_RANGE, 16) 33 | .add(Attributes.MAX_HEALTH, 1) 34 | .add(Attributes.MOVEMENT_SPEED, 0.25f) 35 | .add(Attributes.ATTACK_DAMAGE, 5) 36 | .add(Attributes.ATTACK_KNOCKBACK, 0.1); 37 | } 38 | 39 | @Override 40 | public AnimatableInstanceCache getAnimatableInstanceCache() { 41 | return cache; 42 | } 43 | 44 | @Override 45 | public void registerControllers(AnimatableManager.ControllerRegistrar arg0) { 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/entity/hitbox/type/implementation/AABBHitboxType.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.entity.hitbox.type.implementation; 2 | 3 | import com.mojang.serialization.Codec; 4 | import com.mojang.serialization.codecs.RecordCodecBuilder; 5 | 6 | import de.dertoaster.multihitboxlib.entity.MHLibPartEntity; 7 | import de.dertoaster.multihitboxlib.entity.hitbox.SubPartConfig; 8 | import de.dertoaster.multihitboxlib.entity.hitbox.type.IHitboxType; 9 | import de.dertoaster.multihitboxlib.util.UtilityCodecs; 10 | import net.minecraft.world.entity.Entity; 11 | import net.minecraft.world.entity.EntityDimensions; 12 | import net.minecraft.world.phys.Vec2; 13 | import net.minecraft.world.phys.Vec3; 14 | 15 | public class AABBHitboxType implements IHitboxType { 16 | 17 | protected final Vec2 baseSize; 18 | protected final Vec3 basePosition; 19 | protected final Vec3 pivot; 20 | 21 | public AABBHitboxType(Vec2 size, Vec3 pos, Vec3 pivot) { 22 | this.baseSize = size; 23 | this.basePosition = pos; 24 | this.pivot = pivot; 25 | } 26 | 27 | public static final Codec CODEC = RecordCodecBuilder.create(instance -> { 28 | return instance.group( 29 | UtilityCodecs.VEC2_CODEC.fieldOf("size").forGetter(obj -> obj.baseSize), 30 | Vec3.CODEC.fieldOf("position").forGetter(obj -> obj.basePosition), 31 | Vec3.CODEC.optionalFieldOf("pivot", Vec3.ZERO).forGetter(obj -> obj.pivot) 32 | ).apply(instance, AABBHitboxType::new); 33 | }); 34 | 35 | @Override 36 | public Codec getType() { 37 | return CODEC; 38 | } 39 | 40 | @Override 41 | public MHLibPartEntity createPartEntity(SubPartConfig config, T parent, int partNumber) { 42 | return new MHLibPartEntity(parent, config, EntityDimensions.scalable(this.baseSize.x, this.baseSize.y), this.basePosition, this.pivot); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/network/AbstractSPacketSyncDatapackContent.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.network; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.function.BiConsumer; 6 | 7 | import com.mojang.serialization.Codec; 8 | 9 | import net.minecraft.nbt.CompoundTag; 10 | import net.minecraft.nbt.NbtOps; 11 | import net.minecraft.network.FriendlyByteBuf; 12 | import net.minecraft.resources.ResourceLocation; 13 | 14 | public abstract class AbstractSPacketSyncDatapackContent> implements IMessage { 15 | 16 | protected final Codec> MAPPER = this.createMapper(); 17 | public final Map data; 18 | protected final Codec CODEC = this.getCodec(); 19 | 20 | protected abstract Codec getCodec(); 21 | protected abstract T createFromPacket(Map data); 22 | public abstract BiConsumer consumer(); 23 | 24 | public AbstractSPacketSyncDatapackContent() { 25 | this.data = null; 26 | } 27 | 28 | public AbstractSPacketSyncDatapackContent(Map data) { 29 | this.data = data; 30 | } 31 | 32 | public Map getData() { 33 | return this.data; 34 | } 35 | 36 | @Override 37 | public T fromBytes(FriendlyByteBuf buffer) { 38 | return this.createFromPacket(this.MAPPER.parse(NbtOps.INSTANCE, buffer.readNbt()).result().orElse(new HashMap<>())); 39 | } 40 | 41 | @Override 42 | public void toBytes(T packet, FriendlyByteBuf buffer) { 43 | buffer.writeNbt((CompoundTag) (packet.MAPPER.encodeStart(NbtOps.INSTANCE, packet.data).result().orElse(new CompoundTag()))); 44 | } 45 | 46 | protected Codec> createMapper() { 47 | return Codec.unboundedMap(ResourceLocation.CODEC, this.getCodec()); 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/glibplus/WrappedAnimationController.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.glibplus; 2 | 3 | import software.bernie.geckolib.core.animation.Animation; 4 | import software.bernie.geckolib.core.animation.AnimationController; 5 | import software.bernie.geckolib.core.animation.RawAnimation; 6 | 7 | /** 8 | * 9 | * 10 | * @param Instances/sublclasses of {@link IExtendedGeoAnimatable}. 11 | */ 12 | public class WrappedAnimationController { 13 | protected final E animatable; 14 | protected final AnimationController baseController; 15 | protected final String controllerName; 16 | protected double animTransitionInterval; 17 | protected double curAnimLength = 0; 18 | protected double curAnimProgress = 0; 19 | protected double curTransitionProgress = 0; 20 | protected IRawAnimation curRawAnim; 21 | 22 | public WrappedAnimationController(E animatable, AnimationController baseController, double animTransitionInterval) { 23 | this.animatable = animatable; 24 | this.baseController = baseController; 25 | this.controllerName = baseController.getName(); 26 | this.animTransitionInterval = animTransitionInterval; 27 | } 28 | 29 | public WrappedAnimationController(E animatable, AnimationController baseController) { 30 | this(animatable, baseController, 1); 31 | } 32 | 33 | public E getAnimatable() { 34 | return animatable; 35 | } 36 | 37 | public AnimationController getBaseController() { 38 | return baseController; 39 | } 40 | 41 | public String getName() { 42 | return controllerName; 43 | } 44 | 45 | public void tick() { 46 | 47 | } 48 | 49 | protected double getSyncedProgress() { 50 | return 0; 51 | } 52 | 53 | public static IRawAnimation none() { 54 | return null; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/pack/AssetSynchPackFinder.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.pack; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import de.dertoaster.multihitboxlib.assetsynch.AbstractAssetEnforcementManager; 6 | import de.dertoaster.multihitboxlib.assetsynch.AssetEnforcement; 7 | import net.minecraft.ChatFormatting; 8 | import net.minecraft.SharedConstants; 9 | import net.minecraft.network.chat.Component; 10 | import net.minecraft.server.packs.PackType; 11 | import net.minecraft.server.packs.repository.Pack; 12 | import net.minecraft.server.packs.repository.Pack.Position; 13 | import net.minecraft.server.packs.repository.PackSource; 14 | import net.minecraft.server.packs.repository.RepositorySource; 15 | import net.minecraft.world.flag.FeatureFlagSet; 16 | 17 | public class AssetSynchPackFinder implements RepositorySource { 18 | 19 | public static final PackSource PACK_SOURCE = PackSource.create(name -> name.copy().withStyle(ChatFormatting.WHITE).append(" (Server-Enforced)").withStyle(ChatFormatting.GOLD), true); 20 | 21 | @Override 22 | public void loadPacks(Consumer pOnLoad) { 23 | for (AbstractAssetEnforcementManager aaem : AssetEnforcement.getRegisteredManagers()) { 24 | Pack pack = Pack.create( 25 | aaem.getId().toString(), 26 | Component.literal(aaem.getId().toString() + "(enforced assets)"), 27 | true, 28 | (s) -> aaem, 29 | new Pack.Info(Component.literal("Enforced assets for manager: " + aaem.getId().toString()), SharedConstants.getCurrentVersion().getPackVersion(PackType.CLIENT_RESOURCES), FeatureFlagSet.of()), 30 | aaem.packType(), 31 | Position.TOP, 32 | true, 33 | PACK_SOURCE 34 | ); 35 | 36 | if (pack != null) { 37 | pOnLoad.accept(pack); 38 | } 39 | } 40 | } 41 | 42 | private static final RepositorySource INSTANCE = new AssetSynchPackFinder(); 43 | 44 | public static RepositorySource instance() { 45 | return INSTANCE; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/data/minecraft/multihitboxlib/hitbox_profiles/creeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "synched-assets": { 3 | "models": [ 4 | "geckolib:geo/entity/creeper.geo.json" 5 | ], 6 | "animations": [ 7 | ], 8 | "textures": [ 9 | "geckolib:textures/entity/creeper.png" 10 | ] 11 | }, 12 | "sync-with-model": true, 13 | "part-update-steps": 3, 14 | "trust-client": true, 15 | "synched-part-update-steps": 1, 16 | "_comment": "The hitbox will synch to the specified bone's pivot point. At best create a modified model with empty bones for the hitboxes and let that be enforced", 17 | "synched-bones": [ 18 | "head" 19 | ], 20 | "main-hitbox": { 21 | "collidable": true, 22 | "canReceiveDamage": false, 23 | "size": [ 24 | 1.0, 25 | 2.0 26 | ] 27 | }, 28 | "parts": [ 29 | { 30 | "name": "feet", 31 | "collidable": true, 32 | "can-receive-damage": true, 33 | "damage-modifier": 0.5, 34 | "max-deviation-from-server": 0.125, 35 | "box": { 36 | "type": "multihitboxlib:aabb", 37 | "size": [ 38 | 0.75, 39 | 0.5 40 | ], 41 | "position": [ 42 | 0, 43 | 0, 44 | 0 45 | ], 46 | "pivot": [ 47 | 0, 48 | 0, 49 | 0 50 | ] 51 | } 52 | }, 53 | { 54 | "name": "body", 55 | "collidable": true, 56 | "can-receive-damage": true, 57 | "damage-modifier": 1.0, 58 | "box": { 59 | "type": "multihitboxlib:aabb", 60 | "size": [ 61 | 0.5, 62 | 0.75 63 | ], 64 | "position": [ 65 | 0, 66 | 0.5, 67 | 0 68 | ], 69 | "pivot": [ 70 | 0, 71 | 0, 72 | 0 73 | ] 74 | } 75 | }, 76 | { 77 | "name": "head", 78 | "collidable": true, 79 | "can-receive-damage": true, 80 | "max-deviation-from-server": 9.0, 81 | "damage-modifier": 2.0, 82 | "box": { 83 | "type": "multihitboxlib:aabb", 84 | "size": [ 85 | 0.6, 86 | 0.6 87 | ], 88 | "position": [ 89 | 0, 90 | 1.2, 91 | 0 92 | ] 93 | } 94 | } 95 | ] 96 | } -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/network/AbstractPacketHandler.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.network; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import javax.annotation.Nullable; 6 | 7 | import de.dertoaster.multihitboxlib.util.ClientOnlyMethods; 8 | import net.minecraft.network.protocol.game.ServerPacketListener; 9 | import net.minecraft.world.entity.player.Player; 10 | import net.minecraft.world.level.Level; 11 | import net.minecraftforge.network.NetworkEvent; 12 | 13 | public abstract class AbstractPacketHandler

implements IMessageHandler

{ 14 | 15 | public IMessageHandler

cast() { 16 | return (IMessageHandler

)this; 17 | } 18 | 19 | @Override 20 | public final void handlePacket(P packet, Supplier context) { 21 | context.get().enqueueWork(() -> { 22 | Player sender = null; 23 | Level world = null; 24 | if(context.get().getNetworkManager().getPacketListener() instanceof ServerPacketListener) { 25 | sender = context.get().getSender(); 26 | if (sender != null) { 27 | world = sender.level(); 28 | } 29 | } 30 | //if(context.get().getNetworkManager().getPacketListener() instanceof ClientPacketListener) { 31 | // Otherwise it MUST be the client 32 | else { 33 | sender = ClientOnlyMethods.getClientPlayer(); 34 | world = ClientOnlyMethods.getWorld(); 35 | } 36 | 37 | 38 | this.execHandlePacket(packet, context, world, sender); 39 | }); 40 | context.get().setPacketHandled(true); 41 | } 42 | 43 | /* 44 | * Params: 45 | * packet: The packet 46 | * context: Network context 47 | * world: Optional, set when player is not null or the packet is received clientside, then it is the currently opened world 48 | * player: Either the sender of the packet or the local player. Is null for packets recepted during login 49 | */ 50 | protected abstract void execHandlePacket(P packet, Supplier context, @Nullable Level world, @Nullable Player player); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/example/init/MHLibExampleEntities.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.example.init; 2 | 3 | import de.dertoaster.multihitboxlib.Constants; 4 | import de.dertoaster.multihitboxlib.MHLibMod; 5 | import de.dertoaster.multihitboxlib.example.entity.Anjanath; 6 | import de.dertoaster.multihitboxlib.example.entity.AnjanathALib; 7 | import net.minecraft.world.entity.Entity; 8 | import net.minecraft.world.entity.EntityType; 9 | import net.minecraft.world.entity.EntityType.EntityFactory; 10 | import net.minecraft.world.entity.MobCategory; 11 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 12 | import net.minecraftforge.registries.DeferredRegister; 13 | import net.minecraftforge.registries.ForgeRegistries; 14 | import net.minecraftforge.registries.RegistryObject; 15 | 16 | public class MHLibExampleEntities { 17 | 18 | public static final DeferredRegister> ENTITY_TYPES = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, Constants.MODID); 19 | 20 | public static final RegistryObject> ANJANATH = registerSized(Anjanath::new, "anjanath", 6, 5, 1); 21 | public static final RegistryObject> ANJANATH_AL = registerSized(AnjanathALib::new, "anjanath_al", 6, 5, 1); 22 | 23 | protected static RegistryObject> registerSized(EntityFactory factory, final String entityName, float width, float height, int updateInterval) { 24 | RegistryObject> result = ENTITY_TYPES.register(entityName, () -> EntityType.Builder 25 | .of(factory, MobCategory.MISC) 26 | .sized(width, height) 27 | .setTrackingRange(128) 28 | .clientTrackingRange(64) 29 | .updateInterval(updateInterval) 30 | .setShouldReceiveVelocityUpdates(true) 31 | .build(MHLibMod.prefix(entityName).toString())); 32 | 33 | return result; 34 | } 35 | 36 | public static void registerEntityTypes() 37 | { 38 | ENTITY_TYPES.register(FMLJavaModLoadingContext.get().getModEventBus()); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/server/SPacketFunctionalAnimProgress.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.server; 2 | 3 | import de.dertoaster.multihitboxlib.api.network.AbstractPacket; 4 | import net.minecraft.network.FriendlyByteBuf; 5 | 6 | public class SPacketFunctionalAnimProgress extends AbstractPacket { 7 | private final String wrappedControllerName; 8 | private final int animatableOwnerId; 9 | private final double clientUpdateTickDelta; 10 | 11 | public SPacketFunctionalAnimProgress() { 12 | this.wrappedControllerName = ""; 13 | this.animatableOwnerId = -1; 14 | this.clientUpdateTickDelta = -1; 15 | } 16 | 17 | public SPacketFunctionalAnimProgress(String wrappedControllerName, int animatableOwnerId, double clientUpdateTickDelta) { 18 | this.wrappedControllerName = wrappedControllerName; 19 | this.animatableOwnerId = animatableOwnerId; 20 | this.clientUpdateTickDelta = clientUpdateTickDelta; 21 | } 22 | 23 | @Override 24 | public Class getPacketClass() { 25 | return SPacketFunctionalAnimProgress.class; 26 | } 27 | 28 | @Override 29 | public SPacketFunctionalAnimProgress fromBytes(FriendlyByteBuf buffer) { 30 | return new SPacketFunctionalAnimProgress(buffer.readUtf(), buffer.readInt(), buffer.readDouble()); 31 | } 32 | 33 | @Override 34 | public void toBytes(SPacketFunctionalAnimProgress packet, FriendlyByteBuf buffer) { 35 | buffer.writeUtf(packet.wrappedControllerName); 36 | buffer.writeInt(packet.animatableOwnerId); 37 | buffer.writeDouble(packet.clientUpdateTickDelta); 38 | } 39 | 40 | public String getWrappedControllerName() { 41 | return wrappedControllerName; 42 | } 43 | 44 | public int getAnimatableOwnerId() { 45 | return animatableOwnerId; 46 | } 47 | 48 | public double getClientUpdateTickDelta() { 49 | return clientUpdateTickDelta; 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/mixin/minecraft/client/MixinEntityRenderDispatcher.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.mixin.minecraft.client; 2 | 3 | import org.joml.Quaternionf; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.injection.At; 6 | import org.spongepowered.asm.mixin.injection.Inject; 7 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 8 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 9 | 10 | import com.mojang.blaze3d.vertex.PoseStack; 11 | import com.mojang.blaze3d.vertex.VertexConsumer; 12 | 13 | import de.dertoaster.multihitboxlib.entity.IOrientableHitbox; 14 | import net.minecraft.client.renderer.entity.EntityRenderDispatcher; 15 | import net.minecraft.world.entity.Entity; 16 | import net.minecraftforge.entity.PartEntity; 17 | 18 | @Mixin(EntityRenderDispatcher.class) 19 | public abstract class MixinEntityRenderDispatcher { 20 | 21 | // Mixin into the second call to renderHitbox, which is used to draw the subpart hitboxes 22 | @Inject( 23 | method = "renderHitbox", 24 | at = @At( 25 | value = "INVOKE", 26 | target = "Lnet/minecraft/client/renderer/LevelRenderer;renderLineBox(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;DDDDDDFFFFFFF)V", 27 | ordinal = 2 28 | ), 29 | locals = LocalCapture.CAPTURE_FAILSOFT 30 | ) 31 | private void mixinDrawHitbox( 32 | PoseStack pPoseStack, 33 | VertexConsumer pBuffer, 34 | Entity pEntity, 35 | float pPartialTicks, 36 | // Local variables 37 | PartEntity enderdragonpart, 38 | // CallbackInfo 39 | CallbackInfo ci 40 | ) { 41 | if (enderdragonpart != null && enderdragonpart instanceof IOrientableHitbox ioh) { 42 | // Translate to center 43 | pPoseStack.translate(ioh.getCenterOffset().x, ioh.getCenterOffset().y, ioh.getCenterOffset().z); 44 | // Rotate! 45 | Quaternionf quat = new Quaternionf().rotateXYZ(ioh.getRotationX(), ioh.getRotationY(), ioh.getRotationZ()); 46 | pPoseStack.mulPose(quat); 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/server/SPacketSetMaster.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.server; 2 | 3 | import java.util.UUID; 4 | 5 | import de.dertoaster.multihitboxlib.api.IMultipartEntity; 6 | import de.dertoaster.multihitboxlib.api.network.AbstractPacket; 7 | import net.minecraft.network.FriendlyByteBuf; 8 | import net.minecraft.world.entity.Entity; 9 | 10 | public class SPacketSetMaster extends AbstractPacket { 11 | 12 | private final int entityID; 13 | private final UUID masterUUID; 14 | 15 | public > SPacketSetMaster(final T entity) { 16 | if (entity instanceof Entity entityTmp) { 17 | this.entityID = ((Entity) entity).getId(); 18 | } else { 19 | throw new IllegalStateException("entity is a instance of IMultipartEntity that is not implemented on a entity!"); 20 | } 21 | this.masterUUID = entity.getMasterUUID(); 22 | } 23 | 24 | public SPacketSetMaster() { 25 | this.entityID = -1; 26 | this.masterUUID = null; 27 | 28 | } 29 | 30 | protected SPacketSetMaster(final int id, final UUID masterID) { 31 | this.entityID = id; 32 | this.masterUUID = masterID; 33 | } 34 | 35 | @Override 36 | public Class getPacketClass() { 37 | return SPacketSetMaster.class; 38 | } 39 | 40 | @Override 41 | public SPacketSetMaster fromBytes(FriendlyByteBuf buffer) { 42 | int id = buffer.readInt(); 43 | if(buffer.readBoolean()) { 44 | return new SPacketSetMaster(id, buffer.readUUID()); 45 | } else { 46 | return new SPacketSetMaster(id, null); 47 | } 48 | } 49 | 50 | @Override 51 | public void toBytes(SPacketSetMaster packet, FriendlyByteBuf buffer) { 52 | buffer.writeInt(packet.getEntityID()); 53 | buffer.writeBoolean(packet.getMasterUUID() != null); 54 | if(packet.getMasterUUID() != null) { 55 | buffer.writeUUID(packet.getMasterUUID()); 56 | } 57 | 58 | } 59 | 60 | public UUID getMasterUUID() { 61 | return masterUUID; 62 | } 63 | 64 | public int getEntityID() { 65 | return entityID; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/client/SPacketHandlerUpdateMultipart.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.client; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import de.dertoaster.multihitboxlib.api.IMultipartEntity; 6 | import de.dertoaster.multihitboxlib.api.network.AbstractPacketHandler; 7 | import de.dertoaster.multihitboxlib.entity.MHLibPartEntity; 8 | import de.dertoaster.multihitboxlib.network.server.SPacketUpdateMultipart; 9 | import de.dertoaster.multihitboxlib.network.server.SPacketUpdateMultipart.PartDataHolder; 10 | import net.minecraft.world.entity.Entity; 11 | import net.minecraft.world.entity.player.Player; 12 | import net.minecraft.world.level.Level; 13 | import net.minecraft.world.phys.Vec3; 14 | import net.minecraftforge.entity.PartEntity; 15 | import net.minecraftforge.network.NetworkEvent.Context; 16 | 17 | public class SPacketHandlerUpdateMultipart extends AbstractPacketHandler { 18 | 19 | @Override 20 | protected void execHandlePacket(SPacketUpdateMultipart packet, Supplier context, Level world, Player player) { 21 | Entity ent = world.getEntity(packet.getId()); 22 | if (ent != null && ent.isMultipartEntity() && ent instanceof IMultipartEntity ime && ime.getHitboxProfile().isPresent()) { 23 | PartEntity[] parts = ent.getParts(); 24 | if (parts == null) 25 | return; 26 | int index = 0; 27 | for (PartEntity part : parts) { 28 | if (part instanceof MHLibPartEntity subPart) { 29 | final PartDataHolder data = packet.getData().get(index); 30 | // Give the client some freedom... 31 | if (ime.getHitboxProfile().get().trustClient()) { 32 | Vec3 partPos = subPart.position(); 33 | Vec3 serverPos = new Vec3(data.x(), data.y(), data.z()); 34 | final double dist = Math.abs(partPos.distanceToSqr(serverPos)); 35 | if (dist > subPart.getConfig().maxDeviationFromServer()) { 36 | subPart.readData(data); 37 | } 38 | } 39 | // ... Or enforce it 40 | else { 41 | subPart.readData(data); 42 | } 43 | index++; 44 | } 45 | } 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/mixin/azurelib/MixinGeoReplacedEntityRenderer.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.mixin.azurelib; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | import de.dertoaster.multihitboxlib.api.IMHLibExtendedRenderLayer; 12 | import de.dertoaster.multihitboxlib.client.azurelib.renderlayer.AzurelibBoneInformationCollectorLayer; 13 | import mod.azure.azurelib.renderer.GeoRenderer; 14 | import mod.azure.azurelib.renderer.GeoReplacedEntityRenderer; 15 | 16 | @Mixin(GeoReplacedEntityRenderer.class) 17 | public abstract class MixinGeoReplacedEntityRenderer { 18 | 19 | @SuppressWarnings({ "unchecked", "rawtypes" }) 20 | @Inject( 21 | method = "(Lnet/minecraft/client/renderer/entity/EntityRendererProvider$Context;Lmod/azure/azurelib/model/GeoModel;Lmod/azure/azurelib/core/animatable/GeoAnimatable;)V", 22 | at = @At("TAIL") 23 | ) 24 | private void mixinConstructor(CallbackInfo ci) { 25 | GeoReplacedEntityRenderer self = (GeoReplacedEntityRenderer)(Object)this; 26 | self.addRenderLayer(new AzurelibBoneInformationCollectorLayer(self)); 27 | } 28 | 29 | @Unique 30 | private void _mhlib_callLayers(final Consumer runPerLayer) { 31 | GeoRenderer self = (GeoRenderer) this; 32 | for (Object layerGeo : self.getRenderLayers()) { 33 | if (layerGeo instanceof IMHLibExtendedRenderLayer mhlibExtension) { 34 | runPerLayer.accept(mhlibExtension); 35 | } 36 | } 37 | } 38 | 39 | @Inject(method = "renderRecursively", at = @At("HEAD"), remap = false) 40 | private void mixinRenderRecursivelyStart(CallbackInfo ci) { 41 | this._mhlib_callLayers(IMHLibExtendedRenderLayer::onRenderRecursivelyStart); 42 | } 43 | 44 | @Inject(method = "renderRecursively", at = @At("TAIL"), remap = false) 45 | private void mixinRenderRecursivelyEnd(CallbackInfo ci) { 46 | this._mhlib_callLayers(IMHLibExtendedRenderLayer::onRenderRecursivelyEnd); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/EntityEventHandler.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib; 2 | 3 | import de.dertoaster.multihitboxlib.api.IMultipartEntity; 4 | import de.dertoaster.multihitboxlib.entity.hitbox.HitboxProfile; 5 | import net.minecraft.server.level.ServerPlayer; 6 | import net.minecraft.world.entity.Entity; 7 | import net.minecraft.world.entity.EntityDimensions; 8 | import net.minecraft.world.entity.LivingEntity; 9 | import net.minecraft.world.phys.Vec2; 10 | import net.minecraftforge.event.entity.EntityEvent; 11 | import net.minecraftforge.event.entity.player.PlayerEvent; 12 | import net.minecraftforge.eventbus.api.SubscribeEvent; 13 | import net.minecraftforge.fml.common.Mod.EventBusSubscriber; 14 | import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus; 15 | 16 | @EventBusSubscriber(modid = Constants.MODID, bus = Bus.FORGE) 17 | public class EntityEventHandler { 18 | 19 | @SubscribeEvent 20 | public static void onEntitySizeEvent(EntityEvent.Size event) { 21 | Entity ent = event.getEntity(); 22 | if (ent instanceof IMultipartEntity ime) { 23 | if (ime.getHitboxProfile().isPresent()) { 24 | HitboxProfile hp = ime.getHitboxProfile().get(); 25 | 26 | if (hp.mainHitboxConfig().baseSize().equals(Vec2.ZERO)) { 27 | return; 28 | } 29 | Vec2 customDims = hp.mainHitboxConfig().baseSize(); 30 | event.setNewSize(EntityDimensions.scalable(customDims.x, customDims.y), true); 31 | } 32 | } 33 | } 34 | 35 | @SubscribeEvent 36 | public static void onStartTracking(PlayerEvent.StartTracking event) { 37 | if (event.getTarget() instanceof LivingEntity && event.getTarget().isMultipartEntity()) { 38 | if (event.getTarget() instanceof IMultipartEntity ime && event.getEntity() instanceof ServerPlayer sp) { 39 | ime.mhLibOnStartTrackingEvent(sp); 40 | } 41 | } 42 | } 43 | 44 | @SubscribeEvent 45 | public static void onStopTracking(PlayerEvent.StopTracking event) { 46 | if (event.getTarget() instanceof LivingEntity && event.getTarget().isMultipartEntity()) { 47 | if (event.getTarget() instanceof IMultipartEntity ime && event.getEntity() instanceof ServerPlayer sp) { 48 | ime.mhLibOnStopTrackingEvent(sp); 49 | } 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/impl/AlibModelEnforcementManager.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.impl; 2 | 3 | import java.util.Optional; 4 | 5 | import com.google.gson.JsonObject; 6 | 7 | import de.dertoaster.multihitboxlib.Constants; 8 | import de.dertoaster.multihitboxlib.MHLibMod; 9 | import mod.azure.azurelib.cache.AzureLibCache; 10 | import mod.azure.azurelib.cache.object.BakedGeoModel; 11 | import mod.azure.azurelib.loading.json.FormatVersion; 12 | import mod.azure.azurelib.loading.json.raw.Model; 13 | import mod.azure.azurelib.loading.object.BakedModelFactory; 14 | import mod.azure.azurelib.loading.object.GeometryTree; 15 | import mod.azure.azurelib.util.JsonUtil; 16 | import net.minecraft.resources.ResourceLocation; 17 | import net.minecraft.util.GsonHelper; 18 | 19 | public class AlibModelEnforcementManager extends MHLibEnforcementManager { 20 | 21 | @Override 22 | protected boolean receiveAndLoadInternally(ResourceLocation id, byte[] data) { 23 | if (AzureLibCache.getBakedModels().containsKey(id)) 24 | MHLibMod.LOGGER.debug("Overriding azurelib model with id <" + id.toString() + ">"); 25 | 26 | Optional optModel = this.createBakedModel(data); 27 | 28 | optModel.ifPresent(rawModel -> { 29 | if (rawModel.formatVersion() != FormatVersion.V_1_12_0) { 30 | MHLibMod.LOGGER.warn("Unsupported geometry json version. Supported versions: 1.12.0, id: {}", id); 31 | } else { 32 | BakedGeoModel baked = BakedModelFactory.getForNamespace(id.getNamespace()).constructGeoModel(GeometryTree.fromModel(rawModel)); 33 | if (baked != null) { 34 | AzureLibCache.getBakedModels().put(id, baked); 35 | } 36 | } 37 | }); 38 | return optModel.isPresent() && optModel.get().formatVersion() == FormatVersion.V_1_12_0; 39 | } 40 | 41 | private Optional createBakedModel(byte[] data) { 42 | String stringData = new String(data); 43 | JsonObject jo = GsonHelper.fromJson(JsonUtil.GEO_GSON, stringData, JsonObject.class); 44 | if (jo != null) { 45 | return Optional.of(JsonUtil.GEO_GSON.fromJson(jo, Model.class)); 46 | } 47 | return Optional.empty(); 48 | } 49 | 50 | @Override 51 | public String getSubDirectoryName() { 52 | return "models/" + Constants.Dependencies.AZURELIB_MODID; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/client/SPacketHandlerFunctionalAnimProgress.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.client; 2 | 3 | import de.dertoaster.multihitboxlib.api.glibplus.IExtendedGeoAnimatableEntity; 4 | import de.dertoaster.multihitboxlib.api.glibplus.WrappedAnimationController; 5 | import de.dertoaster.multihitboxlib.api.network.AbstractPacketHandler; 6 | import de.dertoaster.multihitboxlib.network.server.SPacketFunctionalAnimProgress; 7 | import net.minecraft.client.multiplayer.ClientLevel; 8 | import net.minecraft.world.entity.Entity; 9 | import net.minecraft.world.entity.player.Player; 10 | import net.minecraft.world.level.Level; 11 | import net.minecraftforge.network.NetworkEvent; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.function.Supplier; 15 | 16 | /** 17 | * Handles S2C functional animation progress (in ticks) sync by updating functional anim progress on the client every time 18 | * it's updated on the server. This helps ensure synchronization of functional animation metadata between the client and server, especially 19 | * in cases where the server is running at less than 18 TPS/is lagging. 20 | */ 21 | public class SPacketHandlerFunctionalAnimProgress extends AbstractPacketHandler { 22 | 23 | @Override 24 | protected void execHandlePacket(SPacketFunctionalAnimProgress packet, Supplier context, @Nullable Level world, @Nullable Player player) { 25 | if (!(world instanceof ClientLevel)) return; 26 | 27 | String wrappedControllerName = packet.getWrappedControllerName(); 28 | int animatableOwnerId = packet.getAnimatableOwnerId(); 29 | double clientUpdateTickDelta = packet.getClientUpdateTickDelta(); 30 | Entity targetPotentialAnimatable = world.getEntity(animatableOwnerId); 31 | 32 | if (!(targetPotentialAnimatable instanceof IExtendedGeoAnimatableEntity) || targetPotentialAnimatable == null) return; 33 | 34 | IExtendedGeoAnimatableEntity targetAnimatable = (IExtendedGeoAnimatableEntity) targetPotentialAnimatable; 35 | WrappedAnimationController targetWrappedController = targetAnimatable.getWrappedControllerByName(wrappedControllerName); 36 | 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/client/SPacketHandlerFunctionalAnimProgressAL.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.client; 2 | 3 | import de.dertoaster.multihitboxlib.api.alibplus.IExtendedGeoAnimatableEntity; 4 | import de.dertoaster.multihitboxlib.api.alibplus.WrappedAnimationController; 5 | import de.dertoaster.multihitboxlib.api.network.AbstractPacketHandler; 6 | import de.dertoaster.multihitboxlib.network.server.SPacketFunctionalAnimProgress; 7 | import net.minecraft.client.multiplayer.ClientLevel; 8 | import net.minecraft.world.entity.Entity; 9 | import net.minecraft.world.entity.player.Player; 10 | import net.minecraft.world.level.Level; 11 | import net.minecraftforge.network.NetworkEvent; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.function.Supplier; 15 | 16 | /** 17 | * Handles S2C functional animation progress (in ticks) sync by updating functional anim progress on the client every time 18 | * it's updated on the server. This helps ensure synchronization of functional animation metadata between the client and server, especially 19 | * in cases where the server is running at less than 18 TPS/is lagging. 20 | */ 21 | public class SPacketHandlerFunctionalAnimProgressAL extends AbstractPacketHandler { 22 | 23 | @Override 24 | protected void execHandlePacket(SPacketFunctionalAnimProgress packet, Supplier context, @Nullable Level world, @Nullable Player player) { 25 | if (!(world instanceof ClientLevel)) return; 26 | 27 | String wrappedControllerName = packet.getWrappedControllerName(); 28 | int animatableOwnerId = packet.getAnimatableOwnerId(); 29 | double clientUpdateTickDelta = packet.getClientUpdateTickDelta(); 30 | Entity targetPotentialAnimatable = world.getEntity(animatableOwnerId); 31 | 32 | if (!(targetPotentialAnimatable instanceof IExtendedGeoAnimatableEntity) || targetPotentialAnimatable == null) return; 33 | 34 | IExtendedGeoAnimatableEntity targetAnimatable = (IExtendedGeoAnimatableEntity) targetPotentialAnimatable; 35 | WrappedAnimationController targetWrappedController = targetAnimatable.getWrappedControllerByName(wrappedControllerName); 36 | 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/impl/GlibModelEnforcementManager.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.impl; 2 | 3 | import java.util.Optional; 4 | 5 | import com.google.gson.JsonObject; 6 | 7 | import de.dertoaster.multihitboxlib.Constants; 8 | import de.dertoaster.multihitboxlib.MHLibMod; 9 | import net.minecraft.resources.ResourceLocation; 10 | import net.minecraft.util.GsonHelper; 11 | import software.bernie.geckolib.cache.GeckoLibCache; 12 | import software.bernie.geckolib.cache.object.BakedGeoModel; 13 | import software.bernie.geckolib.loading.json.FormatVersion; 14 | import software.bernie.geckolib.loading.json.raw.Model; 15 | import software.bernie.geckolib.loading.object.BakedModelFactory; 16 | import software.bernie.geckolib.loading.object.GeometryTree; 17 | import software.bernie.geckolib.util.JsonUtil; 18 | 19 | public class GlibModelEnforcementManager extends MHLibEnforcementManager { 20 | 21 | @Override 22 | protected boolean receiveAndLoadInternally(ResourceLocation id, byte[] data) { 23 | if (GeckoLibCache.getBakedModels().containsKey(id)) 24 | MHLibMod.LOGGER.debug("Overriding geckolib model with id <" + id.toString() + ">"); 25 | 26 | Optional optModel = this.createBakedModel(data); 27 | 28 | optModel.ifPresent(rawModel -> { 29 | if (rawModel.formatVersion() != FormatVersion.V_1_12_0) { 30 | MHLibMod.LOGGER.warn("Unsupported geometry json version. Supported versions: 1.12.0, id: {}", id); 31 | } else { 32 | BakedGeoModel baked = BakedModelFactory.getForNamespace(id.getNamespace()).constructGeoModel(GeometryTree.fromModel(rawModel)); 33 | if (baked != null) { 34 | GeckoLibCache.getBakedModels().put(id, baked); 35 | } 36 | } 37 | }); 38 | return optModel.isPresent() && optModel.get().formatVersion() == FormatVersion.V_1_12_0; 39 | } 40 | 41 | private Optional createBakedModel(byte[] data) { 42 | String stringData = new String(data); 43 | JsonObject jo = GsonHelper.fromJson(JsonUtil.GEO_GSON, stringData, JsonObject.class); 44 | if (jo != null) { 45 | return Optional.of(JsonUtil.GEO_GSON.fromJson(jo, Model.class)); 46 | } 47 | return Optional.empty(); 48 | } 49 | 50 | @Override 51 | public String getSubDirectoryName() { 52 | return "models/" + Constants.Dependencies.GECKOLIB_MODID; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/network/AbstractSPacketHandlerSyncDatapackContent.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.network; 2 | 3 | import java.util.Map; 4 | import java.util.function.Supplier; 5 | 6 | import javax.annotation.Nullable; 7 | 8 | import de.dertoaster.multihitboxlib.util.ClientOnlyMethods; 9 | import net.minecraft.network.protocol.game.ServerPacketListener; 10 | import net.minecraft.resources.ResourceLocation; 11 | import net.minecraft.world.entity.player.Player; 12 | import net.minecraft.world.level.Level; 13 | import net.minecraftforge.network.NetworkEvent; 14 | 15 | public abstract class AbstractSPacketHandlerSyncDatapackContent> implements IMessageHandler

{ 16 | 17 | public IMessageHandler

cast() { 18 | return (IMessageHandler

)this; 19 | } 20 | 21 | @Override 22 | public final void handlePacket(P packet, Supplier context) { 23 | context.get().enqueueWork(() -> { 24 | Player sender = null; 25 | Level world = null; 26 | if(context.get().getNetworkManager().getPacketListener() instanceof ServerPacketListener) { 27 | sender = context.get().getSender(); 28 | if(sender != null) { 29 | world = sender.level(); 30 | } 31 | } 32 | //if(context.get().getNetworkManager().getPacketListener() instanceof ClientPacketListener) { 33 | //Otherwise it must be client? 34 | else { 35 | sender = ClientOnlyMethods.getClientPlayer(); 36 | world = ClientOnlyMethods.getWorld(); 37 | } 38 | 39 | 40 | this.execHandlePacket(packet, context, world, sender); 41 | }); 42 | context.get().setPacketHandled(true); 43 | } 44 | 45 | /* 46 | * Params: 47 | * packet: The packet 48 | * context: Network context 49 | * world: Optional, set when player is not null or the packet is received clientside, then it is the currently opened world 50 | * player: Either the sender of the packet or the local player. Is null for packets recepted during login 51 | */ 52 | protected void execHandlePacket(P packet, Supplier context, @Nullable Level world, @Nullable Player player) { 53 | for (Map.Entry entry : packet.getData().entrySet()) { 54 | packet.consumer().accept(entry.getKey(), entry.getValue()); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/Constants.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib; 2 | 3 | import java.io.File; 4 | import java.nio.file.Path; 5 | import java.util.function.Supplier; 6 | 7 | import de.dertoaster.multihitboxlib.util.LazyLoadField; 8 | import net.minecraftforge.fml.ModList; 9 | import net.minecraftforge.fml.loading.FMLPaths; 10 | import net.minecraftforge.fml.loading.LoadingModList; 11 | 12 | public class Constants { 13 | public static final String MODID = "multihitboxlib"; 14 | public static final String NETWORK_VERSION = "1.0.1"; 15 | 16 | public static final LazyLoadField MHLIB_CONFIG_DIR = new LazyLoadField<>(() -> { 17 | final Path configDir = FMLPaths.CONFIGDIR.get(); 18 | final File result = new File(configDir.toFile(), "mhlib"); 19 | return result; 20 | }); 21 | public static final File MHLIB_SYNC_DIR = new File(MHLIB_CONFIG_DIR.get(), "_sync"); 22 | public static final File MHLIB_ASSET_DIR = new File(MHLIB_CONFIG_DIR.get(), "assetsynch"); 23 | public static final String DISABLE_EXAMPLES_PROPERTY_KEY = MODID + ".disable_examples"; 24 | 25 | public static class Dependencies { 26 | public static String GECKOLIB_MODID = "geckolib"; 27 | public static String AZURELIB_MODID = "azurelib"; 28 | 29 | protected static class ModLoadedPredicate implements Supplier { 30 | 31 | private final String MODID; 32 | 33 | public ModLoadedPredicate(final String modid) { 34 | this.MODID = modid; 35 | } 36 | 37 | @Override 38 | public Boolean get() { 39 | return isModLoaded(this.MODID); 40 | } 41 | } 42 | 43 | public static final boolean isModLoaded(final String modid) { 44 | ModList ml = ModList.get(); 45 | if (ml == null) { 46 | // try the loading modlist 47 | LoadingModList lml = LoadingModList.get(); 48 | if (lml == null) { 49 | // Odd 50 | throw new RuntimeException("unable to lookup any modlist!"); 51 | } else { 52 | // Janky, but gets the job done 53 | return lml.getModFileById(modid) != null; 54 | } 55 | } 56 | return ml.isLoaded(modid); 57 | } 58 | 59 | public static final Supplier GECKOLIB_LOADED = new ModLoadedPredicate(Constants.Dependencies.GECKOLIB_MODID); 60 | public static final Supplier AZURELIB_LOADED = new ModLoadedPredicate(Constants.Dependencies.AZURELIB_MODID); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/mixin/MHLibPlugin.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.mixin; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.Set; 6 | import java.util.function.Supplier; 7 | 8 | import org.objectweb.asm.tree.ClassNode; 9 | import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; 10 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo; 11 | 12 | import com.google.common.collect.ImmutableMap; 13 | 14 | import de.dertoaster.multihitboxlib.Constants; 15 | 16 | public class MHLibPlugin implements IMixinConfigPlugin { 17 | 18 | private static final Supplier TRUE = () -> true; 19 | 20 | private static final Map> CONDITIONS = ImmutableMap.of( 21 | // Geckolib 22 | "de.dertoaster.multihitboxlib.mixin.geckolib.MixinGeoEntityRenderer", Constants.Dependencies.GECKOLIB_LOADED, 23 | "de.dertoaster.multihitboxlib.mixin.geckolib.MixinGeoReplacedEntityRenderer", Constants.Dependencies.GECKOLIB_LOADED, 24 | "de.dertoaster.multihitboxlib.mixin.geckolib.MixinGeoRenderer", Constants.Dependencies.GECKOLIB_LOADED, 25 | // Azurelib 26 | "de.dertoaster.multihitboxlib.mixin.azurelib.MixinGeoEntityRenderer", Constants.Dependencies.AZURELIB_LOADED, 27 | "de.dertoaster.multihitboxlib.mixin.azurelib.MixinGeoReplacedEntityRenderer", Constants.Dependencies.AZURELIB_LOADED, 28 | "de.dertoaster.multihitboxlib.mixin.azurelib.MixinGeoRenderer", Constants.Dependencies.AZURELIB_LOADED 29 | ); 30 | 31 | @Override 32 | public void onLoad(String mixinPackage) { 33 | 34 | } 35 | 36 | @Override 37 | public String getRefMapperConfig() { 38 | return null; 39 | } 40 | 41 | @Override 42 | public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { 43 | return CONDITIONS.getOrDefault(mixinClassName, TRUE).get(); 44 | } 45 | 46 | @Override 47 | public void acceptTargets(Set myTargets, Set otherTargets) { 48 | 49 | } 50 | 51 | @Override 52 | public List getMixins() { 53 | return null; 54 | } 55 | 56 | @Override 57 | public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { 58 | 59 | } 60 | 61 | @Override 62 | public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { 63 | 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/ModEventHandler.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib; 2 | 3 | import de.dertoaster.multihitboxlib.api.event.server.AssetEnforcementManagerRegistrationEvent; 4 | import de.dertoaster.multihitboxlib.api.event.server.SynchAssetFinderRegistrationEvent; 5 | import de.dertoaster.multihitboxlib.assetsynch.assetfinders.HitboxProfileAssetFinder; 6 | import de.dertoaster.multihitboxlib.assetsynch.impl.AlibAnimationEnforcementManager; 7 | import de.dertoaster.multihitboxlib.assetsynch.impl.AlibModelEnforcementManager; 8 | import de.dertoaster.multihitboxlib.assetsynch.impl.GlibAnimationEnforcementManager; 9 | import de.dertoaster.multihitboxlib.assetsynch.impl.GlibModelEnforcementManager; 10 | import de.dertoaster.multihitboxlib.assetsynch.impl.TextureEnforcementManager; 11 | import net.minecraftforge.eventbus.api.SubscribeEvent; 12 | import net.minecraftforge.fml.common.Mod.EventBusSubscriber; 13 | import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus; 14 | 15 | @EventBusSubscriber(modid = Constants.MODID, bus = Bus.MOD) 16 | public class ModEventHandler { 17 | 18 | @SubscribeEvent 19 | public static void onAssetEnforcementRegistration(AssetEnforcementManagerRegistrationEvent event) { 20 | // TODO: Log 21 | // Textures 22 | if (!event.tryAdd(MHLibMod.prefixAssesEnforcementManager("textures"), new TextureEnforcementManager())) { 23 | 24 | } 25 | // Dependency specific 26 | if (Constants.Dependencies.GECKOLIB_LOADED.get()) { 27 | if (!event.tryAdd(MHLibMod.prefixAssesEnforcementManager("models/geckolib"), new GlibModelEnforcementManager())) { 28 | 29 | } 30 | if (!event.tryAdd(MHLibMod.prefixAssesEnforcementManager("animations/geckolib"), new GlibAnimationEnforcementManager())) { 31 | 32 | } 33 | } 34 | if (Constants.Dependencies.AZURELIB_LOADED.get()) { 35 | if (!event.tryAdd(MHLibMod.prefixAssesEnforcementManager("models/azurelib"), new AlibModelEnforcementManager())) { 36 | 37 | } 38 | if (!event.tryAdd(MHLibMod.prefixAssesEnforcementManager("animations/azurelib"), new AlibAnimationEnforcementManager())) { 39 | 40 | } 41 | } 42 | } 43 | 44 | @SubscribeEvent 45 | public static void onAssetFinderRegistration(SynchAssetFinderRegistrationEvent event) { 46 | if (!event.tryAdd(MHLibMod.prefixAssetFinder("hitbox_profile"), new HitboxProfileAssetFinder())); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/util/CompressionUtil.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.util; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.util.zip.DataFormatException; 6 | import java.util.zip.Deflater; 7 | import java.util.zip.Inflater; 8 | 9 | public class CompressionUtil { 10 | 11 | // Source: http://www.java2s.com/example/java-book/compressing-byte-arrays.html 12 | public static byte[] compress(byte[] input, int compressionLevel, boolean GZIPFormat) throws IOException { 13 | 14 | // Create a Deflater object to compress data 15 | Deflater compressor = new Deflater(compressionLevel, GZIPFormat); 16 | 17 | // Set the input for the compressor 18 | compressor.setInput(input); 19 | 20 | // Call the finish() method to indicate that we have 21 | // no more input for the compressor object 22 | compressor.finish(); 23 | 24 | // Compress the data 25 | ByteArrayOutputStream bao = new ByteArrayOutputStream(); 26 | byte[] readBuffer = new byte[1024]; 27 | int readCount = 0; 28 | 29 | while (!compressor.finished()) { 30 | readCount = compressor.deflate(readBuffer); 31 | if (readCount > 0) { 32 | // Write compressed data to the output stream 33 | bao.write(readBuffer, 0, readCount); 34 | } 35 | } 36 | 37 | // End the compressor 38 | compressor.end(); 39 | 40 | // Return the written bytes from output stream 41 | return bao.toByteArray(); 42 | } 43 | 44 | // Source: http://www.java2s.com/example/java-book/compressing-byte-arrays.html 45 | public static byte[] decompress(byte[] input, boolean GZIPFormat) throws IOException, DataFormatException { 46 | // Create an Inflater object to compress the data 47 | Inflater decompressor = new Inflater(GZIPFormat); 48 | 49 | // Set the input for the decompressor 50 | decompressor.setInput(input); 51 | 52 | // Decompress data 53 | ByteArrayOutputStream bao = new ByteArrayOutputStream(); 54 | byte[] readBuffer = new byte[1024]; 55 | int readCount = 0; 56 | 57 | while (!decompressor.finished()) { 58 | readCount = decompressor.inflate(readBuffer); 59 | if (readCount > 0) { 60 | // Write the data to the output stream 61 | bao.write(readBuffer, 0, readCount); 62 | } 63 | } 64 | 65 | // End the decompressor 66 | decompressor.end(); 67 | 68 | // Return the written bytes from the output stream 69 | return bao.toByteArray(); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/IMHLibFieldAccessor.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api; 2 | 3 | import de.dertoaster.multihitboxlib.entity.MHLibPartEntity; 4 | import de.dertoaster.multihitboxlib.network.client.CPacketBoneInformation; 5 | import de.dertoaster.multihitboxlib.util.BoneInformation; 6 | import net.minecraft.world.entity.LivingEntity; 7 | import net.minecraftforge.entity.PartEntity; 8 | import org.apache.commons.lang3.NotImplementedException; 9 | 10 | import java.util.Map; 11 | import java.util.Queue; 12 | import java.util.Optional; 13 | import java.util.UUID; 14 | 15 | // DO NOT IMPLEMENT THIS INTERFACE!! 16 | public interface IMHLibFieldAccessor { 17 | 18 | public default PartEntity[] _mhlibAccess_getPartArray() { 19 | throw new NotImplementedException(); 20 | } 21 | 22 | public default void _mhlibAccess_setPartArray(final PartEntity[] value) { 23 | throw new NotImplementedException(); 24 | } 25 | 26 | public default Queue _mhlibAccess_getTrackerQueue() { 27 | throw new NotImplementedException(); 28 | } 29 | 30 | public default int _mhlibAccess_getTicksSinceLastSynch() { 31 | throw new NotImplementedException(); 32 | } 33 | 34 | public default void _mhlibAccess_setTicksSinceLastSynch(int value) { 35 | throw new NotImplementedException(); 36 | } 37 | 38 | public default Map> _mhlibAccess_getPartMap() { 39 | throw new NotImplementedException(); 40 | } 41 | 42 | public default void _mhlibAccess_setPartMap(Map> value) { 43 | throw new NotImplementedException(); 44 | } 45 | 46 | public default Map _mhlibAccess_getSynchMap() { 47 | throw new NotImplementedException(); 48 | } 49 | 50 | public default UUID _mhlibAccess_getMasterUUID() { 51 | throw new NotImplementedException(); 52 | } 53 | 54 | public default void _mhlibAccess_setMasterUUID(UUID value) { 55 | throw new NotImplementedException(); 56 | } 57 | 58 | public default Optional _mlibAccess_getBoneInfoBuilder() { 59 | throw new NotImplementedException(); 60 | } 61 | 62 | public default void _mlibAccess_setBoneInfoBuilder(Optional value) { 63 | throw new NotImplementedException(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="${mod_loader}" 2 | loaderVersion="${loader_version}" 3 | license="${mod_license}" 4 | issueTrackerURL="${mod_issueTrackerURL}" 5 | 6 | [[mods]] 7 | modId="${modid}" 8 | version="${mod_version}" 9 | displayName="${mod_name}" 10 | displayURL="https://www.curseforge.com/minecraft/mc-mods/multihitboxlib" 11 | logoFile="logo.png" 12 | credits="${mod_credits}" 13 | description='''${mod_description}''' 14 | 15 | # Base 16 | [[dependencies."${modid}"]] 17 | modId="forge" 18 | mandatory=true 19 | versionRange="${forge_version_range}" 20 | ordering="NONE" 21 | side="BOTH" 22 | 23 | [[dependencies."${modid}"]] 24 | modId="minecraft" 25 | mandatory=true 26 | versionRange="${mc_version_range}" 27 | ordering="NONE" 28 | side="BOTH" 29 | 30 | # Dependencies (Optional) 31 | # COMMON 32 | [[dependencies."${modid}"]] 33 | modId="geckolib" 34 | mandatory=false 35 | versionRange="${geckolib_version_range}" 36 | ordering="BEFORE" 37 | side="BOTH" 38 | 39 | [[dependencies."${modid}"]] 40 | modId="azurelib" 41 | mandatory=false 42 | versionRange="${azurelib_version_range}" 43 | ordering="BEFORE" 44 | side="BOTH" 45 | 46 | [[dependencies."${modid}"]] 47 | modId="jei" 48 | mandatory=false 49 | versionRange="${jei_version_range}" 50 | ordering="BEFORE" 51 | side="BOTH" 52 | 53 | [[dependencies."${modid}"]] 54 | modId="jeed" 55 | mandatory=false 56 | versionRange="${jeed_version_range}" 57 | ordering="NONE" 58 | side="BOTH" 59 | 60 | [[dependencies."${modid}"]] 61 | modId="jade" 62 | mandatory=false 63 | versionRange="${jade_version_range}" 64 | ordering="NONE" 65 | side="BOTH" 66 | 67 | # CLIENT 68 | [[dependencies."${modid}"]] 69 | modId="appleskin" 70 | mandatory=false 71 | versionRange="${appleskin_version_range}" 72 | ordering="NONE" 73 | side="CLIENT" 74 | 75 | [[dependencies."${modid}"]] 76 | modId="catalogue" 77 | mandatory=false 78 | versionRange="${catalogue_version_range}" 79 | ordering="NONE" 80 | side="CLIENT" 81 | 82 | [[dependencies."${modid}"]] 83 | modId="gamemenumodoption" 84 | mandatory=false 85 | versionRange="${gmmo_version_range}" 86 | ordering="NONE" 87 | side="CLIENT" 88 | 89 | [[dependencies."${modid}"]] 90 | modId="mixinbooster" 91 | mandatory=true 92 | versionRange="${mixinbooster_version_range}" 93 | ordering="BEFORE" 94 | side="BOTH" -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/mixin/azurelib/MixinGeoEntityRenderer.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.mixin.azurelib; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | import de.dertoaster.multihitboxlib.api.IMHLibExtendedRenderLayer; 12 | import de.dertoaster.multihitboxlib.client.azurelib.renderlayer.AzurelibBoneInformationCollectorLayer; 13 | import mod.azure.azurelib.core.animatable.GeoAnimatable; 14 | import mod.azure.azurelib.renderer.GeoEntityRenderer; 15 | import mod.azure.azurelib.renderer.GeoRenderer; 16 | import net.minecraft.client.renderer.entity.EntityRenderer; 17 | import net.minecraft.client.renderer.entity.EntityRendererProvider; 18 | import net.minecraft.world.entity.Entity; 19 | 20 | @Mixin(value = GeoEntityRenderer.class, priority = Integer.MAX_VALUE) 21 | public abstract class MixinGeoEntityRenderer extends EntityRenderer implements GeoRenderer { 22 | 23 | protected MixinGeoEntityRenderer(EntityRendererProvider.Context pContext) { 24 | super(pContext); 25 | } 26 | 27 | @SuppressWarnings({ "unchecked", "rawtypes" }) 28 | @Inject( 29 | method = "(Lnet/minecraft/client/renderer/entity/EntityRendererProvider$Context;Lmod/azure/azurelib/model/GeoModel;)V", 30 | at = @At("TAIL") 31 | ) 32 | private void mixinConstructor(CallbackInfo ci) { 33 | GeoEntityRenderer self = (GeoEntityRenderer)(Object)this; 34 | self.addRenderLayer(new AzurelibBoneInformationCollectorLayer(self)); 35 | } 36 | 37 | @Unique 38 | private void _mhlib_callLayers(final Consumer runPerLayer) { 39 | GeoRenderer self = (GeoRenderer) this; 40 | for (Object layerGeo : self.getRenderLayers()) { 41 | if (layerGeo instanceof IMHLibExtendedRenderLayer mhlibExtension) { 42 | runPerLayer.accept(mhlibExtension); 43 | } 44 | } 45 | } 46 | 47 | @Inject(method = "renderRecursively", at = @At("HEAD"), remap = false) 48 | private void mixinRenderRecursivelyStart(CallbackInfo ci) { 49 | this._mhlib_callLayers(IMHLibExtendedRenderLayer::onRenderRecursivelyStart); 50 | } 51 | 52 | @Inject(method = "renderRecursively", at = @At("TAIL"), remap = false) 53 | private void mixinRenderRecursivelyEnd(CallbackInfo ci) { 54 | this._mhlib_callLayers(IMHLibExtendedRenderLayer::onRenderRecursivelyEnd); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/mixin/geckolib/MixinGeoEntityRenderer.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.mixin.geckolib; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | import de.dertoaster.multihitboxlib.api.IMHLibExtendedRenderLayer; 12 | import de.dertoaster.multihitboxlib.client.geckolib.renderlayer.GeckolibBoneInformationCollectorLayer; 13 | import net.minecraft.client.renderer.entity.EntityRenderer; 14 | import net.minecraft.client.renderer.entity.EntityRendererProvider; 15 | import net.minecraft.world.entity.Entity; 16 | import software.bernie.geckolib.core.animatable.GeoAnimatable; 17 | import software.bernie.geckolib.renderer.GeoEntityRenderer; 18 | import software.bernie.geckolib.renderer.GeoRenderer; 19 | 20 | @Mixin(value = GeoEntityRenderer.class, priority = Integer.MAX_VALUE) 21 | public abstract class MixinGeoEntityRenderer extends EntityRenderer implements GeoRenderer { 22 | 23 | protected MixinGeoEntityRenderer(EntityRendererProvider.Context pContext) { 24 | super(pContext); 25 | } 26 | 27 | @SuppressWarnings({ "unchecked", "rawtypes" }) 28 | @Inject( 29 | method = "(Lnet/minecraft/client/renderer/entity/EntityRendererProvider$Context;Lsoftware/bernie/geckolib/model/GeoModel;)V", 30 | at = @At("TAIL") 31 | ) 32 | private void mixinConstructor(CallbackInfo ci) { 33 | GeoEntityRenderer self = (GeoEntityRenderer)(Object)this; 34 | self.addRenderLayer(new GeckolibBoneInformationCollectorLayer(self)); 35 | } 36 | 37 | @Unique 38 | private void _mhlib_callLayers(final Consumer runPerLayer) { 39 | GeoRenderer self = (GeoRenderer) this; 40 | for (Object layerGeo : self.getRenderLayers()) { 41 | if (layerGeo instanceof IMHLibExtendedRenderLayer mhlibExtension) { 42 | runPerLayer.accept(mhlibExtension); 43 | } 44 | } 45 | } 46 | 47 | @Inject(method = "renderRecursively", at = @At("HEAD"), remap = false) 48 | private void mixinRenderRecursivelyStart(CallbackInfo ci) { 49 | this._mhlib_callLayers(IMHLibExtendedRenderLayer::onRenderRecursivelyStart); 50 | } 51 | 52 | @Inject(method = "renderRecursively", at = @At("TAIL"), remap = false) 53 | private void mixinRenderRecursivelyEnd(CallbackInfo ci) { 54 | this._mhlib_callLayers(IMHLibExtendedRenderLayer::onRenderRecursivelyEnd); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/alibplus/IRawAnimation.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.alibplus; 2 | 3 | import mod.azure.azurelib.core.animation.Animation; 4 | 5 | public interface IRawAnimation { 6 | 7 | /** 8 | * Gets the name of this animation instance. Should match the name inside the animation json file. 9 | * 10 | * @return The name of this animation instance. 11 | */ 12 | String getName(); 13 | 14 | /** 15 | * Gets the length of this animation instance, in ticks. 16 | * 17 | * @return The length (in ticks) of this animation instance. 18 | */ 19 | double getAnimLength(); 20 | /** 21 | * Gets the current progress (in ticks) of this animation instance. 22 | * 23 | * @return The current tick progress of this animation instance. 24 | */ 25 | double getCurAnimProgress(); 26 | /** 27 | * Gets the speed modifier (in ticks) for this animation instance, usually defaults to {@code 1}. 28 | * 29 | * @return The speed modifier (in ticks) for this animation instance. 30 | */ 31 | double getAnimSpeedModifier(); 32 | 33 | /** 34 | * Gets the animatable owner of this animation instance. 35 | * 36 | * @return The animatable owner of this animation instance. 37 | */ 38 | IExtendedGeoAnimatableEntity getAnimatableOwner(); 39 | 40 | /** 41 | * Gets the loop type of this animation instance. 42 | * 43 | * @return The loop type of this animation instance. 44 | */ 45 | Animation.LoopType getLoopType(); 46 | 47 | /** 48 | * Plays this animation instance (leniently if {@code forceAnim} is {@code false}). Leniently-played animations will 49 | * wait for any animation(s) currently playing in the owner {@link WrappedAnimationController} of this animation instance 50 | * to stop. 51 | * 52 | * @param forceAnim Whether this animation instance should take priority and override any currently playing animation(s) in the owner {@link WrappedAnimationController}. 53 | */ 54 | void playAnimation(boolean forceAnim); 55 | 56 | /** 57 | * Stops this animation instance (leniently of {@code lenientStop} is {@code true}). Leniently-stopped animations will 58 | * attempt to stop after this animation instance is done (regardless of loop type). Otherwise, it will normally/forcefully 59 | * stop this animation instance. 60 | * 61 | * @param lenientStop Whether this animation should leniently stop if its loop type is {@link Animation.LoopType#PLAY_ONCE}. 62 | */ 63 | void stopAnimation(boolean lenientStop); 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/client/TextureClientLogic.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.client; 2 | 3 | import java.io.IOException; 4 | 5 | import com.mojang.blaze3d.platform.NativeImage; 6 | 7 | import de.dertoaster.multihitboxlib.assetsynch.AbstractAssetEnforcementManager; 8 | import net.minecraft.client.Minecraft; 9 | import net.minecraft.client.renderer.texture.DynamicTexture; 10 | import net.minecraft.resources.ResourceLocation; 11 | 12 | public class TextureClientLogic { 13 | 14 | public static boolean receiveAndLoad(final AbstractAssetEnforcementManager manager, final ResourceLocation id, final byte[] data) throws IOException { 15 | NativeImage ni = null; 16 | try { 17 | ni = NativeImage.read(data); 18 | } catch (IOException e) { 19 | e.printStackTrace(); 20 | return false; 21 | } 22 | final Minecraft mc = Minecraft.getInstance(); 23 | 24 | DynamicTexture dynTex = new DynamicTexture(ni); 25 | 26 | mc.getTextureManager().register(id, dynTex); 27 | return true; 28 | 29 | //Causes the client to freeze cause it waits for the submit call 30 | // AbstractTexture loadedInternally; 31 | // 32 | // try { 33 | // loadedInternally = mc.submit(() -> mc.getTextureManager().getTexture(id)).get(); 34 | // } 35 | // catch (InterruptedException | ExecutionException e) { 36 | // throw new IOException("Failed to load original texture: " + id, e); 37 | // } 38 | // if (loadedInternally instanceof DynamicTexture dt) { 39 | // // If it is dynamic => easy, just reupload 40 | // dt.getPixels().copyFrom(ni); 41 | // dt.upload(); 42 | // } else { 43 | // // Otherwise it gets icky... 44 | // RenderSystem.assertOnRenderThreadOrInit(); 45 | // final int texId = TextureUtil.generateTextureId(); 46 | // 47 | // Optional textureBaseResource = Minecraft.getInstance().getResourceManager().getResource(id); 48 | // 49 | // Optional textureBaseMeta; 50 | // try { 51 | // textureBaseMeta = textureBaseResource.isPresent() ? textureBaseResource.get().metadata().getSection(TextureMetadataSection.SERIALIZER) : Optional.empty(); 52 | // } catch (IOException e) { 53 | // textureBaseMeta = Optional.empty(); 54 | // e.printStackTrace(); 55 | // } 56 | // boolean blur = textureBaseMeta.isPresent() && textureBaseMeta.get().isBlur(); 57 | // boolean clamp = textureBaseMeta.isPresent() && textureBaseMeta.get().isClamp(); 58 | // 59 | // TextureUtil.prepareImage(texId, 0, ni.getWidth(), ni.getHeight()); 60 | // ni.upload(0, 0, 0, 0, 0, ni.getWidth(), ni.getHeight(), blur, clamp, false, true); 61 | // } 62 | // 63 | // return true; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/glibplus/IRawAnimation.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.glibplus; 2 | 3 | import software.bernie.geckolib.core.animation.Animation; 4 | 5 | public interface IRawAnimation { 6 | 7 | /** 8 | * Gets the name of this animation instance. Should match the name inside the animation json file. 9 | * 10 | * @return The name of this animation instance. 11 | */ 12 | String getName(); 13 | 14 | /** 15 | * Gets the length of this animation instance, in ticks. 16 | * 17 | * @return The length (in ticks) of this animation instance. 18 | */ 19 | double getAnimLength(); 20 | /** 21 | * Gets the current progress (in ticks) of this animation instance. 22 | * 23 | * @return The current tick progress of this animation instance. 24 | */ 25 | double getCurAnimProgress(); 26 | /** 27 | * Gets the speed modifier (in ticks) for this animation instance, usually defaults to {@code 1}. 28 | * 29 | * @return The speed modifier (in ticks) for this animation instance. 30 | */ 31 | double getAnimSpeedModifier(); 32 | 33 | /** 34 | * Gets the animatable owner of this animation instance. 35 | * 36 | * @return The animatable owner of this animation instance. 37 | */ 38 | IExtendedGeoAnimatableEntity getAnimatableOwner(); 39 | 40 | /** 41 | * Gets the loop type of this animation instance. 42 | * 43 | * @return The loop type of this animation instance. 44 | */ 45 | Animation.LoopType getLoopType(); 46 | 47 | /** 48 | * Plays this animation instance (leniently if {@code forceAnim} is {@code false}). Leniently-played animations will 49 | * wait for any animation(s) currently playing in the owner {@link WrappedAnimationController} of this animation instance 50 | * to stop. 51 | * 52 | * @param forceAnim Whether this animation instance should take priority and override any currently playing animation(s) in the owner {@link WrappedAnimationController}. 53 | */ 54 | void playAnimation(boolean forceAnim); 55 | 56 | /** 57 | * Stops this animation instance (leniently of {@code lenientStop} is {@code true}). Leniently-stopped animations will 58 | * attempt to stop after this animation instance is done (regardless of loop type). Otherwise, it will normally/forcefully 59 | * stop this animation instance. 60 | * 61 | * @param lenientStop Whether this animation should leniently stop if its loop type is {@link software.bernie.geckolib.core.animation.Animation.LoopType#PLAY_ONCE}. 62 | */ 63 | void stopAnimation(boolean lenientStop); 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/mixin/geckolib/MixinGeoReplacedEntityRenderer.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.mixin.geckolib; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | import de.dertoaster.multihitboxlib.api.IMHLibExtendedRenderLayer; 12 | import de.dertoaster.multihitboxlib.client.geckolib.renderlayer.GeckolibBoneInformationCollectorLayer; 13 | import net.minecraft.client.renderer.entity.EntityRenderer; 14 | import net.minecraft.client.renderer.entity.EntityRendererProvider; 15 | import net.minecraft.world.entity.Entity; 16 | import software.bernie.geckolib.core.animatable.GeoAnimatable; 17 | import software.bernie.geckolib.renderer.GeoRenderer; 18 | import software.bernie.geckolib.renderer.GeoReplacedEntityRenderer; 19 | 20 | @Mixin(value = GeoReplacedEntityRenderer.class, priority = Integer.MAX_VALUE) 21 | public abstract class MixinGeoReplacedEntityRenderer extends EntityRenderer implements GeoRenderer { 22 | 23 | protected MixinGeoReplacedEntityRenderer(EntityRendererProvider.Context pContext) { 24 | super(pContext); 25 | } 26 | 27 | @SuppressWarnings({ "unchecked", "rawtypes" }) 28 | @Inject( 29 | method = "(Lnet/minecraft/client/renderer/entity/EntityRendererProvider$Context;Lsoftware/bernie/geckolib/model/GeoModel;Lsoftware/bernie/geckolib/core/animatable/GeoAnimatable;)V", 30 | at = @At("TAIL") 31 | ) 32 | private void mixinConstructor(CallbackInfo ci) { 33 | GeoReplacedEntityRenderer self = (GeoReplacedEntityRenderer)(Object)this; 34 | self.addRenderLayer(new GeckolibBoneInformationCollectorLayer(self)); 35 | } 36 | 37 | @Unique 38 | private void _mhlib_callLayers(final Consumer runPerLayer) { 39 | GeoRenderer self = (GeoRenderer) this; 40 | for (Object layerGeo : self.getRenderLayers()) { 41 | if (layerGeo instanceof IMHLibExtendedRenderLayer mhlibExtension) { 42 | runPerLayer.accept(mhlibExtension); 43 | } 44 | } 45 | } 46 | 47 | @Inject(method = "renderRecursively", at = @At("HEAD"), remap = false) 48 | private void mixinRenderRecursivelyStart(CallbackInfo ci) { 49 | this._mhlib_callLayers(IMHLibExtendedRenderLayer::onRenderRecursivelyStart); 50 | } 51 | 52 | @Inject(method = "renderRecursively", at = @At("TAIL"), remap = false) 53 | private void mixinRenderRecursivelyEnd(CallbackInfo ci) { 54 | this._mhlib_callLayers(IMHLibExtendedRenderLayer::onRenderRecursivelyEnd); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/network/AbstractSPacketCodecWrappingPacket.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api.network; 2 | 3 | import java.io.IOException; 4 | import java.util.zip.DataFormatException; 5 | import java.util.zip.Deflater; 6 | 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonParser; 9 | import com.mojang.serialization.Codec; 10 | import com.mojang.serialization.DataResult; 11 | import com.mojang.serialization.JsonOps; 12 | 13 | import de.dertoaster.multihitboxlib.util.CompressionUtil; 14 | import net.minecraft.network.FriendlyByteBuf; 15 | 16 | public abstract class AbstractSPacketCodecWrappingPacket> implements IMessage

{ 17 | 18 | protected final T data; 19 | 20 | public AbstractSPacketCodecWrappingPacket() { 21 | this.data = null; 22 | } 23 | 24 | public AbstractSPacketCodecWrappingPacket(T data) { 25 | this.data = data; 26 | } 27 | 28 | protected abstract Codec codec(); 29 | protected abstract P createPacket(DataResult dr); 30 | protected abstract P createPacket(T data); 31 | 32 | @Override 33 | public P fromBytes(FriendlyByteBuf buffer) { 34 | // Crashes the client for some odd reason 35 | if (buffer.readBoolean()) { 36 | byte[] bytes = buffer.readByteArray(); 37 | if (bytes.length > 0) { 38 | try { 39 | bytes = CompressionUtil.decompress(bytes, true); 40 | } catch (IOException e) { 41 | e.printStackTrace(); 42 | return null; 43 | } catch (DataFormatException e) { 44 | e.printStackTrace(); 45 | return null; 46 | } 47 | JsonElement je = JsonParser.parseString(new String(bytes)); 48 | DataResult dr = this.codec().parse(JsonOps.COMPRESSED, je); 49 | return this.createPacket(dr); 50 | } 51 | } 52 | return this.createPacket((T)null); 53 | //T data = buffer.readJsonWithCodec(this.codec()); 54 | //return this.createPacket(data); 55 | } 56 | 57 | @Override 58 | public void toBytes(P packet, FriendlyByteBuf buffer) { 59 | DataResult dr = packet.codec().encodeStart(JsonOps.COMPRESSED, packet.data); 60 | JsonElement je = dr.getOrThrow(false, (s) -> { 61 | 62 | }); 63 | if (je != null) { 64 | byte[] bytes = je.toString().getBytes(); 65 | try { 66 | bytes = CompressionUtil.compress(bytes, Deflater.BEST_COMPRESSION, true); 67 | buffer.writeBoolean(true); 68 | buffer.writeByteArray(bytes); 69 | } catch (IOException e) { 70 | buffer.writeBoolean(false); 71 | e.printStackTrace(); 72 | } 73 | } else { 74 | buffer.writeBoolean(false); 75 | } 76 | //buffer.writeJsonWithCodec(this.codec(), packet.getData()); 77 | } 78 | 79 | public T getData() { 80 | return this.data; 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/impl/TextureEnforcementManager.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch.impl; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import javax.annotation.Nonnull; 10 | 11 | import de.dertoaster.multihitboxlib.Constants; 12 | import de.dertoaster.multihitboxlib.assetsynch.AbstractNInOneEntriesEnforcementManager; 13 | import de.dertoaster.multihitboxlib.assetsynch.client.TextureClientLogic; 14 | import net.minecraft.resources.ResourceLocation; 15 | 16 | public class TextureEnforcementManager extends AbstractNInOneEntriesEnforcementManager { 17 | 18 | @Override 19 | protected List getRawByteEntriesFor(ResourceLocation id) { 20 | List result = new ArrayList<>(2); 21 | File location = this.getFileForId(id); 22 | if (!location.exists() || !location.isFile()) { 23 | // TODO: Scan the relevant modjar for this file and add that, but this is hacky, so watch out! Also if you found it, write the file to disk 24 | return List.of(); 25 | } 26 | try { 27 | result.add(Files.readAllBytes(location.toPath())); 28 | } catch (IOException e1) { 29 | e1.printStackTrace(); 30 | return List.of(); 31 | } 32 | File metaFile = this.getFileForId(id.withSuffix(".mcmeta")); 33 | if (metaFile.exists() && metaFile.isFile()) { 34 | byte[] metaBytes = null; 35 | try { 36 | metaBytes = Files.readAllBytes(metaFile.toPath()); 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | metaBytes = new byte[] {}; 40 | } 41 | if (metaBytes != null && metaBytes.length > 0) { 42 | result.add(metaBytes); 43 | } 44 | } 45 | 46 | return result; 47 | } 48 | 49 | @Override 50 | protected boolean loadEntry(ResourceLocation id, byte[] data, int index) { 51 | if (index != 0) { 52 | return true; 53 | } 54 | if (data == null) { 55 | return false; 56 | } 57 | if (data.length <= 0) { 58 | return false; 59 | } 60 | // Here goes nothing... 61 | try { 62 | return TextureClientLogic.receiveAndLoad(this, id, data); 63 | } catch (IOException e) { 64 | e.printStackTrace(); 65 | return false; 66 | } 67 | } 68 | 69 | @Override 70 | protected boolean writeEntryToFile(ResourceLocation id, byte[] data, int index) { 71 | ResourceLocation idToUse = id; 72 | if (index != 0) { 73 | idToUse = id.withSuffix(".mcmeta"); 74 | } 75 | File target = this.getFileForId(idToUse); 76 | return ensureFileFor(target, idToUse) && writeToFile(target, data); 77 | } 78 | 79 | @Nonnull 80 | @Override 81 | protected File createServerDirectory() { 82 | return new File(Constants.MHLIB_ASSET_DIR, this.getSubDirectoryName()); 83 | } 84 | 85 | @Nonnull 86 | @Override 87 | protected File createSynchDirectory() { 88 | return new File(Constants.MHLIB_SYNC_DIR, this.getSubDirectoryName()); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/client/azurelib/AzurelibEntityRenderEventHandler.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.client.azurelib; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import de.dertoaster.multihitboxlib.api.IMHLibExtendedRenderLayer; 6 | import de.dertoaster.multihitboxlib.client.EntityRenderEventHandlerCommonLogic; 7 | import de.dertoaster.multihitboxlib.client.IBoneInformationCollectorLayerCommonLogic; 8 | import mod.azure.azurelib.event.GeoRenderEvent; 9 | import mod.azure.azurelib.renderer.GeoEntityRenderer; 10 | import mod.azure.azurelib.renderer.GeoRenderer; 11 | import mod.azure.azurelib.renderer.layer.GeoRenderLayer; 12 | import net.minecraft.world.entity.Entity; 13 | 14 | public class AzurelibEntityRenderEventHandler extends EntityRenderEventHandlerCommonLogic { 15 | 16 | static void callLayers(GeoRenderer renderer, Consumer runPerLayer) { 17 | for(GeoRenderLayer layerGeo : renderer.getRenderLayers()) { 18 | if (layerGeo instanceof IMHLibExtendedRenderLayer mhlibExtension) { 19 | runPerLayer.accept(mhlibExtension); 20 | } 21 | } 22 | } 23 | 24 | public static void onPostRenderEntity(GeoRenderEvent.Entity.Post event) { 25 | Entity animatable = event.getEntity(); 26 | performCommonLogic(event.getPoseStack(), event.getRenderer(), event.getBufferSource(), event.getPartialTick(), event.getPackedLight(), animatable); 27 | performAlibLogic(event.getRenderer(), animatable); 28 | if (!event.getEntity().isMultipartEntity()) { 29 | return; 30 | } 31 | callLayers(event.getRenderer(), IMHLibExtendedRenderLayer::onPostRender); 32 | } 33 | 34 | public static void onPreRenderEntity(GeoRenderEvent.Entity.Pre event) { 35 | if (!event.getEntity().isMultipartEntity()) { 36 | return; 37 | } 38 | callLayers(event.getRenderer(), IMHLibExtendedRenderLayer::onPreRender); 39 | } 40 | 41 | public static void onPreRenderReplacedEntity(GeoRenderEvent.ReplacedEntity.Pre event) { 42 | if (!event.getReplacedEntity().isMultipartEntity()) { 43 | return; 44 | } 45 | callLayers(event.getRenderer(), IMHLibExtendedRenderLayer::onPreRender); 46 | } 47 | 48 | public static void onPostRenderReplacedEntity(GeoRenderEvent.ReplacedEntity.Post event) { 49 | Entity animatable = event.getReplacedEntity(); 50 | performCommonLogic(event.getPoseStack(), event.getRenderer(), event.getBufferSource(), event.getPartialTick(), event.getPackedLight(), animatable); 51 | if (!animatable.isMultipartEntity()) { 52 | return; 53 | } 54 | callLayers(event.getRenderer(), IMHLibExtendedRenderLayer::onPostRender); 55 | } 56 | 57 | private static void performAlibLogic(GeoEntityRenderer geoRenderer, Entity entitybeingRenderer) { 58 | for(GeoRenderLayer gle : geoRenderer.getRenderLayers()) { 59 | if(gle instanceof IBoneInformationCollectorLayerCommonLogic bicl) { 60 | bicl.onPostRender(entitybeingRenderer); 61 | } 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/client/geckolib/GeckolibEntityRenderEventHandler.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.client.geckolib; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import de.dertoaster.multihitboxlib.api.IMHLibExtendedRenderLayer; 6 | import de.dertoaster.multihitboxlib.client.EntityRenderEventHandlerCommonLogic; 7 | import de.dertoaster.multihitboxlib.client.IBoneInformationCollectorLayerCommonLogic; 8 | import net.minecraft.world.entity.Entity; 9 | import software.bernie.geckolib.event.GeoRenderEvent; 10 | import software.bernie.geckolib.renderer.GeoEntityRenderer; 11 | import software.bernie.geckolib.renderer.GeoRenderer; 12 | import software.bernie.geckolib.renderer.layer.GeoRenderLayer; 13 | 14 | public class GeckolibEntityRenderEventHandler extends EntityRenderEventHandlerCommonLogic { 15 | 16 | static void callLayers(GeoRenderer renderer, Consumer runPerLayer) { 17 | for(GeoRenderLayer layerGeo : renderer.getRenderLayers()) { 18 | if (layerGeo instanceof IMHLibExtendedRenderLayer mhlibExtension) { 19 | runPerLayer.accept(mhlibExtension); 20 | } 21 | } 22 | } 23 | 24 | public static void onPostRenderEntity(GeoRenderEvent.Entity.Post event) { 25 | Entity animatable = event.getEntity(); 26 | performCommonLogic(event.getPoseStack(), event.getRenderer(), event.getBufferSource(), event.getPartialTick(), event.getPackedLight(), animatable); 27 | performGlibLogic(event.getRenderer(), animatable); 28 | if (!event.getEntity().isMultipartEntity()) { 29 | return; 30 | } 31 | callLayers(event.getRenderer(), IMHLibExtendedRenderLayer::onPostRender); 32 | } 33 | 34 | public static void onPreRenderEntity(GeoRenderEvent.Entity.Pre event) { 35 | if (!event.getEntity().isMultipartEntity()) { 36 | return; 37 | } 38 | callLayers(event.getRenderer(), IMHLibExtendedRenderLayer::onPreRender); 39 | } 40 | 41 | public static void onPreRenderReplacedEntity(GeoRenderEvent.ReplacedEntity.Pre event) { 42 | if (!event.getReplacedEntity().isMultipartEntity()) { 43 | return; 44 | } 45 | callLayers(event.getRenderer(), IMHLibExtendedRenderLayer::onPreRender); 46 | } 47 | 48 | public static void onPostRenderReplacedEntity(GeoRenderEvent.ReplacedEntity.Post event) { 49 | Entity animatable = event.getReplacedEntity(); 50 | performCommonLogic(event.getPoseStack(), event.getRenderer(), event.getBufferSource(), event.getPartialTick(), event.getPackedLight(), animatable); 51 | if (!animatable.isMultipartEntity()) { 52 | return; 53 | } 54 | callLayers(event.getRenderer(), IMHLibExtendedRenderLayer::onPostRender); 55 | } 56 | 57 | private static void performGlibLogic(GeoEntityRenderer geoRenderer, Entity entitybeingRenderer) { 58 | for(GeoRenderLayer gle : geoRenderer.getRenderLayers()) { 59 | if(gle instanceof IBoneInformationCollectorLayerCommonLogic bicl) { 60 | bicl.onPostRender(entitybeingRenderer); 61 | } 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx6400m" "-Xms6400m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/client/IBoneInformationCollectorLayerCommonLogic.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.client; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.mojang.blaze3d.vertex.VertexConsumer; 5 | 6 | import de.dertoaster.multihitboxlib.api.IMultipartEntity; 7 | import de.dertoaster.multihitboxlib.entity.MHLibPartEntity; 8 | import de.dertoaster.multihitboxlib.entity.hitbox.HitboxProfile; 9 | import net.minecraft.client.renderer.MultiBufferSource; 10 | import net.minecraft.client.renderer.RenderType; 11 | import net.minecraft.world.entity.Entity; 12 | import net.minecraft.world.entity.LivingEntity; 13 | import net.minecraft.world.phys.Vec3; 14 | 15 | import java.util.Optional; 16 | 17 | public interface IBoneInformationCollectorLayerCommonLogic { 18 | 19 | public int getCurrentTick(); 20 | public void setCurrentTick(int tick); 21 | 22 | public void calcScales(T bone); 23 | public void calcRotations(T bone); 24 | public String getBoneName(T bone); 25 | public Vec3 getBoneWorldPosition(T bone); 26 | public boolean isBoneHidden(T bone); 27 | 28 | public Vec3 getScaleVector(); 29 | public void setScales(int x, int y, int z); 30 | 31 | public Vec3 getRotationVector(); 32 | public void setRotations(int x, int y, int z); 33 | 34 | public default void onRenderBone(PoseStack poseStack, Entity entity, T bone, RenderType renderType, MultiBufferSource bufferSource, VertexConsumer buffer, float partialTick, int packedLight, int packedOverlay) { 35 | // Only collect once per tick! 36 | if (entity != null && entity.isMultipartEntity() && entity instanceof IMultipartEntity ime && ime.getHitboxProfile().isPresent()) { 37 | HitboxProfile hitboxProfile = ime.getHitboxProfile().get(); 38 | final Vec3 worldPos = this.getBoneWorldPosition(bone); 39 | this.calcScales(bone); 40 | this.calcRotations(bone); 41 | 42 | if (hitboxProfile.synchedBones().contains(this.getBoneName(bone))) { 43 | if (hitboxProfile.syncToModel()) { 44 | if (this.getCurrentTick() == entity.tickCount || this.getCurrentTick() < 0) { 45 | ime.tryAddBoneInformation(this.getBoneName(bone), this.isBoneHidden(bone), worldPos, this.getScaleVector(), this.getRotationVector()); 46 | //System.out.println("RenderRecursively: " + worldPos.toString()); 47 | //ime.getPartByName(bone.getName()).get().setPos(worldPos); 48 | } 49 | } 50 | // After we collected stuff, we set the position directly if we trust the client... 51 | // Unsafe but honestly, mixins are a thing. Nobody can stop anyone else from installing a clientside mod that moves all hitboxes out of place... 52 | if (hitboxProfile.trustClient()) { 53 | Optional> optPart = ime.getPartByName(this.getBoneName(bone)); 54 | if (optPart.isPresent()) { 55 | MHLibPartEntity part = optPart.get(); 56 | part.applyInformation(worldPos, this.getScaleVector(), this.getRotationVector(), this.isBoneHidden(bone)); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | public default void onPostRender(Entity animatable) { 64 | if (!(animatable instanceof LivingEntity le)) { 65 | return; 66 | } 67 | 68 | if (this.getCurrentTick() == le.tickCount || this.getCurrentTick() < 0) { 69 | this.setCurrentTick(le.tickCount +1); 70 | } 71 | 72 | this.setScales(1, 1, 1); 73 | this.setRotations(0, 0, 0); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/client/EntityRenderEventHandlerCommonLogic.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.client; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | 5 | import de.dertoaster.multihitboxlib.Constants; 6 | import de.dertoaster.multihitboxlib.api.IMultipartEntity; 7 | import de.dertoaster.multihitboxlib.client.azurelib.AzurelibEntityRenderEventHandler; 8 | import de.dertoaster.multihitboxlib.client.geckolib.GeckolibEntityRenderEventHandler; 9 | import de.dertoaster.multihitboxlib.entity.MHLibPartEntity; 10 | import de.dertoaster.multihitboxlib.mixin.accessor.AccessorEntityRenderer; 11 | import net.minecraft.client.renderer.MultiBufferSource; 12 | import net.minecraft.client.renderer.entity.EntityRenderer; 13 | import net.minecraft.util.Mth; 14 | import net.minecraft.world.entity.Entity; 15 | import net.minecraft.world.entity.LivingEntity; 16 | import net.minecraft.world.phys.Vec3; 17 | import net.minecraftforge.entity.PartEntity; 18 | import net.minecraftforge.eventbus.api.IEventBus; 19 | 20 | public abstract class EntityRenderEventHandlerCommonLogic { 21 | 22 | public static void registerRelevantEventListeners(final IEventBus bus) { 23 | if (Constants.Dependencies.isModLoaded(Constants.Dependencies.GECKOLIB_MODID)) { 24 | bus.addListener(GeckolibEntityRenderEventHandler::onPostRenderEntity); 25 | bus.addListener(GeckolibEntityRenderEventHandler::onPostRenderReplacedEntity); 26 | bus.addListener(GeckolibEntityRenderEventHandler::onPreRenderEntity); 27 | bus.addListener(GeckolibEntityRenderEventHandler::onPreRenderReplacedEntity); 28 | } 29 | if (Constants.Dependencies.isModLoaded(Constants.Dependencies.AZURELIB_MODID)) { 30 | bus.addListener(AzurelibEntityRenderEventHandler::onPostRenderEntity); 31 | bus.addListener(AzurelibEntityRenderEventHandler::onPostRenderReplacedEntity); 32 | bus.addListener(AzurelibEntityRenderEventHandler::onPreRenderEntity); 33 | bus.addListener(AzurelibEntityRenderEventHandler::onPreRenderReplacedEntity); 34 | } 35 | } 36 | 37 | @SuppressWarnings("unchecked") 38 | protected static void performCommonLogic(PoseStack poseStack, EntityRenderer entityRenderer, MultiBufferSource bufferSource, float partialTick, int packedLight, Entity entitybeingRenderer) { 39 | if (!(entitybeingRenderer instanceof LivingEntity le)) { 40 | return; 41 | } 42 | if (le.isMultipartEntity() && entitybeingRenderer instanceof IMultipartEntity ime && le.getParts() != null && le.getParts().length > 0) { 43 | for(PartEntity part : le.getParts()) { 44 | if(part instanceof MHLibPartEntity mhlpe) { 45 | if (mhlpe.hasCustomRenderer() && mhlpe.isPartEnabled()) { 46 | EntityRenderer> renderer = MHLibClient.getRendererFor(mhlpe, ((AccessorEntityRenderer)entityRenderer).getEntityRenderDispatcher()); 47 | if (renderer == null) { 48 | continue; 49 | } 50 | 51 | float f = Mth.lerp(partialTick, mhlpe.yRotO, mhlpe.getYRot()); 52 | 53 | poseStack.pushPose(); 54 | 55 | Vec3 translate = mhlpe.position().subtract(le.position()); 56 | poseStack.translate(translate.x(), translate.y(), translate.z()); 57 | 58 | ((EntityRenderer>) renderer).render(mhlpe, f, partialTick, poseStack, bufferSource, packedLight); 59 | 60 | poseStack.popPose(); 61 | } else { 62 | continue; 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/client/MHLibClient.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.client; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Map.Entry; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | import java.util.function.Function; 8 | 9 | import de.dertoaster.multihitboxlib.Constants; 10 | import de.dertoaster.multihitboxlib.api.event.client.PartRendererRegistrationEvent; 11 | import de.dertoaster.multihitboxlib.entity.MHLibPartEntity; 12 | import net.minecraft.client.renderer.entity.EntityRenderDispatcher; 13 | import net.minecraft.client.renderer.entity.EntityRenderer; 14 | import net.minecraftforge.api.distmarker.Dist; 15 | import net.minecraftforge.common.MinecraftForge; 16 | import net.minecraftforge.eventbus.api.SubscribeEvent; 17 | import net.minecraftforge.fml.common.Mod; 18 | import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus; 19 | import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; 20 | 21 | @Mod.EventBusSubscriber(modid = Constants.MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) 22 | public class MHLibClient { 23 | 24 | protected static final Map>, Function>>> ENTITY_PART_RENDERER_PRODUCERS = new ConcurrentHashMap<>(); 25 | protected static final Map>, EntityRenderer>> ENTITY_PART_RENDERERS = new ConcurrentHashMap<>(); 26 | 27 | protected static void registerEntityPartRenderer(final Class> partClass, Function>> rendererFactory) { 28 | ENTITY_PART_RENDERER_PRODUCERS.put(partClass, rendererFactory); 29 | } 30 | 31 | @SuppressWarnings("unchecked") 32 | public static >, P extends MHLibPartEntity> EntityRenderer> getRendererFor(MHLibPartEntity cpe, EntityRenderDispatcher entityRenderDispatcher) { 33 | return ENTITY_PART_RENDERERS.computeIfAbsent((Class>) cpe.getClass(), partClass -> { 34 | Function>> constructor = null; 35 | for(Entry>, Function>>> entry : ENTITY_PART_RENDERER_PRODUCERS.entrySet()) { 36 | if(entry.getKey().equals(partClass)) { 37 | constructor = entry.getValue(); 38 | break; 39 | } else if(partClass.isAssignableFrom(entry.getKey())) { 40 | constructor = entry.getValue(); 41 | } 42 | } 43 | if(constructor != null) { 44 | return constructor.apply(entityRenderDispatcher); 45 | } 46 | return null; 47 | }); 48 | } 49 | 50 | @SubscribeEvent 51 | public static void onClientSetup(FMLClientSetupEvent event) { 52 | EntityRenderEventHandlerCommonLogic.registerRelevantEventListeners(MinecraftForge.EVENT_BUS); 53 | 54 | // Fire part renderer event and collect 55 | final Map>, Function>>> map = new HashMap<>(); 56 | PartRendererRegistrationEvent registrationEvent = new PartRendererRegistrationEvent(map); 57 | Bus.MOD.bus().get().post(registrationEvent); 58 | if (map != null) { 59 | map.entrySet().forEach(entry -> registerEntityPartRenderer(entry.getKey(), entry.getValue())); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/api/DatapackRegistry.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.api; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Optional; 6 | import java.util.stream.Collectors; 7 | 8 | import javax.annotation.Nullable; 9 | 10 | import com.mojang.serialization.Codec; 11 | 12 | import de.dertoaster.multihitboxlib.util.LazyLoadField; 13 | import net.minecraft.core.Holder; 14 | import net.minecraft.core.Registry; 15 | import net.minecraft.core.RegistryAccess; 16 | import net.minecraft.resources.RegistryFileCodec; 17 | import net.minecraft.resources.ResourceKey; 18 | import net.minecraft.resources.ResourceLocation; 19 | import net.minecraftforge.registries.DataPackRegistryEvent.NewRegistry; 20 | 21 | public class DatapackRegistry { 22 | 23 | protected final ResourceKey> registryKey; 24 | protected final Codec objectCodec; 25 | protected final Codec> registryCodec; 26 | 27 | protected final LazyLoadField> lazyLoadByNameCodec = new LazyLoadField<>(this::createByNameCodec); 28 | 29 | public DatapackRegistry(final ResourceLocation id, final Codec codec) { 30 | this(ResourceKey.createRegistryKey(id), codec); 31 | } 32 | 33 | public DatapackRegistry(final ResourceKey> resourceKey, final Codec codec) { 34 | this(resourceKey, codec, RegistryFileCodec.create(resourceKey, codec)); 35 | } 36 | 37 | public DatapackRegistry(final ResourceKey> resourceKey, final Codec codec, final Codec> registryCodec) { 38 | super(); 39 | this.registryCodec = registryCodec; 40 | this.objectCodec = codec; 41 | this.registryKey = resourceKey; 42 | } 43 | 44 | public void registerSynchable(NewRegistry registryEvent) { 45 | register(registryEvent, true); 46 | } 47 | 48 | public void register(NewRegistry registryEvent) { 49 | register(registryEvent, false); 50 | } 51 | 52 | public void register(NewRegistry registryEvent, boolean synchable) { 53 | if (synchable) { 54 | registryEvent.dataPackRegistry(registryKey, objectCodec, objectCodec); 55 | } else { 56 | registryEvent.dataPackRegistry(registryKey, objectCodec); 57 | } 58 | } 59 | 60 | @Nullable 61 | public T get(ResourceLocation id, RegistryAccess registryAccess) { 62 | Optional> optRegistry = this.registry(registryAccess); 63 | if (optRegistry.isEmpty()) { 64 | return null; 65 | } 66 | return optRegistry.get().get(id); 67 | } 68 | 69 | public Optional> registry(RegistryAccess registryAccess) { 70 | return registryAccess.registry(this.registryKey()); 71 | } 72 | 73 | public List values(RegistryAccess registryAccess) { 74 | Optional> optRegistry = this.registry(registryAccess); 75 | if (optRegistry.isEmpty()) { 76 | return Collections.emptyList(); 77 | } 78 | return optRegistry.get().stream().collect(Collectors.toList()); 79 | } 80 | 81 | public ResourceKey> registryKey() { 82 | return this.registryKey; 83 | } 84 | 85 | public Codec objectCodec() { 86 | return this.objectCodec; 87 | } 88 | 89 | public Codec byNameCodec() { 90 | return this.lazyLoadByNameCodec.get(); 91 | } 92 | 93 | public Codec> registryCodec() { 94 | return this.registryCodec; 95 | } 96 | 97 | protected Codec createByNameCodec() { 98 | return createByNameCodec(this); 99 | } 100 | 101 | protected static Codec createByNameCodec(DatapackRegistry registry) { 102 | return registry.registryCodec().xmap(Holder::get, Holder::direct); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/assetsynch/AbstractNInOneEntriesEnforcementManager.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.assetsynch; 2 | 3 | import java.io.IOException; 4 | import java.io.Serializable; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import java.util.zip.Deflater; 10 | 11 | import de.dertoaster.multihitboxlib.util.CompressionUtil; 12 | import io.netty.buffer.ByteBuf; 13 | import io.netty.buffer.Unpooled; 14 | import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; 15 | import net.minecraft.resources.ResourceLocation; 16 | 17 | public abstract class AbstractNInOneEntriesEnforcementManager extends AbstractAssetEnforcementManager { 18 | 19 | private volatile Map DESERIALIZED_PAIRS = new Object2ObjectArrayMap<>(); 20 | 21 | public static class ByteEntryContainer extends ArrayList implements Serializable { 22 | private static final long serialVersionUID = -7300641348899040116L; 23 | 24 | public ByteEntryContainer() { 25 | super(1); 26 | } 27 | 28 | public ByteEntryContainer(int slots) { 29 | super(slots); 30 | } 31 | 32 | public byte[] serialize() { 33 | int length = 0; 34 | for (byte[] entry : this) { 35 | length += entry.length; 36 | } 37 | ByteBuf bb = Unpooled.buffer(length); 38 | bb.writeInt(this.size()); 39 | this.forEach(entry -> { 40 | bb.writeInt(entry.length); 41 | bb.writeBytes(entry); 42 | }); 43 | 44 | return bb.array(); 45 | } 46 | 47 | public static ByteEntryContainer deserialize(final byte[] bytes) { 48 | ByteBuf bb = Unpooled.copiedBuffer(bytes); 49 | int length = bb.readInt(); 50 | ByteEntryContainer result = new ByteEntryContainer(length); 51 | for (int i = 0; i < length; i++) { 52 | byte[] entry = new byte[bb.readInt()]; 53 | bb.readBytes(entry); 54 | result.add(entry); 55 | } 56 | return result; 57 | } 58 | } 59 | 60 | protected abstract List getRawByteEntriesFor(ResourceLocation id); 61 | 62 | @Override 63 | protected Optional encodeData(ResourceLocation id) { 64 | List relevantEntries = this.getRawByteEntriesFor(id); 65 | ByteEntryContainer container = new ByteEntryContainer(); 66 | if (relevantEntries.isEmpty()) { 67 | return Optional.empty(); 68 | } 69 | container.addAll(relevantEntries); 70 | byte[] result = container.serialize(); 71 | byte[] payload = null; 72 | try { 73 | payload = CompressionUtil.compress(result, Deflater.BEST_COMPRESSION, true); 74 | } catch(IOException e) { 75 | e.printStackTrace(); 76 | payload = null; 77 | } 78 | return Optional.ofNullable(payload); 79 | } 80 | 81 | protected abstract boolean loadEntry(final ResourceLocation id, byte[] data, int index); 82 | 83 | @Override 84 | protected boolean receiveAndLoadInternally(ResourceLocation id, byte[] data) { 85 | ByteEntryContainer container = ByteEntryContainer.deserialize(data); 86 | DESERIALIZED_PAIRS.put(id, container); 87 | if (container == null || container.size() <= 0) { 88 | return false; 89 | } 90 | boolean result = true; 91 | for (int i = 0; i < container.size(); i++) { 92 | result &= this.loadEntry(id, container.get(i), i); 93 | } 94 | return result; 95 | } 96 | 97 | protected abstract boolean writeEntryToFile(final ResourceLocation id, byte[] data, int index); 98 | 99 | @Override 100 | protected boolean writeFile(ResourceLocation id, byte[] data) { 101 | ByteEntryContainer container = DESERIALIZED_PAIRS.getOrDefault(id, null); 102 | if (container == null) { 103 | container = ByteEntryContainer.deserialize(data); 104 | } 105 | if (container == null) { 106 | return false; 107 | } 108 | boolean result = true; 109 | for (int i = 0; i < container.size(); i++) { 110 | result &= this.writeEntryToFile(id, container.get(i), i); 111 | } 112 | 113 | return result; 114 | } 115 | 116 | @Override 117 | public String getSubDirectoryName() { 118 | return "textures"; 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/server/SPacketUpdateMultipart.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.server; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import de.dertoaster.multihitboxlib.api.network.AbstractPacket; 7 | import de.dertoaster.multihitboxlib.entity.MHLibPartEntity; 8 | import net.minecraft.network.FriendlyByteBuf; 9 | import net.minecraft.network.syncher.SynchedEntityData; 10 | import net.minecraft.world.entity.Entity; 11 | import net.minecraftforge.entity.PartEntity; 12 | 13 | public class SPacketUpdateMultipart extends AbstractPacket { 14 | 15 | private int id; 16 | private Entity entity; 17 | private int len; 18 | private final List data = new ArrayList<>(); 19 | 20 | public SPacketUpdateMultipart() { 21 | // Nothing to do here... 22 | } 23 | 24 | public SPacketUpdateMultipart(Entity entity) { 25 | this.entity = entity; 26 | } 27 | 28 | @Override 29 | public Class getPacketClass() { 30 | return SPacketUpdateMultipart.class; 31 | } 32 | 33 | @Override 34 | public SPacketUpdateMultipart fromBytes(FriendlyByteBuf buffer) { 35 | SPacketUpdateMultipart result = new SPacketUpdateMultipart(); 36 | result.id = buffer.readInt(); 37 | result.len = buffer.readInt(); 38 | for (int i = 0; i < result.len; i++) { 39 | if (buffer.readBoolean()) { 40 | result.data.add(PartDataHolder.decode(buffer)); 41 | } 42 | } 43 | return result; 44 | } 45 | 46 | @Override 47 | public void toBytes(SPacketUpdateMultipart packet, FriendlyByteBuf buffer) { 48 | buffer.writeInt(packet.entity.getId()); 49 | PartEntity[] parts = packet.entity.getParts(); 50 | // We assume the client and server part arrays are identical, else everything will crash and burn. Don't even bother handling it. 51 | if (parts != null) { 52 | buffer.writeInt(parts.length); 53 | for (PartEntity part : parts) { 54 | if (part instanceof MHLibPartEntity subPart) { 55 | buffer.writeBoolean(true); 56 | subPart.writeData().encode(buffer); 57 | } else { 58 | buffer.writeBoolean(false); 59 | } 60 | } 61 | } else { 62 | buffer.writeInt(0); 63 | } 64 | 65 | } 66 | 67 | public int getId() { 68 | return id; 69 | } 70 | 71 | public int getLen() { 72 | return len; 73 | } 74 | 75 | public List getData() { 76 | return data; 77 | } 78 | 79 | 80 | // Copied from https://github.com/TeamTwilight/twilightforest/blob/aa59de8ff2e9f84fe36d3da595e2cab53d4695af/src/main/java/twilightforest/network/UpdateTFMultipartPacket.java#L16 81 | public record PartDataHolder(double x, double y, double z, float yRot, float xRot, float width, float height, boolean fixed, boolean dirty, List> data) { 82 | 83 | public void encode(FriendlyByteBuf buffer) { 84 | buffer.writeDouble(this.x); 85 | buffer.writeDouble(this.y); 86 | buffer.writeDouble(this.z); 87 | buffer.writeFloat(this.yRot); 88 | buffer.writeFloat(this.xRot); 89 | buffer.writeFloat(this.width); 90 | buffer.writeFloat(this.height); 91 | buffer.writeBoolean(this.fixed); 92 | buffer.writeBoolean(this.dirty); 93 | if (this.dirty) { 94 | for (SynchedEntityData.DataValue datavalue : this.data) { 95 | datavalue.write(buffer); 96 | } 97 | 98 | buffer.writeByte(255); 99 | } 100 | } 101 | 102 | static PartDataHolder decode(FriendlyByteBuf buffer) { 103 | boolean dirty; 104 | return new PartDataHolder(buffer.readDouble(), buffer.readDouble(), buffer.readDouble(), buffer.readFloat(), buffer.readFloat(), buffer.readFloat(), buffer.readFloat(), buffer.readBoolean(), dirty = buffer.readBoolean(), dirty ? unpack( 105 | buffer) : null); 106 | } 107 | 108 | private static List> unpack(FriendlyByteBuf buf) { 109 | List> list = new ArrayList<>(); 110 | 111 | int i; 112 | while ((i = buf.readUnsignedByte()) != 255) { 113 | list.add(SynchedEntityData.DataValue.read(buf, i)); 114 | } 115 | 116 | return list; 117 | } 118 | 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/MHLibMod.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.util.Locale; 7 | 8 | import org.slf4j.Logger; 9 | 10 | import com.mojang.logging.LogUtils; 11 | 12 | import de.dertoaster.multihitboxlib.assetsynch.AssetEnforcement; 13 | import de.dertoaster.multihitboxlib.init.MHLibPackets; 14 | import net.minecraft.resources.ResourceLocation; 15 | import net.minecraftforge.common.MinecraftForge; 16 | import net.minecraftforge.event.server.ServerStartingEvent; 17 | import net.minecraftforge.eventbus.api.IEventBus; 18 | import net.minecraftforge.eventbus.api.SubscribeEvent; 19 | import net.minecraftforge.fml.common.Mod; 20 | import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; 21 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 22 | import net.minecraftforge.fml.loading.FMLEnvironment; 23 | import software.bernie.example.GeckoLibMod; 24 | 25 | // The value here should match an entry in the META-INF/mods.toml file 26 | @Mod(Constants.MODID) 27 | public class MHLibMod { 28 | public static final Logger LOGGER = LogUtils.getLogger(); 29 | 30 | public MHLibMod() { 31 | IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); 32 | 33 | // Register the commonSetup method for modloading 34 | modEventBus.addListener(this::commonSetup); 35 | 36 | // Register ourselves for server and other game events we are interested in 37 | MinecraftForge.EVENT_BUS.register(this); 38 | 39 | // Now, initialize all our folders 40 | initializeConfigDirectories(); 41 | 42 | if (shouldRegisterExamples()) { 43 | //MHLibExampleEntities.registerEntityTypes(); 44 | } 45 | } 46 | 47 | private static void initializeConfigDirectories() { 48 | final File mainDir = Constants.MHLIB_CONFIG_DIR.get(); 49 | try { 50 | checkAndCreateFolder(mainDir); 51 | checkAndCreateFolder(Constants.MHLIB_ASSET_DIR); 52 | checkAndCreateFolder(Constants.MHLIB_SYNC_DIR); 53 | 54 | // Hide the synch dir 55 | Files.setAttribute(Constants.MHLIB_SYNC_DIR.toPath(), "dos:hidden", true); 56 | } catch (IOException e) { 57 | e.printStackTrace(); 58 | // TODO: Log 59 | } 60 | } 61 | 62 | public static void checkAndCreateFolder(File directory) throws IOException { 63 | if (!directory.exists()) { 64 | if (!directory.mkdirs()) { 65 | throw new IOException("Unable to create directory <" + directory.getAbsolutePath() + "!"); 66 | } 67 | } else if (!directory.isDirectory()) { 68 | if (directory.delete()) { 69 | checkAndCreateFolder(directory); 70 | } else { 71 | throw new IOException("Directory <" + directory.getAbsolutePath() + "> is a file and could not be deleted!"); 72 | } 73 | } 74 | } 75 | 76 | private void commonSetup(final FMLCommonSetupEvent event) { 77 | MHLibPackets.init(); 78 | 79 | // Throws registration event and registers all asset enforcers 80 | AssetEnforcement.init(); 81 | } 82 | 83 | // You can use SubscribeEvent and let the Event Bus discover methods to call 84 | @SubscribeEvent 85 | public void onServerStarting(ServerStartingEvent event) { 86 | } 87 | 88 | public static ResourceLocation prefixAssesEnforcementManager(String string) { 89 | return prefix("asset_manager/" + string); 90 | } 91 | 92 | public static ResourceLocation prefixAssetFinder(String string) { 93 | return prefix("asset_finder/" + string); 94 | } 95 | 96 | public static ResourceLocation prefix(String path) { 97 | return new ResourceLocation(Constants.MODID, path.toLowerCase(Locale.ROOT)); 98 | } 99 | 100 | /** 101 | * By default, GeckoLib will register and activate several example entities, 102 | * items, and blocks when in dev.
103 | * These examples are not present when in a production environment 104 | * (normal players).
105 | * This can be disabled by setting the 106 | * {@link GeckoLibMod#DISABLE_EXAMPLES_PROPERTY_KEY} to false in your run args 107 | */ 108 | public static boolean shouldRegisterExamples() { 109 | return false && !FMLEnvironment.production && !Boolean.getBoolean(Constants.DISABLE_EXAMPLES_PROPERTY_KEY); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multihitbox-Lib 2 | Library for advanced multihitbox mobs. Can sync hitboxes to geckolib bones 3 | 4 | The goal is to simplify the process of creating multipart entities. Also it allows synching hitboxes to geckolib animations! 5 | 6 | # Downloads 7 | - [Curseforge](https://www.curseforge.com/minecraft/mc-mods/multihitboxlib) 8 | - [Modrinth](https://modrinth.com/mod/multihitboxlib) 9 | 10 | # Terms of use 11 | The license needs to be met (GNU license). 12 | 13 | In addition to that, the following points must be met too: 14 | - Jarinjaring, shadowing or directly including the source code or a built jar of this project in any way is not allowed unless otherwisely discussed with me personally. 15 | - Forks and ports: not allowed. period. If you want it for a different version, create a pull request on github. 16 | - Forks in any form of the library created for the intent to publish it somewhere else are not allowed. Excuses are to be aranged with me via e-mail contact (dertoaster@cq-repoured.net). Pull requests are allowed and welcome. Reuploading forks or ports to other mod loaders or minecraft versions are not permitted, if you want it for different versions, create a pull request on github. 17 | - Complete re-uploads (of the source code or builds) on other sites is not permitted unless explicitly permitted by me. 18 | - Usage of the library in commercial projects (as in you get any form of direct monetary revenue for this (e.g. writing a mod for some youtuber)) is not permitted by default. For permission, contact me via e-mail (dertoaster@cq-repoured.net) directly 19 | - If the library is being used, the authors of this library have to be mentioned at least in the mod's credits section 20 | - If the library is being used in a commercial (as in you get any form of direct monetary revenue for this (e.g. writing a mod for some youtuber)) projects, direct credit has to be given and we need to be contacted first for permission 21 | 22 | In short: as a user installing this mod, just install it normally. If you are a developer that wants to use this library in their mod: don't be a dick, depend on it NORMALLY without including this jar or the source code in your stuff and if you make profits via some "40k$ mr beast mod" stuff then please ask in before if it is alright. Leeching from the work of others isn't cool. 23 | 24 | # Using (for mod developers) 25 | To use MHLib, use cursemaven for mixinbooster and either ivy or cursemaven for the main library itself. 26 | 27 | For ivy, you need to do this: 28 | ``` 29 | repositories { 30 | ivy { 31 | name "Github Releases - DT Versioning" // Github Releases 32 | url "https://github.com" 33 | 34 | patternLayout { 35 | artifact "[organisation]/[module]/releases/download/MC[revision]/[module]-[revision].[ext]" 36 | } 37 | 38 | metadataSources { artifact() } 39 | } 40 | maven { 41 | name "CurseMaven" 42 | url "https://cursemaven.com" 43 | } 44 | } 45 | 46 | dependencies { 47 | implementation fg.deobf("dertoaster98:multihitboxlib:${mc_version}-${multihitboxlib_version}@jar") 48 | // This is the 0.1.0 version for 1.20.1 https://www.curseforge.com/minecraft/mc-mods/mixinbooster/files/5146058 49 | implementation fg.deobf("curse.maven:mixinbooster-980731:5146058") 50 | } 51 | ``` 52 | ${mc_version} and ${multihitboxlib_version} are parameters drawn from gradle.properties, so set them there. 53 | Example: 54 | ``` 55 | mc_version=1.20.1 56 | multihitboxlib_version=1.6.0 57 | ``` 58 | 59 | For just cursemaven, you need to do this: 60 | ``` 61 | repositories { 62 | maven { 63 | name "CurseMaven" 64 | url "https://cursemaven.com" 65 | } 66 | } 67 | 68 | dependencies { 69 | // This is the 1.8.1 version for 1.20.1 https://www.curseforge.com/minecraft/mc-mods/multihitboxlib/files/5779529 70 | implementation fg.deobf("curse.maven:multihitboxlib-899090:5779529") 71 | // This is the 0.1.0 version for 1.20.1 https://www.curseforge.com/minecraft/mc-mods/mixinbooster/files/5146058 72 | implementation fg.deobf("curse.maven:mixinbooster-980731:5146058") 73 | } 74 | ``` 75 | 76 | Please depend on the library like a normal mod! 77 | That in essence means add it to your mods requirements list and don't shadow or jar-in-jar or directly include it or similar in your mod's jar or source code. 78 | 79 | # Contact 80 | - Project discord server: [MHLib Discord](https://discord.com/invite/XxwCynDtf3) 81 | - E-Mail (for contacts regarding commercial use. Alternatively, ask on the discord server): [dertoaster@cq-repoured.net](mailto:dertoaster@cq-repoured.net?subject=[MHLib]%20contact%20request) 82 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/init/MHLibPackets.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.init; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.util.Optional; 5 | 6 | import de.dertoaster.multihitboxlib.Constants; 7 | import de.dertoaster.multihitboxlib.MHLibMod; 8 | import de.dertoaster.multihitboxlib.api.network.IMessage; 9 | import de.dertoaster.multihitboxlib.api.network.IMessageHandler; 10 | import de.dertoaster.multihitboxlib.network.client.*; 11 | import de.dertoaster.multihitboxlib.network.client.assetsync.CPacketRequestSynch; 12 | import de.dertoaster.multihitboxlib.network.client.assetsync.SPacketHandlerSynchAssets; 13 | import de.dertoaster.multihitboxlib.network.server.CPacketHandlerBoneInformation; 14 | import de.dertoaster.multihitboxlib.network.server.SPacketFunctionalAnimProgress; 15 | import de.dertoaster.multihitboxlib.network.server.SPacketSetMaster; 16 | import de.dertoaster.multihitboxlib.network.server.SPacketUpdateMultipart; 17 | import de.dertoaster.multihitboxlib.network.server.assetsync.CPacketHandlerRequestSynch; 18 | import de.dertoaster.multihitboxlib.network.server.assetsync.SPacketSynchAssets; 19 | import net.minecraftforge.network.NetworkDirection; 20 | import net.minecraftforge.network.NetworkRegistry; 21 | import net.minecraftforge.network.PacketDistributor.PacketTarget; 22 | import net.minecraftforge.network.simple.SimpleChannel; 23 | 24 | public class MHLibPackets { 25 | public static final SimpleChannel MHLIB_NETWORK = NetworkRegistry.newSimpleChannel(MHLibMod.prefix("main"), () -> Constants.NETWORK_VERSION, Constants.NETWORK_VERSION::equals, Constants.NETWORK_VERSION::equals); 26 | // Start the IDs at 1 so any unregistered messages (ID 0) throw a more obvious exception when received 27 | private static int MESSAGE_ID = 0; 28 | 29 | public static void init() { 30 | registerClientToServer(CPacketBoneInformation.class, CPacketHandlerBoneInformation.class); 31 | 32 | registerServerToClient(SPacketSetMaster.class, SPacketHandlerSetMaster.class); 33 | registerServerToClient(SPacketUpdateMultipart.class, SPacketHandlerUpdateMultipart.class); 34 | registerServerToClient(SPacketFunctionalAnimProgress.class, SPacketHandlerFunctionalAnimProgress.class); 35 | registerServerToClient(SPacketFunctionalAnimProgress.class, SPacketHandlerFunctionalAnimProgressAL.class); 36 | 37 | // Asset Synch 38 | registerClientToServer(CPacketRequestSynch.class, CPacketHandlerRequestSynch.class); 39 | registerServerToClient(SPacketSynchAssets.class, SPacketHandlerSynchAssets.class); 40 | } 41 | 42 | public static void send(T packet, PacketTarget target) { 43 | MHLIB_NETWORK.send(target, packet); 44 | } 45 | 46 | public static void sendToServer(T packet) { 47 | MHLIB_NETWORK.sendToServer(packet); 48 | } 49 | 50 | protected static void registerClientToServer(Class> clsMessage, Class> clsHandler) { 51 | register(clsMessage, clsHandler, Optional.of(NetworkDirection.PLAY_TO_SERVER)); 52 | } 53 | 54 | protected static void registerServerToClient(Class> clsMessage, Class> clsHandler) { 55 | register(clsMessage, clsHandler, Optional.of(NetworkDirection.PLAY_TO_CLIENT)); 56 | } 57 | 58 | protected static void register(Class> clsMessage, Class> clsHandler) { 59 | register(clsMessage, clsHandler, Optional.empty()); 60 | } 61 | 62 | protected static void register(Class> clsMessage, Class> clsHandler, final Optional networkDirection) { 63 | IMessage message = null; 64 | IMessageHandler handler = null; 65 | try { 66 | message = clsMessage.getConstructor(new Class[] {}).newInstance(); 67 | handler = clsHandler.getConstructor(new Class[] {}).newInstance(); 68 | } catch (InstantiationException e) { 69 | e.printStackTrace(); 70 | } catch (IllegalAccessException e) { 71 | e.printStackTrace(); 72 | } catch (IllegalArgumentException e) { 73 | e.printStackTrace(); 74 | } catch (InvocationTargetException e) { 75 | e.printStackTrace(); 76 | } catch (NoSuchMethodException e) { 77 | e.printStackTrace(); 78 | } catch (SecurityException e) { 79 | e.printStackTrace(); 80 | } 81 | if (handler != null && message != null) register(message, handler, networkDirection); 82 | } 83 | 84 | protected static void register(IMessage message, IMessageHandler handler) { 85 | register(message, handler, Optional.empty()); 86 | } 87 | 88 | protected static void register(IMessage message, IMessageHandler handler, final Optional networkDirection) { 89 | MHLIB_NETWORK.registerMessage(MESSAGE_ID++, message.getPacketClass(), message::toBytes, message::fromBytes, handler::handlePacket, networkDirection); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/client/azurelib/renderlayer/AzurelibBoneInformationCollectorLayer.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.client.azurelib.renderlayer; 2 | 3 | import org.joml.Vector3d; 4 | 5 | import com.mojang.blaze3d.vertex.PoseStack; 6 | import com.mojang.blaze3d.vertex.VertexConsumer; 7 | 8 | import de.dertoaster.multihitboxlib.api.IMHLibExtendedRenderLayer; 9 | import de.dertoaster.multihitboxlib.client.IBoneInformationCollectorLayerCommonLogic; 10 | import it.unimi.dsi.fastutil.Stack; 11 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 12 | import mod.azure.azurelib.cache.object.GeoBone; 13 | import mod.azure.azurelib.core.animatable.GeoAnimatable; 14 | import mod.azure.azurelib.renderer.GeoRenderer; 15 | import mod.azure.azurelib.renderer.GeoReplacedEntityRenderer; 16 | import mod.azure.azurelib.renderer.layer.GeoRenderLayer; 17 | import net.minecraft.client.renderer.MultiBufferSource; 18 | import net.minecraft.client.renderer.RenderType; 19 | import net.minecraft.util.Tuple; 20 | import net.minecraft.world.entity.Entity; 21 | import net.minecraft.world.phys.Vec3; 22 | 23 | public class AzurelibBoneInformationCollectorLayer extends GeoRenderLayer implements IBoneInformationCollectorLayerCommonLogic, IMHLibExtendedRenderLayer { 24 | 25 | private Stack> scaleAndRotationStack = new ObjectArrayList<>(); 26 | private Vector3d currentScaling = new Vector3d(1,1,1); 27 | private Vector3d currentRotation = new Vector3d(0,0,0); 28 | 29 | public AzurelibBoneInformationCollectorLayer(GeoRenderer entityRendererIn) { 30 | super(entityRendererIn); 31 | } 32 | 33 | private int currentTick = -1; 34 | 35 | @Override 36 | public void renderForBone(PoseStack poseStack, T animatable, GeoBone bone, RenderType renderType, MultiBufferSource bufferSource, VertexConsumer buffer, float partialTick, int packedLight, int packedOverlay) { 37 | Entity entity = null; 38 | if (!(animatable instanceof Entity)) { 39 | if (this.renderer instanceof GeoReplacedEntityRenderer grer) { 40 | entity = grer.getCurrentEntity(); 41 | } 42 | } else { 43 | entity = (Entity)animatable; 44 | } 45 | this.onRenderBone(poseStack, entity, bone, renderType, bufferSource, buffer, partialTick, packedLight, packedOverlay); 46 | } 47 | 48 | @Override 49 | public int getCurrentTick() { 50 | return this.currentTick; 51 | } 52 | 53 | @Override 54 | public void setCurrentTick(int tick) { 55 | this.currentTick = tick; 56 | } 57 | 58 | @Override 59 | public void calcScales(GeoBone bone) { 60 | Vector3d scale = this.getCurrentScaling(); 61 | scale.x *= bone.getScaleX(); 62 | scale.y *= bone.getScaleY(); 63 | scale.z *= bone.getScaleZ(); 64 | //this.scaleX *= bone.getScaleX(); 65 | //this.scaleY *= bone.getScaleY(); 66 | //this.scaleZ *= bone.getScaleZ(); 67 | } 68 | 69 | @Override 70 | public String getBoneName(GeoBone bone) { 71 | return bone.getName(); 72 | } 73 | 74 | @Override 75 | public Vec3 getBoneWorldPosition(GeoBone bone) { 76 | final Vec3 worldPos = new Vec3(bone.getWorldPosition().x, bone.getWorldPosition().y, bone.getWorldPosition().z); 77 | return worldPos; 78 | } 79 | 80 | @Override 81 | public boolean isBoneHidden(GeoBone bone) { 82 | return bone.isHidden(); 83 | } 84 | 85 | @Override 86 | public Vec3 getScaleVector() { 87 | Vector3d scale = this.getCurrentScaling(); 88 | return new Vec3(scale.x, scale.y, scale.z); 89 | } 90 | 91 | @Override 92 | public void setScales(int x, int y, int z) { 93 | Vector3d scale = this.getCurrentScaling(); 94 | scale.x *= x; 95 | scale.y *= y; 96 | scale.z *= z; 97 | /*this.scaleX = x; 98 | this.scaleY = y; 99 | this.scaleZ = z;*/ 100 | } 101 | 102 | @Override 103 | public void calcRotations(GeoBone bone) { 104 | Vector3d rot = this.getCurrentRotation(); 105 | rot.x += bone.getRotX(); 106 | rot.y += bone.getRotY(); 107 | rot.z += bone.getRotZ(); 108 | /*this.rotX += bone.getRotX(); 109 | this.rotY += bone.getRotY(); 110 | this.rotZ += bone.getRotZ();*/ 111 | } 112 | 113 | @Override 114 | public Vec3 getRotationVector() { 115 | Vector3d rot = this.getCurrentRotation(); 116 | return new Vec3(rot.x, rot.y, rot.z); 117 | } 118 | 119 | @Override 120 | public void setRotations(int x, int y, int z) { 121 | Vector3d rot = this.getCurrentRotation(); 122 | rot.x = x; 123 | rot.y = y; 124 | rot.z = z; 125 | /*this.rotX = x; 126 | this.rotY = y; 127 | this.rotZ = z;*/ 128 | } 129 | 130 | @Override 131 | public void pushToStack(Vector3d scaling, Vector3d rotation) { 132 | this.scaleAndRotationStack.push(new Tuple<>(scaling, rotation)); 133 | } 134 | 135 | @Override 136 | public Tuple popStack() { 137 | return this.scaleAndRotationStack.pop(); 138 | } 139 | 140 | @Override 141 | public Vector3d getCurrentScaling() { 142 | return this.currentScaling; 143 | } 144 | 145 | @Override 146 | public Vector3d getCurrentRotation() { 147 | return this.currentRotation; 148 | } 149 | 150 | @Override 151 | public void applyCurrentValues(Vector3d scaling, Vector3d rotation) { 152 | this.currentRotation = rotation; 153 | this.currentScaling = scaling; 154 | } 155 | 156 | @Override 157 | public void resetStack() { 158 | this.scaleAndRotationStack = new ObjectArrayList<>(); 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/client/geckolib/renderlayer/GeckolibBoneInformationCollectorLayer.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.client.geckolib.renderlayer; 2 | 3 | import org.joml.Vector3d; 4 | 5 | import com.mojang.blaze3d.vertex.PoseStack; 6 | import com.mojang.blaze3d.vertex.VertexConsumer; 7 | 8 | import de.dertoaster.multihitboxlib.api.IMHLibExtendedRenderLayer; 9 | import de.dertoaster.multihitboxlib.client.IBoneInformationCollectorLayerCommonLogic; 10 | import it.unimi.dsi.fastutil.Stack; 11 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 12 | import net.minecraft.client.renderer.MultiBufferSource; 13 | import net.minecraft.client.renderer.RenderType; 14 | import net.minecraft.util.Tuple; 15 | import net.minecraft.world.entity.Entity; 16 | import net.minecraft.world.phys.Vec3; 17 | import software.bernie.geckolib.cache.object.GeoBone; 18 | import software.bernie.geckolib.core.animatable.GeoAnimatable; 19 | import software.bernie.geckolib.renderer.GeoRenderer; 20 | import software.bernie.geckolib.renderer.GeoReplacedEntityRenderer; 21 | import software.bernie.geckolib.renderer.layer.GeoRenderLayer; 22 | 23 | public class GeckolibBoneInformationCollectorLayer extends GeoRenderLayer implements IBoneInformationCollectorLayerCommonLogic, IMHLibExtendedRenderLayer{ 24 | 25 | private Stack> scaleAndRotationStack = new ObjectArrayList<>(); 26 | private Vector3d currentScaling = new Vector3d(1,1,1); 27 | private Vector3d currentRotation = new Vector3d(0,0,0); 28 | 29 | public GeckolibBoneInformationCollectorLayer(GeoRenderer entityRendererIn) { 30 | super(entityRendererIn); 31 | } 32 | 33 | private int currentTick = -1; 34 | 35 | @Override 36 | public void renderForBone(PoseStack poseStack, T animatable, GeoBone bone, RenderType renderType, MultiBufferSource bufferSource, VertexConsumer buffer, float partialTick, int packedLight, int packedOverlay) { 37 | Entity entity = null; 38 | if (!(animatable instanceof Entity)) { 39 | if (this.renderer instanceof GeoReplacedEntityRenderer grer) { 40 | entity = grer.getCurrentEntity(); 41 | } 42 | } else { 43 | entity = (Entity)animatable; 44 | } 45 | this.onRenderBone(poseStack, entity, bone, renderType, bufferSource, buffer, partialTick, packedLight, packedOverlay); 46 | } 47 | 48 | @Override 49 | public int getCurrentTick() { 50 | return this.currentTick; 51 | } 52 | 53 | @Override 54 | public void setCurrentTick(int tick) { 55 | this.currentTick = tick; 56 | } 57 | 58 | @Override 59 | public void calcScales(GeoBone bone) { 60 | Vector3d scale = this.getCurrentScaling(); 61 | scale.x *= bone.getScaleX(); 62 | scale.y *= bone.getScaleY(); 63 | scale.z *= bone.getScaleZ(); 64 | //this.scaleX *= bone.getScaleX(); 65 | //this.scaleY *= bone.getScaleY(); 66 | //this.scaleZ *= bone.getScaleZ(); 67 | } 68 | 69 | @Override 70 | public String getBoneName(GeoBone bone) { 71 | return bone.getName(); 72 | } 73 | 74 | @Override 75 | public Vec3 getBoneWorldPosition(GeoBone bone) { 76 | final Vec3 worldPos = new Vec3(bone.getWorldPosition().x, bone.getWorldPosition().y, bone.getWorldPosition().z); 77 | return worldPos; 78 | } 79 | 80 | @Override 81 | public boolean isBoneHidden(GeoBone bone) { 82 | return bone.isHidden(); 83 | } 84 | 85 | @Override 86 | public Vec3 getScaleVector() { 87 | Vector3d scale = this.getCurrentScaling(); 88 | return new Vec3(scale.x, scale.y, scale.z); 89 | } 90 | 91 | @Override 92 | public void setScales(int x, int y, int z) { 93 | Vector3d scale = this.getCurrentScaling(); 94 | scale.x *= x; 95 | scale.y *= y; 96 | scale.z *= z; 97 | /*this.scaleX = x; 98 | this.scaleY = y; 99 | this.scaleZ = z;*/ 100 | } 101 | 102 | @Override 103 | public void calcRotations(GeoBone bone) { 104 | Vector3d rot = this.getCurrentRotation(); 105 | rot.x += bone.getRotX(); 106 | rot.y += bone.getRotY(); 107 | rot.z += bone.getRotZ(); 108 | /*this.rotX += bone.getRotX(); 109 | this.rotY += bone.getRotY(); 110 | this.rotZ += bone.getRotZ();*/ 111 | } 112 | 113 | @Override 114 | public Vec3 getRotationVector() { 115 | Vector3d rot = this.getCurrentRotation(); 116 | return new Vec3(rot.x, rot.y, rot.z); 117 | } 118 | 119 | @Override 120 | public void setRotations(int x, int y, int z) { 121 | Vector3d rot = this.getCurrentRotation(); 122 | rot.x = x; 123 | rot.y = y; 124 | rot.z = z; 125 | /*this.rotX = x; 126 | this.rotY = y; 127 | this.rotZ = z;*/ 128 | } 129 | 130 | @Override 131 | public void pushToStack(Vector3d scaling, Vector3d rotation) { 132 | this.scaleAndRotationStack.push(new Tuple<>(scaling, rotation)); 133 | } 134 | 135 | @Override 136 | public Tuple popStack() { 137 | return this.scaleAndRotationStack.pop(); 138 | } 139 | 140 | @Override 141 | public Vector3d getCurrentScaling() { 142 | return this.currentScaling; 143 | } 144 | 145 | @Override 146 | public Vector3d getCurrentRotation() { 147 | return this.currentRotation; 148 | } 149 | 150 | @Override 151 | public void applyCurrentValues(Vector3d scaling, Vector3d rotation) { 152 | this.currentRotation = rotation; 153 | this.currentScaling = scaling; 154 | } 155 | 156 | @Override 157 | public void resetStack() { 158 | this.scaleAndRotationStack = new ObjectArrayList<>(); 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/network/client/CPacketBoneInformation.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.network.client; 2 | 3 | import java.util.HashSet; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | import java.util.Set; 7 | 8 | import de.dertoaster.multihitboxlib.api.IMultipartEntity; 9 | import de.dertoaster.multihitboxlib.api.network.AbstractPacket; 10 | import de.dertoaster.multihitboxlib.init.MHLibPackets; 11 | import de.dertoaster.multihitboxlib.util.BoneInformation; 12 | import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; 13 | import net.minecraft.network.FriendlyByteBuf; 14 | import net.minecraft.world.entity.Entity; 15 | import net.minecraft.world.phys.Vec3; 16 | 17 | public class CPacketBoneInformation extends AbstractPacket { 18 | 19 | private int entityID; 20 | private Map boneInformation; 21 | 22 | // Necessary for how the packet "api" works 23 | public CPacketBoneInformation() { 24 | 25 | } 26 | 27 | @Override 28 | public Class getPacketClass() { 29 | return CPacketBoneInformation.class; 30 | } 31 | 32 | @Override 33 | public CPacketBoneInformation fromBytes(FriendlyByteBuf buffer) { 34 | final int entityID = buffer.readInt(); 35 | int infoCount = buffer.readInt(); 36 | final Map infoMap = new Object2ObjectArrayMap<>(infoCount); 37 | while (infoCount > 0) { 38 | infoCount--; 39 | BoneInformation bi = buffer.readJsonWithCodec(BoneInformation.CODEC); 40 | infoMap.put(bi.name(), bi); 41 | } 42 | 43 | return new CPacketBoneInformation(entityID, infoMap); 44 | } 45 | 46 | @Override 47 | public void toBytes(CPacketBoneInformation packet, FriendlyByteBuf buffer) { 48 | buffer.writeInt(packet.entityID); 49 | buffer.writeInt(packet.boneInformation.values().size()); 50 | for (BoneInformation bi : packet.boneInformation.values()) { 51 | buffer.writeJsonWithCodec(BoneInformation.CODEC, bi); 52 | } 53 | } 54 | 55 | CPacketBoneInformation(final int entityID, final Set boneInformation) { 56 | this.entityID = entityID; 57 | this.boneInformation = new Object2ObjectArrayMap<>(boneInformation.size()); 58 | for (BoneInformation bi : boneInformation) { 59 | this.boneInformation.put(bi.name(), bi); 60 | } 61 | } 62 | 63 | CPacketBoneInformation(final int entityID, final Map boneInformation) { 64 | this.entityID = entityID; 65 | this.boneInformation = boneInformation; 66 | } 67 | 68 | public void send() { 69 | MHLibPackets.sendToServer(this); 70 | } 71 | 72 | public static Builder builder(Entity entity) { 73 | return new Builder(entity.getId()); 74 | } 75 | 76 | public static class Builder { 77 | 78 | private final int entityID; 79 | private final Set processedBones = new HashSet<>(); 80 | private final Set boneInformation = new HashSet<>(); 81 | 82 | private Optional currentBoneName = Optional.empty(); 83 | private Optional currentBonePos = Optional.empty(); 84 | private Optional currentBoneScales = Optional.empty(); 85 | private Optional currentBoneRotations = Optional.empty(); 86 | private Optional currentBoneHidden = Optional.empty(); 87 | 88 | Builder(final int entityID) { 89 | this.entityID = entityID; 90 | } 91 | 92 | public Builder addInfo(final String boneName) { 93 | if (this.processedBones.contains(boneName)) { 94 | throw new IllegalStateException("a bone with the name of " + boneName + " has already been added!"); 95 | } 96 | if (this.currentBoneName.isPresent()) { 97 | this.compileAndAddBone(); 98 | } 99 | this.currentBoneName = Optional.of(boneName); 100 | 101 | return this; 102 | } 103 | 104 | public Builder done() { 105 | if (this.currentBoneName.isPresent()) { 106 | this.compileAndAddBone(); 107 | } 108 | return this; 109 | } 110 | 111 | public Builder position(final Vec3 worldPosition) { 112 | this.checkState(); 113 | 114 | this.currentBonePos = Optional.ofNullable(worldPosition); 115 | 116 | return this; 117 | } 118 | 119 | public Builder scaling(final Vec3 currentScaling) { 120 | this.checkState(); 121 | 122 | this.currentBoneScales = Optional.ofNullable(currentScaling); 123 | 124 | return this; 125 | } 126 | 127 | public Builder rotation(Vec3 rotation) { 128 | this.checkState(); 129 | 130 | this.currentBoneRotations = Optional.ofNullable(rotation); 131 | 132 | return this; 133 | } 134 | 135 | public Builder hidden(final boolean hidden) { 136 | this.checkState(); 137 | 138 | this.currentBoneHidden = Optional.ofNullable(hidden); 139 | 140 | return this; 141 | } 142 | 143 | private void checkState() { 144 | if (this.currentBoneName.isEmpty()) { 145 | throw new IllegalStateException("a bone name must be set, otherwise the state is invalid!"); 146 | } 147 | } 148 | 149 | private void compileAndAddBone() { 150 | BoneInformation bi = new BoneInformation(this.currentBoneName.get(), this.currentBoneHidden.orElse(false), this.currentBonePos.orElse(Vec3.ZERO), this.currentBoneScales.orElse(BoneInformation.DEFAULT_SCALING), this.currentBoneRotations.orElse(Vec3.ZERO)); 151 | if (!this.boneInformation.add(bi)) { 152 | // throw new IllegalStateException("Unable to add information for bone " + bi.name()); 153 | } 154 | 155 | this.currentBoneName = Optional.empty(); 156 | this.currentBonePos = Optional.empty(); 157 | this.currentBoneScales = Optional.empty(); 158 | this.currentBoneRotations = Optional.empty(); 159 | } 160 | 161 | public CPacketBoneInformation build() { 162 | if (this.currentBoneName.isPresent()) { 163 | this.compileAndAddBone(); 164 | } 165 | 166 | return new CPacketBoneInformation(this.entityID, this.boneInformation); 167 | } 168 | } 169 | 170 | public int getEntityID() { 171 | return this.entityID; 172 | } 173 | 174 | public Map getBoneInformation() { 175 | return this.boneInformation; 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/de/dertoaster/multihitboxlib/mixin/entity/MixinLivingEntity.java: -------------------------------------------------------------------------------- 1 | package de.dertoaster.multihitboxlib.mixin.entity; 2 | 3 | import de.dertoaster.multihitboxlib.api.IMHLibFieldAccessor; 4 | import de.dertoaster.multihitboxlib.api.IMultipartEntity; 5 | import de.dertoaster.multihitboxlib.entity.MHLibPartEntity; 6 | import de.dertoaster.multihitboxlib.network.client.CPacketBoneInformation; 7 | import de.dertoaster.multihitboxlib.util.BoneInformation; 8 | import net.minecraft.world.entity.Entity; 9 | import net.minecraft.world.entity.EntityType; 10 | import net.minecraft.world.entity.LivingEntity; 11 | import net.minecraft.world.level.Level; 12 | import net.minecraft.world.phys.Vec3; 13 | import net.minecraftforge.entity.PartEntity; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.Unique; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 20 | 21 | import javax.annotation.Nullable; 22 | import java.util.*; 23 | import java.util.concurrent.LinkedTransferQueue; 24 | 25 | @Mixin(LivingEntity.class) 26 | public abstract class MixinLivingEntity extends Entity implements IMultipartEntity, IMHLibFieldAccessor { 27 | 28 | @Unique 29 | public Map> partMap = new HashMap<>(); 30 | @Unique 31 | public Map syncDataMap = new HashMap<>(); 32 | 33 | @Unique 34 | private PartEntity[] partArray; 35 | 36 | @Unique 37 | private Optional boneInformationBuilder = Optional.empty(); 38 | 39 | @Unique 40 | private final Queue trackerQueue = new LinkedTransferQueue<>(); 41 | @Unique 42 | private int _mhlibTicksSinceLastSync = 0; 43 | 44 | @Override 45 | public int getTicksSinceLastSync() { 46 | return this._mhlibTicksSinceLastSync; 47 | } 48 | 49 | @Override 50 | public Queue getTrackerQueue() { 51 | return this.trackerQueue; 52 | } 53 | 54 | @Unique 55 | @Nullable 56 | private UUID masterUUID = null; 57 | 58 | public MixinLivingEntity(EntityType pEntityType, Level pLevel) { 59 | super(pEntityType, pLevel); 60 | } 61 | 62 | @Inject( 63 | method = "(Lnet/minecraft/world/entity/EntityType;Lnet/minecraft/world/level/Level;)V", 64 | at = @At("TAIL") 65 | ) 66 | private void mixinConstructor(CallbackInfo ci) { 67 | this.mhlibOnConstructor(); 68 | } 69 | 70 | /*@Inject( 71 | method = "hurt(Lnet/minecraft/world/damagesource/DamageSource;F)Z", 72 | at = @At("HEAD"), 73 | cancellable = true 74 | ) 75 | private void mixinHurt(DamageSource pSource, float pAmount, CallbackInfoReturnable cir) { 76 | if(!(this.HITBOX_PROFILE != null && this.HITBOX_PROFILE.isPresent())) { 77 | return; 78 | } 79 | if (pSource.is(DamageTypes.OUT_OF_WORLD)) { 80 | return; 81 | } 82 | 83 | if (pSource.isCreativePlayer()) { 84 | //return; 85 | } 86 | 87 | if (this.HITBOX_PROFILE != null && this.HITBOX_PROFILE.isPresent() && !this.HITBOX_PROFILE.get().mainHitboxConfig().canReceiveDamage()) { 88 | if (!this.hurtFromPart) { 89 | cir.setReturnValue(false); 90 | cir.cancel(); 91 | } 92 | } 93 | }*/ 94 | 95 | // After ticking all parts => call alignment code 96 | // Bind to interface to potentially cancel auto ticking and alignment 97 | @Inject( 98 | method = "aiStep", 99 | at = @At("TAIL") 100 | ) 101 | private void mixinAiStep(CallbackInfo ci) { 102 | if (!this.isMultipartEntity()) { 103 | return; 104 | } 105 | 106 | this.mhlibAiStep(); 107 | } 108 | 109 | // In tick method => intercept and tick the subparts 110 | @Inject( 111 | method = "tick", 112 | at = @At("TAIL") 113 | ) 114 | private void mixinTick(CallbackInfo ci) { 115 | if(!this.isMultipartEntity()) { 116 | return; 117 | } 118 | 119 | this.tickParts(this.partMap.values()); 120 | } 121 | 122 | @Override 123 | public synchronized boolean tryAddBoneInformation(String boneName, boolean hidden, Vec3 position, Vec3 scaling, Vec3 rotation) { 124 | return IMultipartEntity.super.tryAddBoneInformation(boneName, hidden, position, scaling, rotation); 125 | } 126 | 127 | // Before the constructor gets called => intercept the entityType and modify it's "size" argument to use the mainHitboxSize 128 | 129 | // Intercept defineSynchedData and add our master data 130 | 131 | // Intercept object creation, then create the hitbox profile 132 | 133 | // Add special hurt method => in interface => used for when subparts where damaged 134 | 135 | // Call this after the object has been created => method in interface, needs to be cancellable 136 | 137 | // Override setID to also set the id of the parts 138 | @Override 139 | public void setId(int pId) { 140 | // Attention: First call super, then MHLib! 141 | super.setId(pId); 142 | this.mhlibSetID(pId); 143 | } 144 | 145 | @Override 146 | public boolean isMultipartEntity() { 147 | return super.isMultipartEntity() || !this.partMap.values().isEmpty(); 148 | } 149 | 150 | @Override 151 | @Nullable 152 | public PartEntity[] getParts() { 153 | return this.mhLibGetParts(); 154 | } 155 | 156 | // Also make sure to modify the result of isMultipartEntity to be correct 157 | 158 | @Inject( 159 | method = "isPickable()Z", 160 | at = @At("RETURN"), 161 | cancellable = true 162 | ) 163 | private void mixinIsPickable(CallbackInfoReturnable cir) { 164 | cir.setReturnValue(this.mhLibIsPickable(cir.getReturnValue())); 165 | } 166 | 167 | // MHLib access stuff 168 | @Override 169 | public PartEntity[] _mhlibAccess_getPartArray() { 170 | return this.partArray; 171 | } 172 | 173 | @Override 174 | public void _mhlibAccess_setPartArray(final PartEntity[] value) { 175 | this.partArray = value; 176 | } 177 | 178 | @Override 179 | public Queue _mhlibAccess_getTrackerQueue() { 180 | return this.trackerQueue; 181 | } 182 | 183 | @Override 184 | public int _mhlibAccess_getTicksSinceLastSynch() { 185 | return this._mhlibTicksSinceLastSync; 186 | } 187 | 188 | @Override 189 | public void _mhlibAccess_setTicksSinceLastSynch(int value) { 190 | this._mhlibTicksSinceLastSync = value; 191 | } 192 | 193 | @Override 194 | public Map> _mhlibAccess_getPartMap() { 195 | return this.partMap; 196 | } 197 | 198 | @Override 199 | public void _mhlibAccess_setPartMap(Map> value) { 200 | this.partMap = value; 201 | } 202 | 203 | @Override 204 | public Map _mhlibAccess_getSynchMap() { 205 | return this.syncDataMap; 206 | } 207 | 208 | @Override 209 | public UUID _mhlibAccess_getMasterUUID() { 210 | return this.masterUUID; 211 | } 212 | 213 | @Override 214 | public void _mhlibAccess_setMasterUUID(UUID value) { 215 | this.masterUUID = value; 216 | } 217 | 218 | @Override 219 | public Optional _mlibAccess_getBoneInfoBuilder() { 220 | return this.boneInformationBuilder; 221 | } 222 | 223 | @Override 224 | public void _mlibAccess_setBoneInfoBuilder(Optional value) { 225 | this.boneInformationBuilder = value; 226 | } 227 | 228 | } 229 | --------------------------------------------------------------------------------