[Feature] Support for Custom Boats (quiqueck/BetterNether#4)
This commit is contained in:
parent
696e0d5def
commit
a18aa400ed
10 changed files with 483 additions and 1 deletions
|
@ -0,0 +1,75 @@
|
|||
package org.betterx.bclib.client.render;
|
||||
|
||||
import org.betterx.bclib.items.boat.BoatTypeOverride;
|
||||
import org.betterx.bclib.items.boat.CustomBoatTypeOverride;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.math.Quaternion;
|
||||
import com.mojang.math.Vector3f;
|
||||
import net.minecraft.client.model.BoatModel;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.vehicle.Boat;
|
||||
import net.minecraft.world.entity.vehicle.ChestBoat;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
||||
@Environment(value = EnvType.CLIENT)
|
||||
public class BoatRenderer {
|
||||
|
||||
public static boolean render(
|
||||
Boat boat,
|
||||
float f,
|
||||
float g,
|
||||
PoseStack poseStack,
|
||||
MultiBufferSource multiBufferSource,
|
||||
int i
|
||||
) {
|
||||
if (boat instanceof CustomBoatTypeOverride cbto) {
|
||||
BoatTypeOverride type = cbto.bcl_getCustomType();
|
||||
if (type != null) {
|
||||
boolean hasChest = boat instanceof ChestBoat;
|
||||
float k;
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(0.0, 0.375, 0.0);
|
||||
poseStack.mulPose(Vector3f.YP.rotationDegrees(180.0f - f));
|
||||
float h = (float) boat.getHurtTime() - g;
|
||||
float j = boat.getDamage() - g;
|
||||
if (j < 0.0f) {
|
||||
j = 0.0f;
|
||||
}
|
||||
if (h > 0.0f) {
|
||||
poseStack.mulPose(Vector3f.XP.rotationDegrees(Mth.sin(h) * h * j / 10.0f * (float) boat.getHurtDir()));
|
||||
}
|
||||
if (!Mth.equal(k = boat.getBubbleAngle(g), 0.0f)) {
|
||||
poseStack.mulPose(new Quaternion(new Vector3f(1.0f, 0.0f, 1.0f), boat.getBubbleAngle(g), true));
|
||||
}
|
||||
ResourceLocation resourceLocation = hasChest ? type.chestBoatTexture : type.boatTexture;
|
||||
BoatModel boatModel = type.getBoatModel(hasChest);
|
||||
poseStack.scale(-1.0f, -1.0f, 1.0f);
|
||||
poseStack.mulPose(Vector3f.YP.rotationDegrees(90.0f));
|
||||
boatModel.setupAnim(boat, g, 0.0f, -0.1f, 0.0f, 0.0f);
|
||||
VertexConsumer vertexConsumer = multiBufferSource.getBuffer(boatModel.renderType(resourceLocation));
|
||||
boatModel.renderToBuffer(
|
||||
poseStack, vertexConsumer, i,
|
||||
OverlayTexture.NO_OVERLAY,
|
||||
1.0f, 1.0f, 1.0f, 1.0f
|
||||
);
|
||||
if (!boat.isUnderWater()) {
|
||||
VertexConsumer vertexConsumer2 = multiBufferSource.getBuffer(RenderType.waterMask());
|
||||
boatModel.waterPatch().render(poseStack, vertexConsumer2, i, OverlayTexture.NO_OVERLAY);
|
||||
}
|
||||
poseStack.popPose();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
25
src/main/java/org/betterx/bclib/items/boat/BaseBoatItem.java
Normal file
25
src/main/java/org/betterx/bclib/items/boat/BaseBoatItem.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
package org.betterx.bclib.items.boat;
|
||||
|
||||
import org.betterx.bclib.interfaces.ItemModelProvider;
|
||||
|
||||
import net.minecraft.world.entity.vehicle.Boat;
|
||||
import net.minecraft.world.item.BoatItem;
|
||||
|
||||
public class BaseBoatItem extends BoatItem implements CustomBoatTypeOverride, ItemModelProvider {
|
||||
BoatTypeOverride customType;
|
||||
|
||||
public BaseBoatItem(boolean bl, BoatTypeOverride type, Properties properties) {
|
||||
super(bl, Boat.Type.OAK, properties);
|
||||
setCustomType(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCustomType(BoatTypeOverride type) {
|
||||
customType = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoatTypeOverride bcl_getCustomType() {
|
||||
return customType;
|
||||
}
|
||||
}
|
172
src/main/java/org/betterx/bclib/items/boat/BoatTypeOverride.java
Normal file
172
src/main/java/org/betterx/bclib/items/boat/BoatTypeOverride.java
Normal file
|
@ -0,0 +1,172 @@
|
|||
package org.betterx.bclib.items.boat;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
|
||||
import net.minecraft.client.model.BoatModel;
|
||||
import net.minecraft.client.model.geom.ModelLayerLocation;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.BoatItem;
|
||||
import net.minecraft.world.item.CreativeModeTab;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class BoatTypeOverride {
|
||||
private static final String DEFAULT_LAYER = "main";
|
||||
|
||||
private static final List<BoatTypeOverride> values = new ArrayList<>(8);
|
||||
private final String name;
|
||||
private final Block planks;
|
||||
private final int ordinal;
|
||||
public final ResourceLocation id;
|
||||
public final ResourceLocation boatTexture;
|
||||
public final ResourceLocation chestBoatTexture;
|
||||
public final ModelLayerLocation boatModelName;
|
||||
public final ModelLayerLocation chestBoatModelName;
|
||||
private BoatModel boatModel, chestBoatModel;
|
||||
private BoatItem boat, chestBoat;
|
||||
|
||||
BoatTypeOverride(String modID, String name, Block planks) {
|
||||
this.id = new ResourceLocation(modID, name);
|
||||
this.name = name;
|
||||
this.planks = planks;
|
||||
int nr = Objects.hash(name);
|
||||
while (byId(nr) != null) {
|
||||
nr++;
|
||||
BCLib.LOGGER.warning("Boat Type Ordinal " + nr + " is already used, searching for another one");
|
||||
}
|
||||
this.ordinal = nr;
|
||||
if (BCLib.isClient()) {
|
||||
this.boatModelName = createBoatModelName(id.getNamespace(), id.getPath());
|
||||
this.chestBoatModelName = createChestBoatModelName(id.getNamespace(), id.getPath());
|
||||
this.boatTexture = getTextureLocation(modID, name, false);
|
||||
this.chestBoatTexture = getTextureLocation(modID, name, true);
|
||||
} else {
|
||||
this.boatModelName = null;
|
||||
this.chestBoatModelName = null;
|
||||
this.boatTexture = null;
|
||||
this.chestBoatTexture = null;
|
||||
}
|
||||
|
||||
values.add(this);
|
||||
}
|
||||
|
||||
public BoatModel getBoatModel(boolean chest) {
|
||||
return chest ? chestBoatModel : boatModel;
|
||||
}
|
||||
|
||||
public void createBoatModels(EntityRendererProvider.Context context) {
|
||||
if (boatModel == null) {
|
||||
boatModel = new BoatModel(context.bakeLayer(boatModelName), false);
|
||||
chestBoatModel = new BoatModel(context.bakeLayer(chestBoatModelName), true);
|
||||
}
|
||||
}
|
||||
|
||||
public Block getPlanks() {
|
||||
return planks;
|
||||
}
|
||||
|
||||
public void setBoatItem(BoatItem item) {
|
||||
this.boat = item;
|
||||
}
|
||||
|
||||
public BoatItem getBoatItem() {
|
||||
return boat;
|
||||
}
|
||||
|
||||
public void setChestBoatItem(BoatItem item) {
|
||||
this.chestBoat = item;
|
||||
}
|
||||
|
||||
public BoatItem getChestBoatItem() {
|
||||
return chestBoat;
|
||||
}
|
||||
|
||||
public static Stream<BoatTypeOverride> values() {
|
||||
return values.stream();
|
||||
}
|
||||
|
||||
private static ModelLayerLocation createBoatModelName(String modID, String name) {
|
||||
return new ModelLayerLocation(new ResourceLocation(modID, "boat/" + name), DEFAULT_LAYER);
|
||||
}
|
||||
|
||||
private static ModelLayerLocation createChestBoatModelName(String modID, String name) {
|
||||
return new ModelLayerLocation(new ResourceLocation(modID, "chest_boat/" + name), DEFAULT_LAYER);
|
||||
}
|
||||
|
||||
private static ResourceLocation getTextureLocation(String modID, String name, boolean chest) {
|
||||
if (chest) {
|
||||
return new ResourceLocation(modID, "textures/entity/chest_boat/" + name + ".png");
|
||||
}
|
||||
return new ResourceLocation(modID, "textures/entity/boat/" + name + ".png");
|
||||
}
|
||||
|
||||
public static BoatTypeOverride create(String modID, String name, Block planks) {
|
||||
BoatTypeOverride t = new BoatTypeOverride(modID, name, planks);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
public BoatItem createItem(boolean hasChest) {
|
||||
return createItem(hasChest, new Item.Properties().stacksTo(1).tab(CreativeModeTab.TAB_TRANSPORTATION));
|
||||
}
|
||||
|
||||
public BoatItem createItem(boolean hasChest, Item.Properties itemSettings) {
|
||||
BoatItem item = new BaseBoatItem(hasChest, this, itemSettings);
|
||||
|
||||
if (hasChest) this.setChestBoatItem(item);
|
||||
else this.setBoatItem(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static BoatTypeOverride byId(int i) {
|
||||
for (BoatTypeOverride t : values) {
|
||||
if (t.ordinal == i) return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static BoatTypeOverride byName(String string) {
|
||||
for (BoatTypeOverride t : values) {
|
||||
if (!t.name().equals(string)) continue;
|
||||
return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int ordinal() {
|
||||
return ordinal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||
var that = (BoatTypeOverride) obj;
|
||||
return Objects.equals(this.name, that.name) &&
|
||||
this.ordinal == that.ordinal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, ordinal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BoatTypeOverride[" +
|
||||
"name=" + name + ", " +
|
||||
"ordinal=" + ordinal + ']';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package org.betterx.bclib.items.boat;
|
||||
|
||||
public interface CustomBoatTypeOverride {
|
||||
void setCustomType(BoatTypeOverride type);
|
||||
BoatTypeOverride bcl_getCustomType();
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.betterx.bclib.mixin.client.boat;
|
||||
|
||||
|
||||
import org.betterx.bclib.items.boat.BoatTypeOverride;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.BoatRenderer;
|
||||
import net.minecraft.client.renderer.entity.EntityRenderer;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.world.entity.vehicle.Boat;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(BoatRenderer.class)
|
||||
public abstract class BoatRendererMixin extends EntityRenderer<Boat> {
|
||||
protected BoatRendererMixin(EntityRendererProvider.Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Inject(method = "<init>", at = @At("TAIL"))
|
||||
private void bcl_init(EntityRendererProvider.Context context, boolean bl, CallbackInfo ci) {
|
||||
BoatTypeOverride.values().forEach(type -> type.createBoatModels(context));
|
||||
}
|
||||
|
||||
@Inject(method = "render(Lnet/minecraft/world/entity/vehicle/Boat;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At("HEAD"), cancellable = true)
|
||||
void bcl_render(
|
||||
Boat boat,
|
||||
float f, float g,
|
||||
PoseStack poseStack, MultiBufferSource multiBufferSource,
|
||||
int i,
|
||||
CallbackInfo ci
|
||||
) {
|
||||
if (org.betterx.bclib.client.render.BoatRenderer.render(boat, f, g, poseStack, multiBufferSource, i)) {
|
||||
super.render(boat, f, g, poseStack, multiBufferSource, i);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package org.betterx.bclib.mixin.common.boat;
|
||||
|
||||
import org.betterx.bclib.items.boat.CustomBoatTypeOverride;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.item.BoatItem;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
|
||||
@Mixin(BoatItem.class)
|
||||
public class BoatItemMixin {
|
||||
@ModifyArg(method = "use", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;noCollision(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;)Z"))
|
||||
Entity bcl_suse(Entity boat) {
|
||||
if (this instanceof CustomBoatTypeOverride self) {
|
||||
if (boat instanceof CustomBoatTypeOverride newBoat) {
|
||||
newBoat.setCustomType(self.bcl_getCustomType());
|
||||
}
|
||||
}
|
||||
return boat;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package org.betterx.bclib.mixin.common.boat;
|
||||
|
||||
import org.betterx.bclib.items.boat.BoatTypeOverride;
|
||||
import org.betterx.bclib.items.boat.CustomBoatTypeOverride;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.EntityDataSerializers;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.vehicle.Boat;
|
||||
import net.minecraft.world.item.BoatItem;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(Boat.class)
|
||||
public abstract class BoatMixin extends Entity implements CustomBoatTypeOverride {
|
||||
private static final EntityDataAccessor<Integer> DATA_CUSTOM_ID_TYPE = SynchedEntityData.defineId(
|
||||
Boat.class,
|
||||
EntityDataSerializers.INT
|
||||
);
|
||||
|
||||
public BoatMixin(EntityType<?> entityType, Level level) {
|
||||
super(entityType, level);
|
||||
}
|
||||
|
||||
public void setCustomType(BoatTypeOverride type) {
|
||||
this.entityData.set(DATA_CUSTOM_ID_TYPE, type != null ? type.ordinal() : -1);
|
||||
}
|
||||
|
||||
public BoatTypeOverride bcl_getCustomType() {
|
||||
return BoatTypeOverride.byId(this.entityData.get(DATA_CUSTOM_ID_TYPE));
|
||||
}
|
||||
|
||||
@Inject(method = "defineSynchedData", at = @At("TAIL"))
|
||||
void bcl_adddefineSynchedData(CallbackInfo ci) {
|
||||
this.entityData.define(DATA_CUSTOM_ID_TYPE, -1);
|
||||
}
|
||||
|
||||
@Inject(method = "addAdditionalSaveData", at = @At("HEAD"))
|
||||
void bcl_addAdditionalSaveData(CompoundTag compoundTag, CallbackInfo ci) {
|
||||
BoatTypeOverride type = this.bcl_getCustomType();
|
||||
if (type != null) {
|
||||
compoundTag.putString("cType", type.name());
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "readAdditionalSaveData", at = @At("HEAD"))
|
||||
void bcl_readAdditionalSaveData(CompoundTag compoundTag, CallbackInfo ci) {
|
||||
if (compoundTag.contains("cType")) {
|
||||
this.setCustomType(BoatTypeOverride.byName(compoundTag.getString("cType")));
|
||||
} else {
|
||||
this.setCustomType(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "getDropItem", at = @At("HEAD"), cancellable = true)
|
||||
void bcl_getDropItem(CallbackInfoReturnable<Item> cir) {
|
||||
BoatTypeOverride type = this.bcl_getCustomType();
|
||||
if (type != null) {
|
||||
BoatItem boat = type.getBoatItem();
|
||||
if (boat != null) {
|
||||
cir.setReturnValue(boat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "checkFallDamage", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/vehicle/Boat;kill()V", shift = At.Shift.AFTER), cancellable = true)
|
||||
void bcl_checkFallDamage(double d, boolean bl, BlockState blockState, BlockPos blockPos, CallbackInfo ci) {
|
||||
BoatTypeOverride type = this.bcl_getCustomType();
|
||||
if (type != null) {
|
||||
if (this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
this.spawnAtLocation(type.getPlanks());
|
||||
}
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
this.spawnAtLocation(Items.STICK);
|
||||
}
|
||||
|
||||
this.resetFallDistance();
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package org.betterx.bclib.mixin.common.boat;
|
||||
|
||||
import org.betterx.bclib.items.boat.BoatTypeOverride;
|
||||
import org.betterx.bclib.items.boat.CustomBoatTypeOverride;
|
||||
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.entity.vehicle.ChestBoat;
|
||||
import net.minecraft.world.item.BoatItem;
|
||||
import net.minecraft.world.item.Item;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(ChestBoat.class)
|
||||
public abstract class ChestBoatMixin {
|
||||
@Shadow
|
||||
public abstract InteractionResult interact(Player player, InteractionHand interactionHand);
|
||||
|
||||
@Inject(method = "getDropItem", at = @At("HEAD"), cancellable = true)
|
||||
void bcl_getDropItem(CallbackInfoReturnable<Item> cir) {
|
||||
if (this instanceof CustomBoatTypeOverride cbto) {
|
||||
BoatTypeOverride type = cbto.bcl_getCustomType();
|
||||
if (type != null) {
|
||||
BoatItem boat = type.getChestBoatItem();
|
||||
if (boat != null) {
|
||||
cir.setReturnValue(boat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,8 @@
|
|||
"ModelManagerMixin",
|
||||
"PresetEditorMixin",
|
||||
"SignEditScreenMixin",
|
||||
"TextureAtlasMixin"
|
||||
"TextureAtlasMixin",
|
||||
"boat.BoatRendererMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
|
|
@ -37,6 +37,9 @@
|
|||
"SurfaceRulesContextAccessor",
|
||||
"TheEndBiomesMixin",
|
||||
"WorldGenRegionMixin",
|
||||
"boat.BoatItemMixin",
|
||||
"boat.BoatMixin",
|
||||
"boat.ChestBoatMixin",
|
||||
"elytra.LivingEntityMixin",
|
||||
"shears.BeehiveBlockMixin",
|
||||
"shears.DiggingEnchantmentMixin",
|
||||
|
|
Loading…
Reference in a new issue