[Feature] Support for Custom Boats (quiqueck/BetterNether#4)

This commit is contained in:
Frank 2022-07-30 00:08:13 +02:00
parent 696e0d5def
commit a18aa400ed
10 changed files with 483 additions and 1 deletions

View file

@ -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;
}
}

View 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;
}
}

View 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 + ']';
}
}

View file

@ -0,0 +1,6 @@
package org.betterx.bclib.items.boat;
public interface CustomBoatTypeOverride {
void setCustomType(BoatTypeOverride type);
BoatTypeOverride bcl_getCustomType();
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}
}

View file

@ -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);
}
}
}
}
}

View file

@ -14,7 +14,8 @@
"ModelManagerMixin",
"PresetEditorMixin",
"SignEditScreenMixin",
"TextureAtlasMixin"
"TextureAtlasMixin",
"boat.BoatRendererMixin"
],
"injectors": {
"defaultRequire": 1

View file

@ -37,6 +37,9 @@
"SurfaceRulesContextAccessor",
"TheEndBiomesMixin",
"WorldGenRegionMixin",
"boat.BoatItemMixin",
"boat.BoatMixin",
"boat.ChestBoatMixin",
"elytra.LivingEntityMixin",
"shears.BeehiveBlockMixin",
"shears.DiggingEnchantmentMixin",