From 464409212033e4f0473ebbd811ab46444b297449 Mon Sep 17 00:00:00 2001 From: Cryghast Date: Sun, 11 Feb 2024 20:37:18 +0800 Subject: [PATCH 01/23] update zh_cn.json --- .../resources/assets/unicopia/lang/zh_cn.json | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/unicopia/lang/zh_cn.json b/src/main/resources/assets/unicopia/lang/zh_cn.json index 5b978ee5..d0a2edd9 100644 --- a/src/main/resources/assets/unicopia/lang/zh_cn.json +++ b/src/main/resources/assets/unicopia/lang/zh_cn.json @@ -49,7 +49,10 @@ "emi.category.unicopia.spellbook": "魔法书", "emi.category.unicopia.cloud_shaping": "塑形", "emi.category.unicopia.growing": "生长", - + "emi.category.unicopia.altar": "黑暗仪式", + "recipe.unicopia.altar.instruction": "将物品掷入火中", + "recipe.unicopia.growing.instruction": "聚焦陆马魔法", + "item.unicopia.alicorn_badge": "天角兽徽章", "item.unicopia.unicorn_badge": "独角兽徽章", "item.unicopia.pegasus_badge": "天马徽章", @@ -222,12 +225,27 @@ "block.unicopia.rocks": "一些石块", "block.unicopia.plunder_vine": "掠夺之藤", "block.unicopia.plunder_vine_bud": "掠夺之藤幼芽", + "block.unicopia.spectral_fire": "节律火", "block.unicopia.bananas": "香蕉", "block.unicopia.zapling": "魔虹苹果树苗", "block.unicopia.zap_log": "魔虹苹果木原木", "block.unicopia.zap_wood": "魔虹苹果木", "block.unicopia.stripped_zap_log": "去皮魔虹苹果木原木", "block.unicopia.stripped_zap_wood": "去皮魔虹苹果木", + "block.unicopia.zap_planks": "魔虹苹果木板", + "block.unicopia.zap_stairs": "魔虹苹果木楼梯", + "block.unicopia.zap_slab": "魔虹苹果木台阶", + "block.unicopia.zap_fence": "魔虹苹果木栅栏", + "block.unicopia.zap_fence_gate": "魔虹苹果木栅栏门", + "block.unicopia.waxed_zap_log": "涂蜡魔虹苹果木原木", + "block.unicopia.waxed_zap_wood": "涂蜡魔虹苹果木", + "block.unicopia.waxed_stripped_zap_log": "涂蜡去皮魔虹苹果木原木", + "block.unicopia.waxed_stripped_zap_wood": "涂蜡去皮魔虹苹果木", + "block.unicopia.waxed_zap_planks": "涂蜡魔虹苹果木板", + "block.unicopia.waxed_zap_stairs": "涂蜡魔虹苹果木楼梯", + "block.unicopia.waxed_zap_slab": "涂蜡魔虹苹果木台阶", + "block.unicopia.waxed_zap_fence": "涂蜡魔虹苹果木栅栏", + "block.unicopia.waxed_zap_fence_gate": "涂蜡魔虹苹果木栅栏门", "block.unicopia.zap_leaves": "魔虹苹果树叶", "block.unicopia.flowering_zap_leaves": "盛开的魔虹苹果树叶", "block.unicopia.zap_apple": "魔虹苹果", @@ -1443,6 +1461,8 @@ "death.attack.unicopia.horseshoe.self": "%1$s 咣了自己", "death.attack.unicopia.horseshoe.item": "%1$s 被 %2$s 用 %3$s 咣了", "death.attack.unicopia.horseshoe.player": "%1$s 被 %2$s 咣了", + "death.attack.unicopia.spikes": "%1$s 被扎死了", + "death.attack.unicopia.spikes.player": "%1$s 在试图逃离 %2$s 时落入了一堆尖刺中", "death.fell.accident.ladder.pegasus": "%1$s 从梯子上掉下来时忘了自己会飞", "death.fell.accident.vines.pegasus": "%1$s 从藤蔓上掉下来时忘了自己会飞", From 80c31b4a1d5d9b886a7ad568da423d2260179907 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sun, 11 Feb 2024 20:31:37 +0000 Subject: [PATCH 02/23] Fix items with heartbound not being restored if it had only 1 enchantment level before dying --- .../unicopia/entity/player/Pony.java | 4 +- .../item/enchantment/EnchantmentUtil.java | 3 +- .../HeartboundEnchantmentUtil.java | 51 +++++++++++++++++++ .../unicopia/mixin/MixinPlayerInventory.java | 38 ++++---------- 4 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/item/enchantment/HeartboundEnchantmentUtil.java diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java index fab87347..181fd53d 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -28,6 +28,7 @@ import com.minelittlepony.unicopia.entity.mob.UEntityAttributes; import com.minelittlepony.unicopia.entity.player.MagicReserves.Bar; import com.minelittlepony.unicopia.item.FriendshipBraceletItem; import com.minelittlepony.unicopia.item.UItems; +import com.minelittlepony.unicopia.item.enchantment.EnchantmentUtil; import com.minelittlepony.unicopia.item.enchantment.UEnchantments; import com.minelittlepony.unicopia.util.*; import com.minelittlepony.unicopia.network.*; @@ -39,7 +40,6 @@ import com.minelittlepony.common.util.animation.Interpolator; import com.mojang.authlib.GameProfile; import net.minecraft.enchantment.Enchantment; -import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.enchantment.Enchantments; import net.minecraft.entity.*; import net.minecraft.entity.attribute.DefaultAttributeContainer; @@ -919,7 +919,7 @@ public class Pony extends Living implements Copyable, Update PlayerInventory inventory = oldPlayer.asEntity().getInventory(); for (int i = 0; i < inventory.size(); i++) { ItemStack stack = inventory.getStack(i); - if (EnchantmentHelper.getLevel(UEnchantments.HEART_BOUND, stack) > 0) { + if (EnchantmentUtil.consumeEnchantment(UEnchantments.HEART_BOUND, 1, stack, entity.getWorld().random, EnchantmentUtil.getLuck(3, oldPlayer.asEntity()))) { asEntity().getInventory().setStack(i, stack); } } diff --git a/src/main/java/com/minelittlepony/unicopia/item/enchantment/EnchantmentUtil.java b/src/main/java/com/minelittlepony/unicopia/item/enchantment/EnchantmentUtil.java index 5bc6d8f2..f518d5d3 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/enchantment/EnchantmentUtil.java +++ b/src/main/java/com/minelittlepony/unicopia/item/enchantment/EnchantmentUtil.java @@ -16,6 +16,7 @@ import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.random.Random; public interface EnchantmentUtil { + String HEART_BOUND_CONSUMED_FLAG = "unicopia:heart_bound_consumed"; static boolean consumeEnchantment(Enchantment enchantment, int levels, ItemStack stack) { return consumeEnchantment(enchantment, levels, stack, null, 0); @@ -33,7 +34,7 @@ public interface EnchantmentUtil { if (level == 0) { enchantments.remove(enchantment); } else { - enchantments.put(enchantment, level - 1); + enchantments.put(enchantment, level); } EnchantmentHelper.set(enchantments, stack); } diff --git a/src/main/java/com/minelittlepony/unicopia/item/enchantment/HeartboundEnchantmentUtil.java b/src/main/java/com/minelittlepony/unicopia/item/enchantment/HeartboundEnchantmentUtil.java new file mode 100644 index 00000000..b27dd3ad --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/item/enchantment/HeartboundEnchantmentUtil.java @@ -0,0 +1,51 @@ +package com.minelittlepony.unicopia.item.enchantment; + +import java.util.List; + +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.item.ItemStack; +import net.minecraft.util.collection.DefaultedList; + +public interface HeartboundEnchantmentUtil { + static InventorySnapshot createSnapshot(List> combinedInventory) { + List> storedCombinedInventory = combinedInventory.stream().map(l -> DefaultedList.ofSize(l.size(), ItemStack.EMPTY)).toList(); + boolean empty = true; + for (int group = 0; group < combinedInventory.size(); group++) { + var original = combinedInventory.get(group); + for (int i = 0; i < original.size(); i++) { + ItemStack stack = original.get(i); + if (EnchantmentHelper.getLevel(Enchantments.BINDING_CURSE, stack) == 0 + && EnchantmentHelper.getLevel(UEnchantments.HEART_BOUND, stack) > 0) { + original.set(i, ItemStack.EMPTY); + storedCombinedInventory.get(group).set(i, stack); + empty = false; + } + } + } + return empty ? InventorySnapshot.EMPTY : new InventorySnapshot(storedCombinedInventory); + } + + public record InventorySnapshot(List> combinedInventory) { + public static InventorySnapshot EMPTY = new InventorySnapshot(List.of()); + + public boolean empty() { + return combinedInventory.isEmpty(); + } + + public void restoreInto(List> combinedInventory) { + if (empty()) { + return; + } + for (int group = 0; group < combinedInventory.size(); group++) { + var original = combinedInventory.get(group); + for (int i = 0; i < original.size(); i++) { + ItemStack stored = this.combinedInventory.get(group).get(i); + if (!stored.isEmpty()) { + original.set(i, stored); + } + } + } + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerInventory.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerInventory.java index 5debbac8..e2311fef 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerInventory.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerInventory.java @@ -11,11 +11,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import com.minelittlepony.unicopia.advancement.UCriteria; -import com.minelittlepony.unicopia.item.enchantment.EnchantmentUtil; -import com.minelittlepony.unicopia.item.enchantment.UEnchantments; - -import net.minecraft.enchantment.EnchantmentHelper; -import net.minecraft.enchantment.Enchantments; +import com.minelittlepony.unicopia.item.enchantment.HeartboundEnchantmentUtil; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.inventory.Inventory; @@ -31,37 +27,21 @@ abstract class MixinPlayerInventory implements Inventory, Nameable { private @Final List> combinedInventory; @Nullable - private List> storedCombinedInventory; + private HeartboundEnchantmentUtil.InventorySnapshot inventorySnapshot; @Inject(method = "dropAll()V", at = @At("HEAD")) public void beforeDropAll(CallbackInfo info) { - storedCombinedInventory = combinedInventory.stream().map(l -> DefaultedList.ofSize(l.size(), ItemStack.EMPTY)).toList(); - for (int group = 0; group < combinedInventory.size(); group++) { - var original = combinedInventory.get(group); - for (int i = 0; i < original.size(); i++) { - ItemStack stack = original.get(i); - if (EnchantmentHelper.getLevel(Enchantments.BINDING_CURSE, stack) == 0 - && EnchantmentUtil.consumeEnchantment(UEnchantments.HEART_BOUND, 1, stack, player.getWorld().random, EnchantmentUtil.getLuck(3, player))) { - original.set(i, ItemStack.EMPTY); - UCriteria.USE_SOULMATE.trigger(player); - storedCombinedInventory.get(group).set(i, stack); - } - } + inventorySnapshot = HeartboundEnchantmentUtil.createSnapshot(combinedInventory); + if (!inventorySnapshot.empty()) { + UCriteria.USE_SOULMATE.trigger(player); } } - @Inject(method = "dropAll()V", at = @At("TAIL")) + @Inject(method = "dropAll()V", at = @At("RETURN")) public void afterDropAll(CallbackInfo info) { - if (storedCombinedInventory != null) { - for (int group = 0; group < combinedInventory.size(); group++) { - var original = combinedInventory.get(group); - for (int i = 0; i < original.size(); i++) { - ItemStack stored = storedCombinedInventory.get(group).get(i); - if (!stored.isEmpty()) { - original.set(i, stored); - } - } - } + if (inventorySnapshot != null) { + inventorySnapshot.restoreInto(combinedInventory); + inventorySnapshot = null; } } } From a9db2490f5fbb407f10398edde2bdfe970d478d5 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sun, 11 Feb 2024 20:51:12 +0000 Subject: [PATCH 03/23] Make common enchantments less common --- .../unicopia/item/enchantment/UEnchantments.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/minelittlepony/unicopia/item/enchantment/UEnchantments.java b/src/main/java/com/minelittlepony/unicopia/item/enchantment/UEnchantments.java index 3414e340..559e9406 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/enchantment/UEnchantments.java +++ b/src/main/java/com/minelittlepony/unicopia/item/enchantment/UEnchantments.java @@ -30,12 +30,12 @@ public interface UEnchantments { /** * Protects against wall collisions and earth pony attacks! */ - Enchantment PADDED = register("padded", new SimpleEnchantment(Options.armor().rarity(Rarity.COMMON).maxLevel(3))); + Enchantment PADDED = register("padded", new SimpleEnchantment(Options.armor().rarity(Rarity.UNCOMMON).maxLevel(3))); /** * Heavy players move more slowly but are less likely to be flung around wildly. */ - Enchantment HEAVY = register("heavy", new AttributedEnchantment(Options.armor().rarity(Rarity.COMMON).maxLevel(4))) + Enchantment HEAVY = register("heavy", new AttributedEnchantment(Options.armor().rarity(Rarity.UNCOMMON).maxLevel(4))) .addModifier(EntityAttributes.GENERIC_MOVEMENT_SPEED, (user, level) -> { return new EntityAttributeModifier(UUID.fromString("a3d5a94f-4c40-48f6-a343-558502a13e10"), "Heavyness", (1 - level/(float)10) - 1, Operation.MULTIPLY_TOTAL); }); @@ -83,7 +83,7 @@ public interface UEnchantments { * Items with loyalty are kept after death. * Only works if they don't also have curse of binding. */ - Enchantment HEART_BOUND = register("heart_bound", new SimpleEnchantment(Options.create(EnchantmentTarget.VANISHABLE, UEnchantmentValidSlots.ANY).rarity(Rarity.COMMON).maxLevel(5))); + Enchantment HEART_BOUND = register("heart_bound", new SimpleEnchantment(Options.create(EnchantmentTarget.VANISHABLE, UEnchantmentValidSlots.ANY).rarity(Rarity.UNCOMMON).maxLevel(5))); /** * Consumes drops whilst mining and produces experience instead From ae5464b758ce4680db341542c26a52ccb89f757d Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 12 Feb 2024 13:14:29 +0000 Subject: [PATCH 04/23] Fixed shapeshifting and fixed disguise entities not playing animations --- .../ability/magic/spell/SpellReference.java | 6 +++- .../render/spell/ShieldSpellRenderer.java | 2 +- .../unicopia/entity/behaviour/Disguise.java | 10 +++++- .../entity/behaviour/EntityAppearance.java | 2 ++ .../unicopia/entity/duck/RotatedView.java | 2 ++ .../unicopia/mixin/MixinWorld.java | 15 ++++++++ .../client/MixinClientPlayNetworkHandler.java | 35 +++++++++++++++++++ .../datasync/SpellNetworkedReference.java | 2 +- src/main/resources/unicopia.mixin.json | 1 + 9 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/mixin/client/MixinClientPlayNetworkHandler.java diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellReference.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellReference.java index c8b504f8..cca32178 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellReference.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellReference.java @@ -50,6 +50,10 @@ public final class SpellReference implements NbtSerialisable { @Override public void fromNBT(NbtCompound compound) { + fromNBT(compound, true); + } + + public void fromNBT(NbtCompound compound, boolean force) { final int hash = compound.hashCode(); if (nbtHash == hash) { return; @@ -58,7 +62,7 @@ public final class SpellReference implements NbtSerialisable { if (spell == null || !Objects.equals(Spell.getUuid(compound), spell.getUuid())) { spell = Spell.readNbt(compound); - } else { + } else if (force || !spell.isDirty()) { spell.fromNBT(compound); } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java index aa96a28f..d6f236de 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java @@ -51,7 +51,7 @@ public class ShieldSpellRenderer extends SpellRenderer { model.render(matrices, buffer, light, 1, radius, colors[0], colors[1], colors[2], alpha * 0.2F); } else { matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180)); - matrices.scale(1, radius == 0 ? 1 : 2.6F / radius, 1); + matrices.scale(1, radius == 0 ? 1 : MathHelper.clamp(2.6F / radius, 0.7F, 1), 1); SphereModel.SPHERE.render(matrices, buffer, light, 1, radius + thickness, colors[0], colors[1], colors[2], alpha * 0.08F); SphereModel.SPHERE.render(matrices, buffer, light, 1, radius - thickness, colors[0], colors[1], colors[2], alpha * 0.05F); SphereModel.SPHERE.render(matrices, buffer, light, 1, radius + thickness * 2, colors[0], colors[1], colors[2], alpha * 0.05F); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java index 272cde97..7041b6e7 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java @@ -9,6 +9,7 @@ import com.minelittlepony.unicopia.Owned; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.duck.LivingEntityDuck; +import com.minelittlepony.unicopia.entity.duck.RotatedView; import com.minelittlepony.unicopia.entity.player.PlayerDimensions; import com.minelittlepony.unicopia.entity.player.Pony; @@ -100,7 +101,14 @@ public interface Disguise extends FlightType.Provider, PlayerDimensions.Provider behaviour.copyBaseAttributes(owner, entity); if (tick && !getDisguise().skipsUpdate()) { - entity.tick(); + ((RotatedView)entity.getWorld()).setMirrorEntityStatuses(entity.getWorld().isClient); + if (entity.getWorld().isClient) { + entity.tick(); + } else { + entity.tick(); + } + + ((RotatedView)entity.getWorld()).setMirrorEntityStatuses(false); } if (!(owner instanceof PlayerEntity) && !((LivingEntityDuck)owner).isJumping()) { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java index 15c8fee6..7a5d4c96 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java @@ -211,6 +211,8 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi if (source.isClient()) { source.asWorld().spawnEntity(entity); + } else { + entity.setId(source.asEntity().getId()); } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/duck/RotatedView.java b/src/main/java/com/minelittlepony/unicopia/entity/duck/RotatedView.java index 33e09463..e5c60d40 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/duck/RotatedView.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/duck/RotatedView.java @@ -10,6 +10,8 @@ public interface RotatedView { boolean hasTransform(); + void setMirrorEntityStatuses(boolean enable); + default void pushRotation(int y) { getRotations().add(y); } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinWorld.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinWorld.java index eda9c5b4..04db071b 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinWorld.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinWorld.java @@ -6,11 +6,13 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import com.minelittlepony.unicopia.entity.duck.RotatedView; import com.minelittlepony.unicopia.server.world.BlockDestructionManager; import net.minecraft.block.BlockState; +import net.minecraft.entity.Entity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraft.world.WorldAccess; @@ -22,6 +24,7 @@ abstract class MixinWorld implements WorldAccess, BlockDestructionManager.Source private int recurseCount = 0; private final Stack rotations = new Stack<>(); + private boolean mirrorEntityStatuses; @Override public Stack getRotations() { @@ -33,11 +36,23 @@ abstract class MixinWorld implements WorldAccess, BlockDestructionManager.Source return recurseCount <= 0; } + @Override + public void setMirrorEntityStatuses(boolean enable) { + mirrorEntityStatuses = enable; + } + @Override public BlockDestructionManager getDestructionManager() { return destructions.get(); } + @Inject(method = "sendEntityStatus(Lnet/minecraft/entity/Entity;B)V", at = @At("HEAD")) + private void onSendEntityStatus(Entity entity, byte status, CallbackInfo info) { + if (mirrorEntityStatuses) { + entity.handleStatus(status); + } + } + @ModifyVariable(method = "setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;II)Z", at = @At("HEAD")) private BlockPos modifyBlockPos(BlockPos pos) { pos = applyRotation(pos); diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinClientPlayNetworkHandler.java b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinClientPlayNetworkHandler.java new file mode 100644 index 00000000..06821d12 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinClientPlayNetworkHandler.java @@ -0,0 +1,35 @@ +package com.minelittlepony.unicopia.mixin.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; +import com.minelittlepony.unicopia.entity.Living; +import com.minelittlepony.unicopia.entity.behaviour.Disguise; +import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; + +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.network.packet.s2c.play.EntityStatusS2CPacket; + +@Mixin(ClientPlayNetworkHandler.class) +abstract class MixinClientPlayNetworkHandler { + @Shadow private ClientWorld world; + + @Inject(method = "onEntityStatus", at = @At("TAIL")) + private void onOnEntityStatus(EntityStatusS2CPacket packet, CallbackInfo info) { + Living living = Living.living(packet.getEntity(world)); + if (living != null) { + living.getSpellSlot() + .get(SpellPredicate.IS_DISGUISE, false) + .map(Disguise::getDisguise) + .map(EntityAppearance::getAppearance) + .ifPresent(appearance -> { + appearance.handleStatus(packet.getStatus()); + }); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java index 9ae9f29a..fbc8ba3b 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java +++ b/src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java @@ -31,7 +31,7 @@ public class SpellNetworkedReference implements NetworkedRefere @Override public boolean fromNbt(NbtCompound comp) { dirty = false; - currentValue.fromNBT(comp); + currentValue.fromNBT(comp, owner.isClient()); return isDirty(); } diff --git a/src/main/resources/unicopia.mixin.json b/src/main/resources/unicopia.mixin.json index 549625a7..20a09367 100644 --- a/src/main/resources/unicopia.mixin.json +++ b/src/main/resources/unicopia.mixin.json @@ -67,6 +67,7 @@ "client.MixinBackgroundRenderer", "client.MixinCamera", "client.MixinClientWorld", + "client.MixinClientPlayNetworkHandler", "client.MixinEntityRenderDispatcher", "client.MixinGameRenderer", "client.MixinHeldItemRenderer", From 6465d99e3e1af8cc0dc1d2c733672818ad41400b Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 12 Feb 2024 13:27:37 +0000 Subject: [PATCH 05/23] Adjust shield spell shape --- .../unicopia/client/render/spell/ShieldSpellRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java index d6f236de..1fc202ad 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java @@ -51,7 +51,7 @@ public class ShieldSpellRenderer extends SpellRenderer { model.render(matrices, buffer, light, 1, radius, colors[0], colors[1], colors[2], alpha * 0.2F); } else { matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180)); - matrices.scale(1, radius == 0 ? 1 : MathHelper.clamp(2.6F / radius, 0.7F, 1), 1); + matrices.scale(1, radius == 0 ? 1 : MathHelper.clamp(2.6F / radius, 0.7F, 1.8F), 1); SphereModel.SPHERE.render(matrices, buffer, light, 1, radius + thickness, colors[0], colors[1], colors[2], alpha * 0.08F); SphereModel.SPHERE.render(matrices, buffer, light, 1, radius - thickness, colors[0], colors[1], colors[2], alpha * 0.05F); SphereModel.SPHERE.render(matrices, buffer, light, 1, radius + thickness * 2, colors[0], colors[1], colors[2], alpha * 0.05F); From bb661da35e152d9a2f7e82d44eecbb2d6e233b29 Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 12 Feb 2024 13:27:50 +0000 Subject: [PATCH 06/23] Add a LEVEL option to the mana command --- .../com/minelittlepony/unicopia/command/ManaCommand.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/minelittlepony/unicopia/command/ManaCommand.java b/src/main/java/com/minelittlepony/unicopia/command/ManaCommand.java index 8af79712..6383163a 100644 --- a/src/main/java/com/minelittlepony/unicopia/command/ManaCommand.java +++ b/src/main/java/com/minelittlepony/unicopia/command/ManaCommand.java @@ -32,6 +32,11 @@ public class ManaCommand { var bar = type.getBar(pony.getMagicalReserves()); float value = source.getArgument("value", Float.class); + if (type == ManaType.LEVEL) { + pony.getLevel().set((int)value); + value -= (int)value; + type = ManaType.XP; + } if (type == ManaType.XP) { int currentLevel = pony.getLevel().get(); while (type == ManaType.XP && value > 1) { @@ -52,7 +57,8 @@ public class ManaCommand { EXHAUSTION(MagicReserves::getExhaustion), ENERGY(MagicReserves::getEnergy), MANA(MagicReserves::getMana), - XP(MagicReserves::getXp); + XP(MagicReserves::getXp), + LEVEL(MagicReserves::getXp); private final Function getter; From ceb98f6fab392b7a88eb0ab3b58be5b517dded4f Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 12 Feb 2024 17:29:02 +0000 Subject: [PATCH 07/23] Add missing ability icons --- .../ability/EarthPonyKickAbility.java | 2 +- .../ability/EarthPonyStompAbility.java | 3 ++- .../unicopia/ability/ToggleFlightAbility.java | 3 ++- .../unicopia/textures/gui/ability/carry.png | Bin 866 -> 4413 bytes .../unicopia/textures/gui/ability/cast.png | Bin 302 -> 4719 bytes .../unicopia/textures/gui/ability/dispell.png | Bin 431 -> 4817 bytes .../gui/ability/kick_bat_backward.png | Bin 0 -> 5310 bytes .../textures/gui/ability/kick_bat_forward.png | Bin 0 -> 5096 bytes .../gui/ability/kick_changeling_backward.png | Bin 0 -> 5142 bytes .../gui/ability/kick_changeling_forward.png | Bin 0 -> 4913 bytes .../gui/ability/kick_hippogriff_backward.png | Bin 0 -> 5305 bytes .../gui/ability/kick_hippogriff_forward.png | Bin 0 -> 7801 bytes .../gui/ability/kick_kirin_backward.png | Bin 0 -> 5219 bytes .../gui/ability/kick_kirin_forward.png | Bin 0 -> 4995 bytes .../gui/ability/kick_pegasus_backward.png | Bin 0 -> 5360 bytes .../gui/ability/kick_pegasus_forward.png | Bin 0 -> 5137 bytes .../gui/ability/kick_unicorn_backward.png | Bin 0 -> 5305 bytes .../gui/ability/kick_unicorn_forward.png | Bin 0 -> 5091 bytes .../unicopia/textures/gui/ability/shoot.png | Bin 6722 -> 4957 bytes .../textures/gui/ability/stomp_bat.png | Bin 0 -> 5725 bytes .../textures/gui/ability/stomp_changeling.png | Bin 0 -> 5617 bytes .../textures/gui/ability/stomp_hippogriff.png | Bin 0 -> 9098 bytes .../textures/gui/ability/stomp_kirin.png | Bin 0 -> 5603 bytes .../textures/gui/ability/stomp_pegasus.png | Bin 0 -> 5755 bytes .../textures/gui/ability/stomp_unicorn.png | Bin 0 -> 5782 bytes .../gui/ability/toggle_flight_land_earth.png | Bin 0 -> 7374 bytes .../gui/ability/toggle_flight_land_kirin.png | Bin 0 -> 7374 bytes .../ability/toggle_flight_land_unicorn.png | Bin 0 -> 7237 bytes .../ability/toggle_flight_takeoff_earth.png | Bin 0 -> 8265 bytes .../ability/toggle_flight_takeoff_kirin.png | Bin 0 -> 8265 bytes .../ability/toggle_flight_takeoff_unicorn.png | Bin 0 -> 8537 bytes 31 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/kick_bat_backward.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/kick_bat_forward.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/kick_changeling_backward.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/kick_changeling_forward.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/kick_hippogriff_backward.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/kick_hippogriff_forward.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/kick_kirin_backward.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/kick_kirin_forward.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/kick_pegasus_backward.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/kick_pegasus_forward.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/kick_unicorn_backward.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/kick_unicorn_forward.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/stomp_bat.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/stomp_changeling.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/stomp_hippogriff.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/stomp_kirin.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/stomp_pegasus.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/stomp_unicorn.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/toggle_flight_land_earth.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/toggle_flight_land_kirin.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/toggle_flight_land_unicorn.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/toggle_flight_takeoff_earth.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/toggle_flight_takeoff_kirin.png create mode 100644 src/main/resources/assets/unicopia/textures/gui/ability/toggle_flight_takeoff_unicorn.png diff --git a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java index 9f6bb09c..0affd0cf 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java @@ -61,7 +61,7 @@ public class EarthPonyKickAbility implements Ability { @Override public Identifier getIcon(Pony player) { return getId().withPath(p -> "textures/gui/ability/" + p - + "_" + player.getObservedSpecies().getId().getPath() + + "_" + (player.getObservedSpecies().isHuman() ? Race.EARTH : player.getObservedSpecies()).getId().getPath() + "_" + (getKickDirection(player) > 0 ? "forward" : "backward") + ".png"); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java index e81f5a0d..b702400f 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java @@ -70,8 +70,9 @@ public class EarthPonyStompAbility implements Ability { @Override public Identifier getIcon(Pony player) { Identifier id = Abilities.REGISTRY.getId(this); + Race race = player.getObservedSpecies(); return new Identifier(id.getNamespace(), "textures/gui/ability/" + id.getPath() - + "_" + player.getObservedSpecies().getId().getPath() + + "_" + (race.isHuman() ? Race.EARTH : race).getId().getPath() + ".png"); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/ToggleFlightAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/ToggleFlightAbility.java index d9811e74..4f20d77d 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/ToggleFlightAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/ToggleFlightAbility.java @@ -43,9 +43,10 @@ public class ToggleFlightAbility implements Ability { @Override public Identifier getIcon(Pony player) { Identifier id = Abilities.REGISTRY.getId(this); + Race race = player.getObservedSpecies(); return new Identifier(id.getNamespace(), "textures/gui/ability/" + id.getPath() + (player.getPhysics().isFlying() ? "_land" : "_takeoff") - + "_" + player.getObservedSpecies().getId().getPath() + + "_" + (race.isHuman() ? Race.EARTH : race).getId().getPath() + ".png"); } diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/carry.png b/src/main/resources/assets/unicopia/textures/gui/ability/carry.png index 6dc5380daa8d1ae6a5c4f375b924d08dccef72df..68271d24b7e49c3f40f0aa8ab39f31549791b9b0 100644 GIT binary patch literal 4413 zcmeHKdvp}l8K2Dq!kYA?sMP~Lh9TwY>^!rNF-r>Bn2cd#NLG__Y-M(4b~9vO*_rHa zQYs+|1?k~Y)QZVb2;?*x${`di5Ria@wAj+1QmqwCEn4-o1uPQ(z|wDKH!tfsJvpcT zm)&!7zxnR({_g#L_q$(qHdK_CMQ9`%_9+1wK@x30-s&uU?R$5fdaSAgyFK&%^>(SVJhp7> zri13<&fJx2AGv#}<;#{iIs8)_cMUF&ToJoJ9sFQV*Arc6*5l3g`|b0Oly10LX2!h* z_J8*G{AhDeym?>`{rgXOPb7wy#Cy_-R!FVx1DLLj%~bp z-%WM(Cey(c6pBut`<(D@-^{*WwZ3wq@kRT|Gy5N0RbHUHk-6dC!+R4qP9MIszn7Wq zvoa6eHS0TNKiH#D3tMMg&93*H*UsD#y{D_~*Y>*eT;2QIj+2iLR6l#TT+N<;pLg1H z*SyYMEeisDwdeWnEjz#5y?OOppO;;#{LRCr+5KrN2hg6!uM~c9;g=7$z3|!3PVzg3 zPSr0uy=K5Nu+n~ZsQa%6%9{t7NJsp}!8y4%^NyW6*Iv>)XpPigIQO)3Fgo?lMp~+h zb)fE@H5nKG@Nw&By;tyo;~#zg$BX@+q;Kii`-ZLe#4K`I``;U0j1M&oUBA3Nwz^R3 zj{f51H+Q@O8JJK!HCl~(DJO=57C{PoWlJI$(T@u$C`?2Iu};=duk2SsPV92eaST-? zCsv*BCf$)DxmGD{ipo_@z)%CC z)j6>m$fBZfR7P0~Ya#LCgc75%MP{@hD*3od*8?dEcyeO3nik;*A|8)h;*2F6^%IoC z;UGwwplKWsxY`)fgajT^XX_M64wtNoQ6-`&;Sj2G3f^#o=EN`{#?DZ+;uw&mHuSYKR8M0>CMsn$+z=Jz;+P!LW{;(i#F6=thG@VD zM-mA+AO``e!m8A`CH4C@GDDZZuLL8;ED(DfQd4}RVvXCIK4OG3b|Wx-gm)ZzICcXJ zD7Tw)g~bMad%Vku>FaY+SX3m=Fa_2jGd7vV9iorHS(=q_fn^0;5GWfX(xT5!ddE=l zAypGXqO4N^xkUjU2ji3SNrx5Bw~;=aWoa+&pePad(Ow5_&u1Kh$c~|SD5^kK3W2e^ z(y1h!%F8em4SC_n_d0M^vO93EgYx1w!7GZa#7L58H>f0$dmtPQ3UHiCQ1Ht{B;+>+ zbiujB6}%IpE##=DA|Pl!nBc^gDxrqN=zvEF%2k@6t4Y~Nn=RjFwbFKmq)6*1=n*-p zLMG~*M&&d{^kd;57!X#_^ArdSKg7ZnMP)$?M?K+iz=@62+>v27TqjA;1ec)6Ksr)| zJ;POaAxUy%a{D@+J1i-_#{Y%ZFArJ(csTM>MTPYn4Oj9;Rmt_qr{rTmF|HDd8kd3- z#AFDn5R)ZipTLzI5^IH!UxwFQ|pYNHI)lrk9;>@RK@=Z*T>mo?K)?`cBFy#XVUqNwrS<=kgg7Bg>#amY5LRKS%$o}=)j|RXqEmHb@6+v#f zRc|Kb`L;X3IaTA`#Zx~{%l`J%1)E>zPk`$^-nH0M^V&b2>HgLeXzsepOL^|lCm($< zxa*p*D({}NYu5JdyqgVWYnZa}!)wh4(%*0T`Hjamr6cCGOa9V7xOEQxOwRe9?*2g9 z@&$j+4(|IKb>UA~f_*P=x@+U|1m^$lmYFX+*xo+l`pLH&*EIa_Xas5Y)h;fRUpiL* zXy=q=w~(#iy|`q_p(D?pseH5doy^@kHs$>mU%z$5@pOO3tNgxubI(fG^GM%;`FZUb z-q@w4E@}f(-8aqiQpS<)lA*b6v$iAEX|vMbsy}qqe`^+hbe;e9ceZ_zP2BeHmbv@x Wh@G-L)tL)%B7AYVYwwa3E&l;u^!M`s delta 844 zcmV-S1GD_SBH{*+BYy)aNkl7a>9qF2R4rEP<}aO5vgQhGmrn-Tc-1-ST)!Brod)2)RHB!eUu z!54+YH61@=1pJ%K_#i zbDjr*2mSuB-nVyOeC$h$+cN^Xq~w}T ztg_9nD}M=8e|BhGTD>lJHvRS5OO~6m~%al|iZrze&)=lvYqI#`fmwHA(Md`))IRc-NpOhzCDWtZE zmqo`+@@DO%;&K}R%{zq~y_nYtj zzTds~cfb3QDN0O;>lHXK5Q3myW|J`q^f2E7x`DrJZtl;ZtE1D*HnTYb3IGoPDe4M| zcnoNwcD7j51?mEPi$LoKY#zh6fg(Sl{}9-NTI|)p4r{@Pfh`w_pVcRgsWA>Eq>l0%I!r z&R#fq$OP%jf|0$0RUJCg_-penrFfRr-ECvG3-tT1BWWN-=Tp@VDY; z?%?E+>jyUW%uKn$oeuE~-M-;(c*YfN#*I}q=#084YxX6uJ;UF&cJF5ltJqW!VLvwO zin(e<*~`rz3T;R8iKaVqBVGEf^&INTrRs_(pGcZhArSaQ3OXA362J& zZB;m>wF*a~s1|KrK2K+HQH(9;f1&xy1M2`D9Jz^M!TdRbtNBJH)0xdr&5w3QxJocA zTna5gHgAC?vS~_KC*W!xB4-iKbQ)BTmUM0PGryAx;D8jAGKxS5m6b%4D2gIDp}`OZ zrj%>sN({qMx+6R5vT6L&&_ztLCKNKG>um7V^u_cQ4%;7J4V=?`Q5`?ql3MYW$C*YNr2S zezfZR%#aHa&vw@T7VLdce%F2OXw6GE(saZvE_!+hy2|ce6Ftv$YL)ja5~(ZRUAd){ znUh@Dr+>us8v!?T-NtUrqwM#hGWM;lf2>&%{USdmXyxUg{!wctkJC49y?QZI6c;|> zd`a1o`PKPVr`9)!Mg>3KSmlPwVh=tWzh6>1b%~)dEM)QG=ll0B9Wk65ye?PzZq4{@ z)e9=*hb~St{sT!ZURRaT=OKATn@vF5Q-gX0nYZ1)zpLi?kfepscgJ(?R_8yt8~bfZ z{i)jz<|iLpIAG|R+Cl!K=d6wz4~I8i%v8@fAU?77P7FeYjeaNdR*&05N|zLxf{Buz z3qp@p>|FV2wSlnK2VPRt%l&(QTD&KooD$}GNNlsdHUII7&3zYAqg*1<=gSU%lJmlJ zZC&Bk>A^~6?86f#G+&eXCPoHLn^~-02%7?CvC_g=Q z%9p@&sW?yfb=(4POzpDc me&MdQ=l(hR&j-)v9qwbiFu<=8+LQ{C1v1AZ7%Sg-ui)P{a*YZA delta 275 zcmV+u0qp+oB(4IGB!BfuL_t(o!|jzZ4udcZMIBVmk>fCCV_~NfOV5&pft?O)9XU?V zK_z#ot27V@#H3691Of7&>~8{)l$4b8XNf&8BO=Z=5JE&D#gDeuqL|y)r$e~R*VcTM z?G6od&o4(m)KVb;KvR_Q1E{6KsqS&AdwPTf6a_*{g<7hwIe)<%fe8K&ysr13`*s`v z0O0m~2zG#_43Nw$5QGm%P@=U|NXDiq0r^xTc%)2-C2duM1;A$ z)fDA-g4ilxx55QsRw(Z(!4BY)2iu&b1#6+(GG_;Hja%MTV#z${F!GVh^mLtmFeP#7 Z4FR~dxWjb*A`Ack002ovPDHLkV1k}|dszSg diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/dispell.png b/src/main/resources/assets/unicopia/textures/gui/ability/dispell.png index 7afef72e44dc957bdd4fb07b5c769cc56e65a277..f79663be0924314db9e2e0091c9c09ca548f282f 100644 GIT binary patch literal 4817 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 Xc06oD9DI2TMzb{uBwz(ZaX_sg zQ%7NN8ODW{+NlfjDXycqAyn+*GF3CEUtPhaj%d}+ePIzg{l58}Z~Tv!@8;fn?m54E z&hMUkl2@#Wiyp>)hYdl{Fjb5)0rVWEc?<&I)|{N5KzEKxRnaQdM92e7fF{cwVlg_P zv3iZ&SOcH|K(-vT5n#;dFzp@IKv%yCjK_41KLz6nT{?^npXCM(1AQfE0x`;>RNVG zwa)ssQe)b7`3iUco$flVPpTgDk-}CN5b+utv@=og3B>_@eFmGx6#i)n8=ip0&yXiSP^DbJgr80Cz1>9bL80P3m1w2!%M%Cs>YQ8Qe z$3i9M#3d0q3kf+1hlR3392fvFP&S<7FzAg|%%Ol?xEL5SVjj$Ksn`}O;8c)DktPep z5phLa6p3=^vIKA_n-gLoX)HlGtqTIo6!3hT&5ZGQcDtQx7jjLO3?5%Dm-A2oPar^m z24c-N+HePAw2osSIx&=#m9Xf{Hl4}HVK8y6DbuEaVX)5W_RnBet9#&$)-DzRA3O(c z=JB~G&tTy7wy@fwvH(bzL%(TZP0BV?yadW>%CrzvR2F5ljq43T5#<-c$j}+gE-OHGA5EK%eoa;%-DFZFF z8dI8xOvXKxQUNpjW2A}Dk(g^pqqsmTKxss)74s31m?99llqL|ZK!R&!T0Tl^CB2|j zMym}s5)=akz_~hrL(wu)jEm)nn4(EUgj0M3r$u~(7RphvR3aoLGGQ->c#95XC9d!7 z6$3>AC>-ZYBNjd|SXu}yc`BGFW70Xa5UnZ8I@=l*! zcqx>{3Nn$wbk$CmIdd!+2nG;~GkFRCTyhW#7HOeyo5_-7GU*j?Pu1;NR)gzA;x=4~ z+b96qQ-_nf>+mEL#n4Xo8923x)X~}hO`EwqoDiT7jyy(Z1^Z{aM4dM(k@~1});ZPd zTvv(1aa{@wCpts0;#m~w@)KZnE)nx_V+IAPM_0Ob%XPnz3N$IA_?We1st~X-fjgY4${tu zRzi7D7__UX{D2-vhL~e!Ss`esFVk31MP&dG4z{V(QG;)}c@Fmq^w=+{fgrakl`tZ8oo8MzZh% z{f(-$hRVJFBDQU>&&V2es9~zpsObtxZ@Ii4IP`O$Qx^5-^9`lAOOEvMol2Y&N- z{N+ItRds{r*`8nYZVyuiPZlnETz2}Ia*X`>$@9NI@!(8ze6&9-R0U-T4oIE%p6+CM zm1KRLoL+M`x&$fy=fSZjV%)~;r*}6^t=V#r)p7lTaJ#R&cUu?~i4Bj?+=?$Vn|eTa&zUQ+#fLpU0zJg`eB*$DIGWq$Fx$Ol==mWn-(^)nSW^>yxoXV8M`lUk z+DkQ~lapebw_kS5JNUzgZi(1etLFG-ZoZdaR~%XDGq}LNsQKC2^@lgETdXM#+Y}di zYj57<>?bAPwZG&It({t0Rki%gx$tT5P&k~+HO*d-yrI+VaB{OYukg=PivH2ba;=O~D?~b*ZuNr#DkSDLUqC zXdLB3@s!7>Y^WoiJ)QIXS|FTrVE|gceo64`{kQJtPjqXH-K4mrt6ZJe5m!^VIA&4Z mt5J6ryQT7;`P+G~2L8Bycc@?E?i(P1AyrhI^2@373jPf@q!CvD literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/kick_changeling_backward.png b/src/main/resources/assets/unicopia/textures/gui/ability/kick_changeling_backward.png new file mode 100644 index 0000000000000000000000000000000000000000..21bed4d9a3c07ed44f2b08d595eec4a0c8982af5 GIT binary patch literal 5142 zcmeHLdsGuw8XqFB;tLA8Xl)yVa-{7fGn2_{B+4TQK?6~uMOJGkGdC~`d6)?hP#3DA zD7wC=mY&qoQ*3oz*i}m1;tMOvSzKNFP}=3Fi|(nmh;GF_Xsx<;lJGk1IeR=?_bITyTAK=zx&;LGi)$sq>c@n9EKpsSbdsq0j$MB2@Qtdd}--jSe*yidW&BF5)uj@ zATl5j2@o)_1Prl70i%#nux%YI&q7!)dp01pqkklX(tn=oLZK(M=~L3W+lb$%_y25 z(UwyoF@-iI?H?Dr>$izJ)=fKgYQ6(i-#d_9=vaR7!ik09fwL1l&wHYl*0nyKUNI4W zVM^b%OGn!-%}mHMy=5sp6<|1Y)a)AqMsZ;gyyy7k_+?u~7BPnC3ZyxeU0 zytg&NeBi4S=^>5V_eEqzrYUuf%cNB#zp%MT-4qleX?W~sOctCx3x-kiWAqa zp4`5-WzMEW??jE}{+@a7-uk7-b<&C@0CT=l8#eY}cgW*4S1#pL?9pD7f?uOpD5-e@#eEVR?l3Pe}=DLFRhN!x@ zsIZm?lU8Moi#`%m+HvG^&I9!-g!ppdcfo3P;OVOBqqll*?LK$;k@k!4Zih(EIIE6z zUG6}T@v}Hnwkz8(k7DdrDb3o=KxEtX5|&RiL; z(P(5CAtMMBB2d1>=AzxGjgJv1d>lH!GfvLo;_NoDz)73!g)Xf`0{g}N{#hLc!vMXF z_p1Q)khy7x43}aut5r611n){If+T*2es%Ik~oeW4R0ybC7PzshA81E=_ z7I?#985t-5RtV+ctoX1gg$*|_LXaSrvpT$^pxDEZF3vJ2)-d0M9&b299f89Kc!#0; zWA}m~%3z>$cBW8pPp{KTg!w7f&TuT{GgTTKFe(+QP?+T?i4hD+GYS@^SxnBzHLS%9 z)I+HBHr_?s7$8tVaw!LSXjY|S<+Ku|EhMC1Xn?9Q+5#;(qagtfXpMOYMW&O3SxFZR z^-7>(Ar-4plX98_C?UrQl(b+pYL=^%C~np$R0=|=G?Tzf#WK_!yVFX;<>ah%E|58F zx!w*za4ONL*GdQ}HrQe;pj{R?Kr5NY*$Ul*9VX5SvRt&FCa%I%DuqI>!4(Qzt20Y@)Qzy+hHtJvJ=oQyVGR17ic8|hwebP0d6OjcF{W8 z1t96bF>LBThF``o3iG)y&>8G3XDRt_Xkqh+;~^d{c^b#V`AfV_z8#eXmiwN3j|H4} zD~ZM4O+nF&F9e=00<70h$mQ!|@@QKwfTxE)UHjX)pGgHw!Qg64BS%%ZnMO%mr9ssI zV^Ek57Nruf7{HXn*?GIgRZKfUVlLDXY6a8NdqIh3co!;eIJ!6w2&;e*aum~`nA}7V z6bYqLy^P_4V6vwlqJpN?W(%f)%Uf*D+u0bvVkkjpBBR9@f1J)9rYAvfYc+CJdwU5a*fFK zL<&3+cqF?<;Kb6Ac?fTzW%F$PLk$@aN?@nag|NzeH~7b&00zlRb+%o$q5~ zw0DB?j)@$fJ}xtE&%ANwmhtLW3toIS>%-~4-oEGT%?!o0u2}weh11ToYiWjluak{Beuw$X=|dgOq#@?qC%0p^rX)5u>}YQIRlvJP$A?Gkd2q6;{O0^vcJb~6 zTgv=|{LLR*W#ye8r*stlxngb2{<8abCsstq=mOK@)Yn`7ik&X63{T2_ZS`+bs^@M^ zTN|FBGp3(du_ZbNscq=qH)GzS&CRNkP56wBB8kZI!tMGdWBT-xIp>=juFNfJum7a{ z(VksbxfFeObWHm}-Am&v!4aF+)N~zP-!!r9S_D}9TH2w`o^K-4rn`40A}yMbJyA)a z*D~d0Yr~BTCnSab>Eh~|)Wi41r!POZ`Jg8zr2gO8Q*-iLqcbn`ej8lEijqjV%#2&c#aKiO6zN+`jZ~NLFm8=N*XceD2;p@mYm<)(MB}3Pc I^!n=m06|Fl_5c6? literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/kick_changeling_forward.png b/src/main/resources/assets/unicopia/textures/gui/ability/kick_changeling_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..505009396246ffdab72504f06ece76afa19263a4 GIT binary patch literal 4913 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/kick_hippogriff_forward.png b/src/main/resources/assets/unicopia/textures/gui/ability/kick_hippogriff_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..03601d149db6613428c3924577597bf03e4f376b GIT binary patch literal 7801 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/kick_kirin_backward.png b/src/main/resources/assets/unicopia/textures/gui/ability/kick_kirin_backward.png new file mode 100644 index 0000000000000000000000000000000000000000..fcef4e75af91890ef78967e27cf81641a46c6c1d GIT binary patch literal 5219 zcmeHKX;c$g77o}dps^WIRNTs7D=3vzC6%ottw0dc5JaE>cPdpCC?$(YkO1zs3NGN* zwkV_BIywrXtu2bu;!&VQLBv7DWl&skLq!?2Ris}9M40Jw=6GiOXX?DX``)|vyZ3$X zzME89Xh;Cp$<2ww;c!)f$}rGFY;))WzRk(Wji9Tfqg7g!Y6Qmt41lKH7aTiQ2Q<5O zvb|j=PA4$81hk$&W_8%+X4l!)uK;rYR(TzeJz8~G8`RF8!v%d7XhIX&#`H|?*mloP)Nu}5xxim_J~k~BXS%SLMV!hF&x8yf4d()M8qPMR8Fe{ z@hxKf{6bZJevr{*&@%Bfhhxo}vsw{YG-g1_v~Yj8ch^y=GHPeYtnr@dc~1U2ou@C3 z92m@RS~!fWTDE*!?L^~Ka^Hj6o4a!sn%L?odze^@fBD&a@{Q@3Q89ZXrYA=Dtu~~%(1ahR&i2Rd8pd~xAf<89?5cD zpjX`|-yb+H*Rg!=nrrvs&bi0L_0P|L9Cvz<@o=XV-u?D_7KE+Z5$a|CB(_nV6!Xf} zAvfu0-piSf-fg+^XQ&aB(2B(*ho3Q?blY@S;H)`58*}$%jE(@C=d!^paGkc z^cKPj>&@;gL<@$JHj^gCXkiR`h{Yr{h6Ia(#{=uo`}lN5wYm*nZ*FA)@FB1gMghu4 z1Uj9dy@lE0KMR1g2K1*E=7=OCEeNB{h6EEy`_H2F7WeiL6xn8POfbdUwnLEuI-b@6 zRWtC4c5umFxHb!xf>=gpv{?bNJ7`)M?FX_t#Kx}Jw$nZkVBUt?LHqsQZOTAPt;UrG zGJy?GrBv`(|2SnJ849)OpJ+OnOI7}VnnJTFp|;&S2ie$#K#&;IszOgqa$Kzfl(i8TVM&t zeL__Vo{*1xmAjozp zaDr^vf|-~_Q?@t(R?89@N9bc|P(51H_5D2aC#j&NFd9Xr5|9NF8H^Da$SXM*ip3I; zW`JB2X-S1PbhANgNhD0PPb}aOa0SxS_5%fZ*bd6GV=RerGYq?DON%zpn)| zj>fRFth zjK0g;ygTKw^TS2e54Q#<_pCfT?CP;3O;eL^&JC}nWz0M;-bu^gdHTE?L!^%%jWb2Y zT`q2_dE{`jc0u)~QA_q8$iMgM#t*#@9rJiLu?%?;@Gvps*SXd?A-ZKNxs&?uypwbG zW~SyzNPTwQkjlHY#^MUExUpGxemhun@bvMbkds&P3ld+i%3rxUXj?MkS?k_iyw;_0 z(6xEhJ-a=sc_Lo_8??Ubn~_QM{F&R`-na__2giuVzfGkA)T{X(aOBp!OZ2UaJ^I=w zWXh~qRRlM&aB^v;-NjRfB&kteWwG3P#h{-jov>c|8Q0V;oHk~^>+IXF@0_SADu1W1 zyys0WC`bvqEOV@06HZoscmF%jK3QHKSk`#W)1CDm5|Omqk^JJ$Wh-Z-k2|Q4?se1d zYp@=wT4|1Pi*$RP=}>>x>BWxF-2pj2$uo38!N2snP9E5*x<7lH=G-oxx#&yhgWxRZEVYe){4Xv^Mu`<0RrmI&Xf(z{Sf#dYBKRNddWo`ta6>E-YGhvUu#%%u;38 z(tgj&M*WmNVf%Bzy*a<6s7i(nb9vR6{Z$aJaRL7A^xWVI=jngD8n(G9+wxr5kM}t2 z#G5fH*E1s})3^8!Pw1I`dG?&qapr-8rv*imeZNhUSIsW2Thh4x?tn7pMD4xZB|+N{ zC?6J_;anuGH7@T8U*4X&ck7AVfwbIJ8)dIUL$ik~OG~PDA5SXb58pq(qDadLDEeCT zZu5qtM-#TzuT436r6Tm?!qgj!bUtB46PKfvo6>x^g%ihK;Mzs{yS#`dlQU;GBhy!> zUh)0rn^nF!3-eRYbL*-fTAK~nijd@{!qv^czO|bRU%1)SJ?=HQJRFsOh;ol_%)&na DD0gPw literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/kick_kirin_forward.png b/src/main/resources/assets/unicopia/textures/gui/ability/kick_kirin_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..54626e5b8f4da48b2f0aa23ada074e79d7957998 GIT binary patch literal 4995 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/kick_unicorn_backward.png b/src/main/resources/assets/unicopia/textures/gui/ability/kick_unicorn_backward.png new file mode 100644 index 0000000000000000000000000000000000000000..73b9851401bfe53c39c6d7a5e6c1e68ae89920d8 GIT binary patch literal 5305 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/kick_unicorn_forward.png b/src/main/resources/assets/unicopia/textures/gui/ability/kick_unicorn_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..7952d24ee660aa83e0824311a18c2738f00c25c7 GIT binary patch literal 5091 zcmeHKc~leE8lRwmV6nJB(OQiW6tt5}HbMr1Lc$U-nhFxYbuyX22wBJsB;XT}YDKZQ zm4X(n;zna>PwlB2iXvKQ3rDE7YOR#2wJM@@dsr>-?u11=ujjnuIj{ejoXh>@-rx7T z-~HY1eq`3DXGeLsk8_70$U_;emH9srUx6k;(L z&{%_PC)N;X2pD@0v{ArjFiacAa(49Vfc<8l{QM7PwANC(>C+Z>#dy= ztoQPl>^dddKYc{%QN!NNZv2ntdo5ek;>xNk4BxPYXjIO1uesJffmPFb#`a;)ks0;* zTMzkodak+Fh#0PVT-~C&+;(ivc)eeeU+C2B$gP6Y>lbK3a<46*r47P5{S?`p@l(8u z-ujeArf(T{UzDNvf&TV&y(~d$%J^}b_{XDD^DzI@2$WFU5-O3wI=0 z%kSa!4s)*wJT0`5_qg4URPX0Cmd7x|9dnd-JX~|CL*vBoB z-|~0P-;vkUUiH=bbFOUp!ac_PcjZftMPF(Uy?3|cPRB38s_tCqom$Hl>P`X)a?nP` zC(?SP>^ScRy}aw3(C z&an`2IkV&OoHSgju6_I@u;7P_wrD-$D<5{g%u2sl2SyFg>sZ`2C1U!KN z1_+qSHqw|4Hd2!rie3%{LE#qCOp_)fo8iQ?rc7GK;edH|e|!eBN;N=lr213GK_ZxsNP}RJK8W#_;4r}#>!cWl6M>j+5XBq|39=GP9~>1! zr2|wtX^=>Wi3nIAor+~mQ4)%h|Ed)lJEb%5&x{NbWbqA(Z;5z9r8dG32 z0Z0exaD0Cqo{sQQq&IwqPG!=O`t1LOW-bpq1mMAuN0Ss-Kie_Xd!yorjNYf-$8^$h zmDp^@r9d&fcMBAjMf4sP;Od>iQ!!%-0jft|y7rHgf0GJ`80QBeQXwqiYcW{Fmq_6t z0>NPf)9V8R2^~TpfesZ0*eR2qwqh0{ECuKYv;yhr_z%VQcN|o}i)d>q!JGmR3`V3d zBJ3AT(kB?NZ^JyMX7ukkg!f;Xgg5|$iVX1U9RtM+)I#1sG3?U}#QRTv`p)8?T!PJh zwa829`%11?a=nxSF9m*;U9aSNDFt2%{3^TtZ*sXmf1M(X;8&0pye!>_l}!aNS}xky zCKNNfZPPAI&rtw6)v^47$=vRfOyBZbQ8MU0K(?OCe}TzETku zpIFyX8e8;M#F+ZL$2Tu5Z`$TlV48LEUR$`QQ^iV!Xq=a%pm3ybk>|SHp+gFW;_4Im z;q2jVOIIGYbo^AMc)#rD;4Q(OS(%!7of-UM-_2{Nsk42Y{^Wgh_hDXn+`NV6iJhG5 z6Yo?O+JDAamrL?n@2v7)WPh4);HN8#P9NMCSHe+I)H@?LzF%m6z-_7@_p2!9$QRC% zO;^MZ9&-XYNy~CCtfs2pviCf^e`)#W%7&E(KMm?`mn=Q5-gAEM4~_sjHhT5zDq-c{tDbTLFx}B@$;%+ zKl9UXN>?R)Q_ZW1spPoJKXU%y+Yin%P_?|BoeKCIduc}5{_~fkz zA6=YOiLS^EvnLPB_>OE%IQdEah;N&d7vmlq?i!@RYhKB z)=n{Kx@?-5Wco%-?Sy4_hb^rrjBKzEl_591qEgNp^gl9_lYj$+m4_9 z>4RqH1-ml~nkL1Ln!WhR+TAL3*SgIo799S@(E3h{ojq+%x6t`=dnR`Eeu<}b>UF|p z*Vsp|<=n1xUD)XR{aDRmPL#7e)W>|Ip7Le2KIwaR>6UT zwSRi2c@15koVXil9uwZ^z2p}9aSy%XR9bOYR`-W>`?5T*cx%EA9S{`-M9i<)bt*Ju z#}QQStV;ef>#6|;a%()qYl1%t+nnKuK%t5n|Y#ka6KVq#B9Z9;Y$ks E1EWbi2LJ#7 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/shoot.png b/src/main/resources/assets/unicopia/textures/gui/ability/shoot.png index 72eb1dcc4a586b6fa4327b3f644b5a4aa3ba00bf..f293d7ec7b04bf93206b62c5340e9f727962eb06 100644 GIT binary patch delta 1286 zcmX?Pa#wAFG84mviK=mo%oAJG!x(^o0}RcX7#Ku=JQ(fn=^LP6q-SWLXKcp6U}Ruq zVr5`tWoV>eXl`X-Vr62)z{tS3fEh?KfHBCVEkcu40C`Q5&vMGt2QO6;Wwq|GNLk@m z7C5nQE0^mEfvnarSs%T>O=`TJUEPZxL=}8U-uUXlqcy9WS1WMTHCR|%Xzudp{O`@B zn`C40?)dKN{@vvb7uFb^edWn8D{uD9kf1Y9!;fCjW@#~Ou~0BRJ!A3`lbYk#ZJBEJ zc7N3QUtHhExHZ90K|x4;Mt$O~Bhp7U&)#;ha=yi(Q=7bM{A@BWv3APuTvzeu*v==L z4j3z^nHl@bD2jQ9po-Or}r!eGd$tm$7xz%&FTV!9g%Dz8&zd_7}@M$~!ig`43 zk~oE2G*&Eb(#koKan*Cis0>L@1;3ai**b3cKq**#Ee@HgwLs`cIP% zc`o>Vw9xKY;|0A7Sr+HMZ+yJPui=4l!P4?STh#>qtL(jaagqCh2WAC@cP~z{eNe(6%_rm^XU$5Oe{*rmZ(sh^24;)l92we20bbk5w((m8i z&n&CAFWFeMeAVRz+ZY)5qBBDxN+NuHtdjF{H)n9yGpQIFCYvT&rWorc8KoxanpjvS z>LyxPnCT`P8XH*{rdcMXm?uu==3B~dZeVV1YHng-WMX7!W@0vZKc6I{+2osifx^&u z4}rzIwt>N9TYhP5;+wPi`4}fpLb{WOIml2lu)HlN4K$x4R2NJ%c^NrjD6=awfl* zkgI>e#3RU}wd9!;Fv&7Wdb&7zuQ^{k(8phVmJ_h)8u2{AEt9?6n0)4L$ivThHD z{sHcSx2Jj?L>6W1AJ{7XAU2})z@hnb7l;TS^1tn{q~zJ}m;Q5fBc`XeWEw%lg_KRw+;P! zr4;x1*c#4ph?R`5RFs|BbY+j^qt{<#ejZ{;D?QS>W_=Bt=%Po#nQiA-7KGMne~&M! zTp_a7K$@I9rl$U#U`;$@7_X))ra|}uzYdyUrw&>W6 zOD_e)6|*H&dDOi! zvv8er+~WX`P;z~C`==^N6}75zBk6aG7?R&VcRg!7Lw@0wFM{4O^$R*GRCqSV^zECN zXkw{gZ+Y<5lQnC_(sVZS$fPG-V_O%yaM#Y}qg|04dW}^)3)Snts|h5mVt5i~J7Jqy z;_rNx73OaFt{xZIqpZ2~ISg33gv@!J izVj)1b7lDd);cErEqasfH*l4MN+(ZOKbLh*2~7Zj-wjp( literal 6722 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/stomp_hippogriff.png b/src/main/resources/assets/unicopia/textures/gui/ability/stomp_hippogriff.png new file mode 100644 index 0000000000000000000000000000000000000000..d928d59db4680f27897513155e602daee0afaf0a GIT binary patch literal 9098 zcmeHMc|4R|`@ik`Bg?c%C8aEl*_oLPS;j7VMCF+cgE1a6vP70bO#sEOJk>+mYZDpkmNP=~srI08f#J2%OXpt@~Bm#(l zbqSy-f;!)ZFDgP}f-(uzSANw~Kwa~z4ZjaUNEna<%WP1Ppsoy-MWE0V7sjdu^%hVh z1%LX&fWYEgX<=pOpoY{%z;#i2V1GCgjfW%g2&5W950A#+G5X;6LT7(?1Ofq4z#OoE z&G+#eC%&WKIPvvwoJ>Gn5Fq-14em&?g5N3K;}i~P&i{7pbXlJAp< zkUAi&AS9w7G+z&_0sRyc5?l*Zz;W!t97^Q8}8h_cfavTEAMGr`?HSDmwm5Z_rDn!d^_~v80z@FEu{!sR@|r9WJQF)$rDikjDV>*j^
7Q_;~?`-KxVyCE{mYrEV1%d{K*{nLjqGi!`m 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/stomp_kirin.png b/src/main/resources/assets/unicopia/textures/gui/ability/stomp_kirin.png new file mode 100644 index 0000000000000000000000000000000000000000..73b51d6e6e86ac22b51bf8fc983e313bbfe0407b GIT binary patch literal 5603 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/stomp_unicorn.png b/src/main/resources/assets/unicopia/textures/gui/ability/stomp_unicorn.png new file mode 100644 index 0000000000000000000000000000000000000000..29fcde627952d9beb669cfd80eb982cb381d19f3 GIT binary patch literal 5782 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/toggle_flight_takeoff_earth.png b/src/main/resources/assets/unicopia/textures/gui/ability/toggle_flight_takeoff_earth.png new file mode 100644 index 0000000000000000000000000000000000000000..1587dcebe6dd9be67891126e9f4edba66c9b3c26 GIT binary patch literal 8265 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/toggle_flight_takeoff_kirin.png b/src/main/resources/assets/unicopia/textures/gui/ability/toggle_flight_takeoff_kirin.png new file mode 100644 index 0000000000000000000000000000000000000000..1587dcebe6dd9be67891126e9f4edba66c9b3c26 GIT binary patch literal 8265 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/toggle_flight_takeoff_unicorn.png b/src/main/resources/assets/unicopia/textures/gui/ability/toggle_flight_takeoff_unicorn.png new file mode 100644 index 0000000000000000000000000000000000000000..17c52359d3327380727ea88f4866783a67ed0e2c GIT binary patch literal 8537 zcmeHLdpMM9yPqMak%~x3(G-z2m@y7BnN$oRG|oBA8e=fV7@2VquUbQJ{3ivb4x$>)Re zL-;}4abOa_=f&_$1}d<0J_G#C-~3qMD}9UM13nVU2N473L|`c3OM-JIFzSirxk`cm z5}5GP-vQw9kXOz|#%71)QL0FFRSh&4ua45ds$;O~8uBO(X7&F684@-1T?;Eny}Ggk8^Sjm0y)nYef zor$5bA()6q1NVN`i#igGM$7HQ;B-*BfH#T<0u>PvSuMJHqnOx6)Hc~|sK0zHK7xn~ z^K#;c!XbR(P=0afVg*DFqno&8}4vKz(S!;|X8WcddK1_g)w2m7z8)-LhrDtT`y_c6?PFs;>zJK7u;L!NQ z

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 Date: Mon, 12 Feb 2024 17:29:37 +0000 Subject: [PATCH 08/23] Allow alicorns to use time change and move the sun to where you're looking when starting the ability --- .../com/minelittlepony/unicopia/ability/TimeChangeAbility.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/minelittlepony/unicopia/ability/TimeChangeAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/TimeChangeAbility.java index 15a00851..198aee2f 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/TimeChangeAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/TimeChangeAbility.java @@ -19,7 +19,7 @@ public class TimeChangeAbility implements Ability { @Override public boolean canUse(Race.Composite race) { - return race.pseudo() == Race.UNICORN; + return Ability.super.canUse(race) || race.pseudo() == Race.UNICORN; } @Override From db53f4906e180aa28633ccd02e7f550eee1f4028 Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 12 Feb 2024 17:45:03 +0000 Subject: [PATCH 09/23] Fixed tribe selection gui not scrolling to the correct position when opening --- .../unicopia/client/gui/TribeSelectionScreen.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/TribeSelectionScreen.java b/src/main/java/com/minelittlepony/unicopia/client/gui/TribeSelectionScreen.java index 32c7dabb..6264eff9 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/TribeSelectionScreen.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/TribeSelectionScreen.java @@ -71,10 +71,7 @@ public class TribeSelectionScreen extends GameGui implements HidesHud { addOption(race, top); } - if (SELECTION == -1) { - SELECTION = options.size() / 2; - } - scroll(SELECTION, false); + scroll(SELECTION == -1 ? options.size() / 2 : SELECTION, false); } private void addOption(Race race, int y) { @@ -167,11 +164,12 @@ public class TribeSelectionScreen extends GameGui implements HidesHud { } private void scroll(int target, boolean animate) { - if (target == SELECTION) { + SELECTION = target; + target *= 4; + if (targetScroll == target) { return; } - SELECTION = target; - targetScroll = SELECTION * 4; + targetScroll = target; if (!animate) { scrollPosition = targetScroll; prevScrollPosition = scrollPosition; From e97adc8841371aa8ae938f655af0112f812d9e0d Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 12 Feb 2024 19:36:43 +0000 Subject: [PATCH 10/23] Add /racelist show and /racelist rest --- .../com/minelittlepony/unicopia/Race.java | 12 ++-- .../{FlowingText.java => TextHelper.java} | 11 +++- .../client/gui/DismissSpellScreen.java | 4 +- .../client/gui/spellbook/SpellbookScreen.java | 4 +- .../SpellbookTraitDexPageContent.java | 4 +- .../unicopia/command/RacelistCommand.java | 42 ++++++++++++- .../unicopia/command/SpeciesCommand.java | 7 +-- .../unicopia/command/UCommandSuggestion.java | 62 +++++++++++++++++++ .../unicopia/command/WorldTribeCommand.java | 2 +- .../unicopia/entity/player/Pony.java | 4 +- .../unicopia/item/GemstoneItem.java | 4 +- .../network/MsgRequestSpeciesChange.java | 6 +- .../resources/assets/unicopia/lang/en_us.json | 9 ++- 13 files changed, 144 insertions(+), 27 deletions(-) rename src/main/java/com/minelittlepony/unicopia/client/{FlowingText.java => TextHelper.java} (59%) create mode 100644 src/main/java/com/minelittlepony/unicopia/command/UCommandSuggestion.java diff --git a/src/main/java/com/minelittlepony/unicopia/Race.java b/src/main/java/com/minelittlepony/unicopia/Race.java index ac63cfff..72ae4d08 100644 --- a/src/main/java/com/minelittlepony/unicopia/Race.java +++ b/src/main/java/com/minelittlepony/unicopia/Race.java @@ -19,6 +19,7 @@ import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; import net.minecraft.command.argument.RegistryKeyArgumentType; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.registry.Registry; @@ -135,18 +136,19 @@ public record Race (Supplier compositeSupplier, Availability availabi public boolean isPermitted(@Nullable PlayerEntity sender) { Set whitelist = Unicopia.getConfig().speciesWhiteList.get(); - return isUnset() + return this == HUMAN + || isUnset() || whitelist.isEmpty() || whitelist.contains(getId().toString()); } public Race validate(PlayerEntity sender) { if (!isPermitted(sender)) { - if (this == EARTH) { - return HUMAN; + Race alternative = this == EARTH ? HUMAN : EARTH.validate(sender); + if (alternative != this && sender instanceof ServerPlayerEntity spe) { + spe.sendMessageToClient(Text.translatable("respawn.reason.illegal_race", getDisplayName()), false); } - - return EARTH.validate(sender); + return alternative; } return this; diff --git a/src/main/java/com/minelittlepony/unicopia/client/FlowingText.java b/src/main/java/com/minelittlepony/unicopia/client/TextHelper.java similarity index 59% rename from src/main/java/com/minelittlepony/unicopia/client/FlowingText.java rename to src/main/java/com/minelittlepony/unicopia/client/TextHelper.java index 1d820649..52d57c43 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/FlowingText.java +++ b/src/main/java/com/minelittlepony/unicopia/client/TextHelper.java @@ -1,12 +1,14 @@ package com.minelittlepony.unicopia.client; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import net.minecraft.client.MinecraftClient; import net.minecraft.text.*; -public interface FlowingText { +public interface TextHelper { static Stream wrap(Text text, int maxWidth) { return MinecraftClient.getInstance().textRenderer.getTextHandler().wrapLines(text, maxWidth, Style.EMPTY).stream().map(line -> { MutableText compiled = Text.literal(""); @@ -17,4 +19,11 @@ public interface FlowingText { return compiled; }); } + + static Text join(Text delimiter, Iterable elements) { + MutableText initial = Text.empty(); + return StreamSupport.stream(elements.spliterator(), false).collect(Collectors.reducing(initial, (a, b) -> { + return a == initial ? b : a.append(delimiter).append(b); + })); + } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java b/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java index ca759456..57ea2a25 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java @@ -9,7 +9,7 @@ import com.minelittlepony.common.client.gui.GameGui; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.*; -import com.minelittlepony.unicopia.client.FlowingText; +import com.minelittlepony.unicopia.client.TextHelper; import com.minelittlepony.unicopia.client.render.model.SphereModel; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.UItems; @@ -195,7 +195,7 @@ public class DismissSpellScreen extends GameGui { tooltip.add(ScreenTexts.EMPTY); tooltip.add(Text.translatable("gui.unicopia.dispell_screen.affinity", actualSpell.getAffinity().name()).formatted(actualSpell.getAffinity().getColor())); tooltip.add(ScreenTexts.EMPTY); - tooltip.addAll(FlowingText.wrap(Text.translatable(actualSpell.getType().getTranslationKey() + ".lore").formatted(actualSpell.getAffinity().getColor()), 180).toList()); + tooltip.addAll(TextHelper.wrap(Text.translatable(actualSpell.getType().getTranslationKey() + ".lore").formatted(actualSpell.getAffinity().getColor()), 180).toList()); if (spell instanceof TimedSpell timed) { tooltip.add(ScreenTexts.EMPTY); tooltip.add(Text.translatable("gui.unicopia.dispell_screen.time_left", StringHelper.formatTicks(timed.getTimer().getTicksRemaining()))); diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookScreen.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookScreen.java index 0dc0a71e..20a1dfb7 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookScreen.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookScreen.java @@ -14,7 +14,7 @@ import com.minelittlepony.unicopia.Debug; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; -import com.minelittlepony.unicopia.client.FlowingText; +import com.minelittlepony.unicopia.client.TextHelper; import com.minelittlepony.unicopia.client.gui.*; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.*; import com.minelittlepony.unicopia.compat.trinkets.TrinketSlotBackSprites; @@ -219,7 +219,7 @@ public class SpellbookScreen extends HandledScreen imple List tooltip = new ArrayList<>(); tooltip.add(spell.type().getName()); - tooltip.addAll(FlowingText.wrap(Text.translatable(spell.type().getTranslationKey() + ".lore").formatted(spell.type().getAffinity().getColor()), 180).toList()); + tooltip.addAll(TextHelper.wrap(Text.translatable(spell.type().getTranslationKey() + ".lore").formatted(spell.type().getAffinity().getColor()), 180).toList()); context.drawTooltip(textRenderer, tooltip, x, y); diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookTraitDexPageContent.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookTraitDexPageContent.java index e8f83db6..c5f7f347 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookTraitDexPageContent.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookTraitDexPageContent.java @@ -9,7 +9,7 @@ import com.minelittlepony.common.client.gui.element.Label; import com.minelittlepony.common.client.gui.sprite.TextureSprite; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.spell.trait.*; -import com.minelittlepony.unicopia.client.FlowingText; +import com.minelittlepony.unicopia.client.TextHelper; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.Chapter; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookScreen.ImageButton; import com.minelittlepony.unicopia.container.SpellbookState; @@ -191,7 +191,7 @@ public class SpellbookTraitDexPageContent implements SpellbookChapterList.Conten .setTextureSize(16, 16) .setSize(16, 16) .setTexture(trait.getSprite())); - getStyle().setTooltip(Tooltip.of(FlowingText.wrap(trait.getTooltip(), 200).toList())); + getStyle().setTooltip(Tooltip.of(TextHelper.wrap(trait.getTooltip(), 200).toList())); onClick(sender -> Pony.of(MinecraftClient.getInstance().player).getDiscoveries().markRead(trait)); } diff --git a/src/main/java/com/minelittlepony/unicopia/command/RacelistCommand.java b/src/main/java/com/minelittlepony/unicopia/command/RacelistCommand.java index 333da868..30a19dd0 100644 --- a/src/main/java/com/minelittlepony/unicopia/command/RacelistCommand.java +++ b/src/main/java/com/minelittlepony/unicopia/command/RacelistCommand.java @@ -1,13 +1,16 @@ package com.minelittlepony.unicopia.command; +import java.util.HashSet; +import java.util.Set; import java.util.function.Function; - import com.minelittlepony.unicopia.*; +import com.minelittlepony.unicopia.client.TextHelper; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -15,8 +18,41 @@ class RacelistCommand { static LiteralArgumentBuilder create() { return CommandManager.literal("racelist").requires(s -> s.hasPermissionLevel(3)) + .then(CommandManager.literal("show") + .executes(context -> { + context.getSource().sendFeedback(() -> { + Set whitelist = Unicopia.getConfig().speciesWhiteList.get(); + if (whitelist.isEmpty()) { + return Text.translatable("commands.racelist.inactive"); + } + Set allowed = new HashSet<>(); + Set unallowed = new HashSet<>(); + Race.REGISTRY.forEach(race -> { + (race.isPermitted(null) ? allowed : unallowed).add(Text.translatable("commands.racelist.get.list_item", + race.getDisplayName(), + Text.literal(race.getId().toString()).formatted(Formatting.GRAY) + )); + }); + + return Text.translatable("commands.racelist.get.allowed", allowed.size()).formatted(Formatting.YELLOW) + .append("\n").append(TextHelper.join(Text.literal("\n"), allowed)) + .append("\n") + .append(Text.translatable("commands.racelist.get.not_allowed", unallowed.size()).formatted(Formatting.YELLOW)) + .append("\n").append(TextHelper.join(Text.literal("\n"), unallowed)); + }, false); + return 0; + }) + ) + .then(CommandManager.literal("reset") + .executes(context -> { + Unicopia.getConfig().speciesWhiteList.get().clear(); + Unicopia.getConfig().save(); + context.getSource().sendFeedback(() -> Text.translatable("commands.racelist.clear.success").formatted(Formatting.GREEN), false); + return 0; + }) + ) .then(CommandManager.literal("allow") - .then(CommandManager.argument("race", Race.argument()) + .then(CommandManager.argument("race", Race.argument()).suggests(UCommandSuggestion.ALL_RACE_SUGGESTIONS) .executes(context -> toggle(context.getSource(), context.getSource().getPlayer(), Race.fromArgument(context, "race"), "allowed", race -> { if (race.isUnset()) { @@ -31,7 +67,7 @@ class RacelistCommand { })) )) .then(CommandManager.literal("disallow") - .then(CommandManager.argument("race", Race.argument()) + .then(CommandManager.argument("race", Race.argument()).suggests(UCommandSuggestion.ALL_RACE_SUGGESTIONS) .executes(context -> toggle(context.getSource(), context.getSource().getPlayer(), Race.fromArgument(context, "race"), "disallowed", race -> { boolean result = Unicopia.getConfig().speciesWhiteList.get().remove(race.getId().toString()); diff --git a/src/main/java/com/minelittlepony/unicopia/command/SpeciesCommand.java b/src/main/java/com/minelittlepony/unicopia/command/SpeciesCommand.java index 1363ee96..e1e9e999 100644 --- a/src/main/java/com/minelittlepony/unicopia/command/SpeciesCommand.java +++ b/src/main/java/com/minelittlepony/unicopia/command/SpeciesCommand.java @@ -6,7 +6,6 @@ import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.network.Channel; import com.minelittlepony.unicopia.network.MsgTribeSelect; import com.mojang.brigadier.builder.LiteralArgumentBuilder; - import net.minecraft.command.argument.EntityArgumentType; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.command.CommandManager; @@ -38,13 +37,13 @@ class SpeciesCommand { .executes(context -> get(context.getSource(), EntityArgumentType.getPlayer(context, "target"), false)) )) .then(CommandManager.literal("set") - .then(CommandManager.argument("race", Race.argument()) + .then(CommandManager.argument("race", Race.argument()).suggests(UCommandSuggestion.ALL_RACE_SUGGESTIONS) .executes(context -> set(context.getSource(), context.getSource().getPlayer(), Race.fromArgument(context, "race"), true)) .then(CommandManager.argument("target", EntityArgumentType.player()) .executes(context -> set(context.getSource(), EntityArgumentType.getPlayer(context, "target"), Race.fromArgument(context, "race"), false))) )) .then(CommandManager.literal("describe") - .then(CommandManager.argument("race", Race.argument()) + .then(CommandManager.argument("race", Race.argument()).suggests(UCommandSuggestion.ALL_RACE_SUGGESTIONS) .executes(context -> describe(context.getSource().getPlayer(), Race.fromArgument(context, "race"))) )) .then(CommandManager.literal("list") @@ -60,7 +59,7 @@ class SpeciesCommand { pony.setDirty(); if (race.isUnset()) { - Channel.SERVER_SELECT_TRIBE.sendToPlayer(new MsgTribeSelect(Race.allPermitted(player), "gui.unicopia.tribe_selection.respawn"), (ServerPlayerEntity)player); + Channel.SERVER_SELECT_TRIBE.sendToPlayer(new MsgTribeSelect(Race.allPermitted(player), "gui.unicopia.tribe_selection.welcome"), (ServerPlayerEntity)player); } if (player == source.getPlayer()) { diff --git a/src/main/java/com/minelittlepony/unicopia/command/UCommandSuggestion.java b/src/main/java/com/minelittlepony/unicopia/command/UCommandSuggestion.java new file mode 100644 index 00000000..39d8f858 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/command/UCommandSuggestion.java @@ -0,0 +1,62 @@ +package com.minelittlepony.unicopia.command; + +import java.util.Locale; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Function; +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.Race; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; + +import net.minecraft.command.CommandSource; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.util.Identifier; + +public class UCommandSuggestion { + public static final SuggestionProvider ALL_RACE_SUGGESTIONS = suggestFromRegistry(Race.REGISTRY_KEY); + public static final SuggestionProvider ALLOWED_RACE_SUGGESTIONS = suggestFromRegistry(Race.REGISTRY_KEY, (context, race) -> race.isPermitted(context.getSource().getPlayer())); + + public static SuggestionProvider suggestFromRegistry(RegistryKey> registryKey, @Nullable BiPredicate, T> filter) { + return (context, builder) -> { + Registry registry = context.getSource().getRegistryManager().get(registryKey); + return suggestIdentifiers( + filter == null ? registry : registry.stream().filter(v -> filter.test(context, v))::iterator, + registry::getId, + builder, registryKey.getValue().getNamespace()); + }; + } + + public static SuggestionProvider suggestFromRegistry(RegistryKey> registryKey) { + return suggestFromRegistry(registryKey, null); + } + + public static CompletableFuture suggestIdentifiers(Iterable candidates, Function idFunc, SuggestionsBuilder builder, String defaultNamespace) { + forEachMatching(candidates, builder.getRemaining().toLowerCase(Locale.ROOT), idFunc, id -> builder.suggest(idFunc.apply(id).toString()), defaultNamespace); + return builder.buildFuture(); + } + + public static void forEachMatching(Iterable candidates, String input, Function idFunc, Consumer consumer, String defaultNamespace) { + final boolean hasNamespaceDelimiter = input.indexOf(58) > -1; + for (T object : candidates) { + final Identifier id = idFunc.apply(object); + if (hasNamespaceDelimiter) { + if (CommandSource.shouldSuggest(input, id.toString())) { + consumer.accept(object); + } + } else { + if (CommandSource.shouldSuggest(input, id.getNamespace()) + || (id.getNamespace().equals(defaultNamespace) && CommandSource.shouldSuggest(input, id.getPath())) + ) { + consumer.accept(object); + } + } + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/command/WorldTribeCommand.java b/src/main/java/com/minelittlepony/unicopia/command/WorldTribeCommand.java index e09b9af1..116068f7 100644 --- a/src/main/java/com/minelittlepony/unicopia/command/WorldTribeCommand.java +++ b/src/main/java/com/minelittlepony/unicopia/command/WorldTribeCommand.java @@ -14,7 +14,7 @@ class WorldTribeCommand { return CommandManager.literal("worldtribe").requires(s -> s.hasPermissionLevel(3)) .then(CommandManager.literal("get").executes(context -> get(context.getSource()))) .then(CommandManager.literal("set") - .then(CommandManager.argument("race", Race.argument()) + .then(CommandManager.argument("race", Race.argument()).suggests(UCommandSuggestion.ALLOWED_RACE_SUGGESTIONS) .executes(context -> set(context.getSource(), Race.fromArgument(context, "race"))))); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java index 181fd53d..072cee98 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -231,7 +231,7 @@ public class Pony extends Living implements Copyable, Update public void setSpecies(Race race) { race = race.validate(entity); Race current = getSpecies(); - entity.getDataTracker().set(RACE, Race.REGISTRY.getId(race.validate(entity)).toString()); + entity.getDataTracker().set(RACE, race.getId().toString()); if (race != current) { clearSuppressedRace(); } @@ -244,7 +244,7 @@ public class Pony extends Living implements Copyable, Update } public void setSuppressedRace(Race race) { - entity.getDataTracker().set(SUPPRESSED_RACE, Race.REGISTRY.getId(race.validate(entity)).toString()); + entity.getDataTracker().set(SUPPRESSED_RACE, race.validate(entity).getId().toString()); } public void clearSuppressedRace() { diff --git a/src/main/java/com/minelittlepony/unicopia/item/GemstoneItem.java b/src/main/java/com/minelittlepony/unicopia/item/GemstoneItem.java index 62160c86..dcc35a05 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/GemstoneItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/GemstoneItem.java @@ -10,7 +10,7 @@ import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; -import com.minelittlepony.unicopia.client.FlowingText; +import com.minelittlepony.unicopia.client.TextHelper; import com.minelittlepony.unicopia.entity.player.PlayerCharmTracker; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.group.MultiItem; @@ -90,7 +90,7 @@ public class GemstoneItem extends Item implements MultiItem, EnchantableItem { line = line.formatted(Formatting.OBFUSCATED); } - lines.addAll(FlowingText.wrap(line, 180).toList()); + lines.addAll(TextHelper.wrap(line, 180).toList()); } } diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgRequestSpeciesChange.java b/src/main/java/com/minelittlepony/unicopia/network/MsgRequestSpeciesChange.java index a0af9093..0fe99689 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgRequestSpeciesChange.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgRequestSpeciesChange.java @@ -36,7 +36,11 @@ public record MsgRequestSpeciesChange ( Pony player = Pony.of(sender); if (force || player.getSpecies().isUnset()) { - player.setSpecies(newRace.isPermitted(sender) ? newRace : UnicopiaWorldProperties.forWorld((ServerWorld)player.asWorld()).getDefaultRace()); + boolean permitted = newRace.isPermitted(sender); + player.setSpecies(permitted ? newRace : UnicopiaWorldProperties.forWorld((ServerWorld)player.asWorld()).getDefaultRace()); + if (!permitted) { + sender.sendMessageToClient(Text.translatable("respawn.reason.illegal_race", newRace.getDisplayName()), false); + } if (force) { if (sender.getWorld().getGameRules().getBoolean(UGameRules.ANNOUNCE_TRIBE_JOINS)) { diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index c55ccb5b..ff4debfb 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -699,6 +699,7 @@ "gui.unicopia.page_num": "%d of %d", "respawn.reason.joined_new_tribe": "%1$s was reborn as a %2$s", + "respawn.reason.illegal_race": "The %s race is not permitted by your server's configuration.", "gui.unicopia.tribe_selection.respawn": "You have died.", "gui.unicopia.tribe_selection.respawn.journey": "But the end is not all, for at the end of every end is another beginning.", @@ -1327,7 +1328,12 @@ "commands.racelist.illegal": "The default race %s cannot be used with this command.", "commands.racelist.allowed": "Added %1$s to the whitelist.", - "commands.racelist.allowed.failed": "%1$s is already whitelisted.", + "commands.racelist.get.allowed": "Allowed (%s):", + "commands.racelist.get.not_allowed": "Not Allowed (%s):", + "commands.racelist.get.list_item": "- %s (%s)", + "commands.racelist.clear.success": "Disabled Whitelist", + "commands.racelist.allowed.failed": "%1$s is already allowed.", + "commands.racelist.inactive": "The allowlist is not active. Add races with /unicopia racelist allow to configure it.", "commands.racelist.disallowed": "Removed %1$s from the whitelist.", "commands.racelist.disallowed.failed": "%1$s is not on the whitelist.", @@ -1335,7 +1341,6 @@ "commands.worldtribe.success.get": "Default race for all new players is currently set to: %s", "commands.worldtribe.success.set": "Set default race for new players is now set to: %s", - "commands.disguise.usage": "/disguise [nbt]", "commands.disguise.notfound": "The entity id '%s' does not exist.", "commands.disguise.removed": "Your disguise has been removed.", "commands.disguise.removed.self": "Removed own disguise.", From 1b424a14453dd256412413e7e32b5e6590a385d3 Mon Sep 17 00:00:00 2001 From: LingVarr Date: Tue, 13 Feb 2024 14:44:12 +1100 Subject: [PATCH 11/23] Update ru_ru.json and READMEs --- README.md | 16 +++++++++------- README_RU.md | 13 ++++++++----- .../resources/assets/unicopia/lang/ru_ru.json | 15 ++++++++++----- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index ac0259f8..7980636f 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Unicorns, Pegasi, Earth Ponies, and even Changelings get their own special abili ### Manage your diet - Playing as a pony isn't all just kicking and zapping, though! As herbivores your food options open up to include + Playing as a pony isn't all just kicking and zapping, though! As herbivores, your food options open up to include a lot of items normal players don't usually get to eat. Feeling peckish? Try for some flowers from the meadow, or some hay! I hear the hay burgers of good, if you can find some oats. @@ -55,21 +55,23 @@ Unicorns, Pegasi, Earth Ponies, and even Changelings get their own special abili ### Natural Stuff - Airflow is simulated (badly) - Pegasi, beware about flying during storms! It can get dangerous out there! + + Pegasi, beware of flying during storms! It can get dangerous out there! If you're playing as a flying species, or just like having nice things, try building a weather vein. It shows the actual, totally real and not simulated badly, wind direction of your minecraft world. Just beware - that the direction and strength is situational (and bad), and will be different depending where you are and + that the direction and strength are situational (and bad), and will be different depending on where you are and how high up you are. - Hot air Rises + No, it's not a bad Star Wars movie, it's an actual mechanic. Sand and lava will give flying species extra lift. Water does the opposite. Try it! Actually don't, I don't want you to drown. ### Magic Items And Artifacts - - Craft and build s shrine for the Crystal Heart to provide valuable support to your friends - - Or give out bangles of comradery to your non-unicorn buddies so they can share in your powers, - or just so you can laugh when you teleport and they end up coming witht + - Craft and build a shrine for the Crystal Heart to provide valuable support to your friends + - Or give out bangles of comradery to your non-unicorn buddies, so they can share in your powers, + or just so you can laugh when you teleport and they end up coming with - Send and receive items using the Dragon's Breath Scroll - Possibly more I'm forgetting about (or am I? OoOoOooOOoo...Spooky surprise mechanics) @@ -86,7 +88,7 @@ View the HOW_TO_PLAY.md file for more details. ### 1.19.3 Only This project uses reach-entity-attributes, which may not be updated at the time of this writing. -If you building for 1.19.3, you may follow these steps to make sure it's available to git: +If you're building for 1.19.3, you may follow these steps to make sure it's available to git: `git clone https://github.com/Sollace/reach-entity-attributes` `cd reach-entity-attributes` diff --git a/README_RU.md b/README_RU.md index 92b40119..c141f52c 100644 --- a/README_RU.md +++ b/README_RU.md @@ -46,19 +46,22 @@ ### Понифицированные картины - Ведь что это был бы за пони-мод, если бы в нём не было этого? У каждой расы есть хотя бы один рисунок, представляющий её, + Ведь что это был бы за пони-мод, если бы в нём _не_ было этого? У каждой расы есть хотя бы один рисунок, представляющий её, так что покажите свою гордость и поднимите флаг! - Дисклеймер: Радужных флагов нет (пока) + Дисклеймер: Радужных флагов (пока) нет ### Природные явления - - Воздушный поток (плохо) влияет на пегасов, остерегайтесь летать во время грозы! Там может быть опасно! - Если вы играете за летающий вид или просто любите приятные вещи, попробуйте построить метеорологическую жилу. + - Воздушный поток + + Пегасы, остерегайтесь полётов во время грозы! Это может быть опасно! + Если вы играете за летающий вид или просто любите приятные вещи, попробуйте построить погодную жилу. Она показывает фактическое, абсолютно реальное, а не плохо смоделированное направление ветра в вашем мире Minecraft. Только учтите, - что направление и сила ветра ситуативны (и плохи), и будут отличаться в зависимости от того, где вы находитесь и на какой высоте. + что направление и сила ветра ситуативны, и будут отличаться в зависимости от того, где вы находитесь и на какой высоте. - Горячий воздух поднимает + Нет, это не плохой фильм про "Звездные войны", это реальная механика. Песок и лава придают летающим видам дополнительную подъёмную силу. Вода - наоборот. Попробуйте! А вообще, не стоит, я не хочу чтобы вы утонули. diff --git a/src/main/resources/assets/unicopia/lang/ru_ru.json b/src/main/resources/assets/unicopia/lang/ru_ru.json index c03f94db..96227648 100644 --- a/src/main/resources/assets/unicopia/lang/ru_ru.json +++ b/src/main/resources/assets/unicopia/lang/ru_ru.json @@ -699,6 +699,7 @@ "gui.unicopia.page_num": "%d из %d", "respawn.reason.joined_new_tribe": "%1$s был перерождён как %2$s", + "respawn.reason.illegal_race": "Раса %s не разрешена конфигурацией вашего сервера.", "gui.unicopia.tribe_selection.respawn": "Вы умерли.", "gui.unicopia.tribe_selection.respawn.journey": "Но конец - это еще не всё, потому что в конце каждого конца есть другое начало.", @@ -1326,16 +1327,20 @@ "commands.race.tell.other.alt": "%s - это ", "commands.racelist.illegal": "Раса %s по умолчанию не может быть использована этой командой.", - "commands.racelist.allowed": "Добавлен %1$s в белый список.", - "commands.racelist.allowed.failed": "%1$s уже внесён в белый список.", + "commands.racelist.allowed": "Раса %1$s добавлена в белый список.", + "commands.racelist.get.allowed": "Разрешены (%s):", + "commands.racelist.get.not_allowed": "Не разрешены (%s):", + "commands.racelist.get.list_item": "- %s (%s)", + "commands.racelist.clear.success": "Белый список отключён.", + "commands.racelist.allowed.failed": "Раса %1$s уже разрешена.", + "commands.racelist.inactive": "Список разрешений не активен. Добавьте расы с помощью /unicopia racelist allow , чтобы настроить его.", - "commands.racelist.disallowed": "Удален %1$s из белого списка.", - "commands.racelist.disallowed.failed": "%1$s отсутствует в белом списке.", + "commands.racelist.disallowed": "Раса %1$s удалена из белого списка.", + "commands.racelist.disallowed.failed": "Раса %1$s отсутствует в белом списке.", "commands.worldtribe.success.get": "Раса по умолчанию для всех новых игроков в настоящее время установлена на: %s.", "commands.worldtribe.success.set": "Установка расы по умолчанию для новых игроков теперь имеет значение: %s.", - "commands.disguise.usage": "/disguise [nbt]", "commands.disguise.notfound": "Идентификатор сущности \"%s\" не существует.", "commands.disguise.removed": "Ваша маскировка была удалена.", "commands.disguise.removed.self": "Удалена собственная маскировка.", From a072317f312555fc12d36ebef110399fb78b49ff Mon Sep 17 00:00:00 2001 From: Cryghast Date: Tue, 13 Feb 2024 23:36:24 +0800 Subject: [PATCH 12/23] update zh_cn.json --- src/main/resources/assets/unicopia/lang/zh_cn.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/resources/assets/unicopia/lang/zh_cn.json b/src/main/resources/assets/unicopia/lang/zh_cn.json index d0a2edd9..bbb43499 100644 --- a/src/main/resources/assets/unicopia/lang/zh_cn.json +++ b/src/main/resources/assets/unicopia/lang/zh_cn.json @@ -145,7 +145,7 @@ "item.unicopia.crispy_hay_fries": "酥脆炸草条", "item.unicopia.horse_shoe_fries": "炸蹄铁", "item.unicopia.wheat_worms": "小麦虫", - "item.unicopia.muffin": "马芬", + "item.unicopia.muffin": "玛芬", "item.unicopia.pegasus_amulet": "伊卡洛斯之翼", "item.unicopia.pegasus_amulet.lore": "让穿戴者享受短暂的飞行乐趣", @@ -699,6 +699,7 @@ "gui.unicopia.page_num": "第%d页(共%d页)", "respawn.reason.joined_new_tribe": "%1$s 以一只 %2$s 的身份重生了", + "respawn.reason.illegal_race": "%s这个种族目前不被你的服务器配置所允许", "gui.unicopia.tribe_selection.respawn": "你已经死了。", "gui.unicopia.tribe_selection.respawn.journey": "但结束不是全部,因为每个结束的尽头都是另一个开始。", @@ -1327,15 +1328,19 @@ "commands.racelist.illegal": "默认种族 %s 不适用于本指令。", "commands.racelist.allowed": "将 %1$s 加进白名单。", - "commands.racelist.allowed.failed": "%1$s 已在白名单中。", - + "commands.racelist.get.allowed": "允许 (%s):", + + "commands.racelist.get.not_allowed": "不允许 (%s):", + "commands.racelist.get.list_item": "- %s (%s)", + "commands.racelist.clear.success": "停用白名单", + "commands.racelist.allowed.failed": "%1$s 已经允许了", + "commands.racelist.inactive": "许可列表未激活。使用 /unicopia racelist allow 指令来添加种族和配置种族。", "commands.racelist.disallowed": "将 %1$s 从白名单移除。", "commands.racelist.disallowed.failed": "%1$s 并不在白名单中。", "commands.worldtribe.success.get": "目前,新玩家都将以 %s 的身份加入游戏", "commands.worldtribe.success.set": "已更改设置,这将使新玩家以 %s 的身份加入游戏", - "commands.disguise.usage": "/disguise <玩家名> <实体名> [nbt]", "commands.disguise.notfound": "实体id '%s' 并不存在。", "commands.disguise.removed": "你的伪装被移除了。", "commands.disguise.removed.self": "移除了自己的伪装。", From 2754d41ee9df544cedea3a5e82ce4d08975be9d3 Mon Sep 17 00:00:00 2001 From: Sollace Date: Tue, 13 Feb 2024 15:35:26 +0000 Subject: [PATCH 13/23] Fix dust appearing in the ground and fix clouds appearing black (use per-vertex lighting) --- .../particle/AbstractBillboardParticle.java | 1 + .../client/particle/DustCloudParticle.java | 45 +++++++++++-------- .../client/render/model/BakedModel.java | 8 +++- .../client/render/model/FanModel.java | 40 +++++++++++++++++ .../render/model/VertexLightSource.java | 44 ++++++++++++++++++ .../client/render/shader/UShaders.java | 13 ++---- .../render/spell/PortalFrameBuffer.java | 4 +- .../unicopia/entity/player/PlayerPhysics.java | 2 +- 8 files changed, 124 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/model/FanModel.java create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/model/VertexLightSource.java diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/AbstractBillboardParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/AbstractBillboardParticle.java index 02d624c6..52420762 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/AbstractBillboardParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/AbstractBillboardParticle.java @@ -27,6 +27,7 @@ public abstract class AbstractBillboardParticle extends AbstractGeometryBasedPar RenderSystem.disableCull(); RenderSystem.enableBlend(); RenderSystem.enableDepthTest(); + RenderSystem.defaultBlendFunc(); Vec3d cam = camera.getPos(); diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/DustCloudParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/DustCloudParticle.java index e39bb0b0..2e550c7a 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/DustCloudParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/DustCloudParticle.java @@ -1,11 +1,16 @@ package com.minelittlepony.unicopia.client.particle; +import org.joml.Vector4f; + import com.minelittlepony.common.util.Color; -import com.minelittlepony.unicopia.client.render.RenderUtil; +import com.minelittlepony.unicopia.client.render.model.FanModel; +import com.minelittlepony.unicopia.client.render.model.VertexLightSource; import net.minecraft.block.Blocks; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.BufferBuilder; import net.minecraft.client.render.Tessellator; +import net.minecraft.client.render.VertexFormat; +import net.minecraft.client.render.VertexFormats; import net.minecraft.client.texture.Sprite; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.world.ClientWorld; @@ -16,15 +21,15 @@ import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.RotationAxis; public class DustCloudParticle extends AbstractBillboardParticle { - //private static final Identifier TEXTURE = new Identifier("textures/particle/big_smoke_3.png"); - protected static final int SEGMENTS = 20; protected static final int SEPARATION = 270 / SEGMENTS; private float scaleFactor; protected Sprite sprite; - private final RenderUtil.Vertex[] vertices; + private final FanModel model; + + private final VertexLightSource lightSource; public DustCloudParticle(BlockStateParticleEffect effect, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) { super(world, x, y, z, velocityX, velocityY, velocityZ); @@ -33,14 +38,15 @@ public class DustCloudParticle extends AbstractBillboardParticle { red = 0.6F; green = 0.6F; blue = 0.6F; - alpha = (float)world.getRandom().nextTriangular(0.6, 0.2); + alpha = (float)world.getRandom().nextTriangular(0.6, 0.2) * 0.3F; scaleFactor = (float)world.getRandom().nextTriangular(2, 1.2); sprite = MinecraftClient.getInstance().getBlockRenderManager().getModels().getModelParticleSprite(effect.getBlockState()); - vertices = new RenderUtil.Vertex[]{ - new RenderUtil.Vertex(-1, -1, 0, sprite.getMinU(), sprite.getMinV()), - new RenderUtil.Vertex(-1, 1, 0, sprite.getMaxU(), sprite.getMinV()), - new RenderUtil.Vertex( 1, 1, 0, sprite.getMaxU(), sprite.getMaxV()), - new RenderUtil.Vertex( 1, -1, 0, sprite.getMinU(), sprite.getMaxV()) + lightSource = new VertexLightSource(world); + model = new FanModel(sprite) { + @Override + protected int getLightAt(Vector4f pos, int light) { + return lightSource.getLight(pos, light); + } }; if (!effect.getBlockState().isOf(Blocks.GRASS_BLOCK)) { int i = MinecraftClient.getInstance().getBlockColors().getColor(effect.getBlockState(), world, BlockPos.ofFloored(x, y, z), 0); @@ -60,26 +66,29 @@ public class DustCloudParticle extends AbstractBillboardParticle { super.tick(); scaleFactor += 0.001F; scale(MathHelper.clamp(age / 5F, 0, 1) * scaleFactor); + lightSource.tick(); } @Override protected void renderQuads(Tessellator te, BufferBuilder buffer, float x, float y, float z, float tickDelta) { - float scale = getScale(tickDelta); + float scale = getScale(tickDelta) * 0.5F; float alpha = this.alpha * (1 - ((float)age / maxAge)); MatrixStack matrices = new MatrixStack(); matrices.translate(x, y, z); - matrices.scale(scale, scale * 0.5F, scale); + matrices.scale(1, 0.5F, 1); - float angle = ((this.age + tickDelta) % 360) / SEGMENTS; + float angle = (MathHelper.sin((this.age + tickDelta) / 100F) * 360) / SEGMENTS; for (int i = 0; i < SEGMENTS; i++) { matrices.push(); - matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees((i * angle) % 360)); - matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees((SEPARATION * i + angle) % 360)); - matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees((SEPARATION * i + angle) % 360)); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees((i * angle))); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees((SEPARATION * i - angle))); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees((SEPARATION * i + angle))); float ringScale = 1 + MathHelper.sin(((i * 10) + age + tickDelta) * 0.05F) * 0.1F; - matrices.scale(ringScale, ringScale, ringScale); - renderQuad(matrices, te, buffer, vertices, alpha, tickDelta); + + buffer.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT); + model.render(matrices, buffer, 0, scale * ringScale, 1, 1, 1, alpha); + te.draw(); matrices.pop(); } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/model/BakedModel.java b/src/main/java/com/minelittlepony/unicopia/client/render/model/BakedModel.java index 3bceca54..58e65456 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/model/BakedModel.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/model/BakedModel.java @@ -50,7 +50,7 @@ public class BakedModel { textureMatrix.identity(); } - public final void render(MatrixStack matrices, VertexConsumer buffer, float scale, float r, float g, float b, float a) { + public final void render(MatrixStack matrices, VertexConsumer buffer, int light, float scale, float r, float g, float b, float a) { scale = Math.abs(scale); if (scale < 0.001F) { return; @@ -62,9 +62,13 @@ public class BakedModel { for (RenderUtil.Vertex vertex : vertices) { Vector4f pos = vertex.position(positionmatrix); Vector4f tex = vertex.texture(textureMatrix); - buffer.vertex(pos.x, pos.y, pos.z).texture(tex.x, tex.y).color(r, g, b, a).next(); + buffer.vertex(pos.x, pos.y, pos.z).texture(tex.x, tex.y).color(r, g, b, a).light(getLightAt(pos, light)).next(); } matrices.pop(); textureMatrix.identity(); } + + protected int getLightAt(Vector4f pos, int light) { + return light; + } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/model/FanModel.java b/src/main/java/com/minelittlepony/unicopia/client/render/model/FanModel.java new file mode 100644 index 00000000..29eaf2d2 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/model/FanModel.java @@ -0,0 +1,40 @@ +package com.minelittlepony.unicopia.client.render.model; + +import com.minelittlepony.unicopia.client.render.RenderUtil; + +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.RotationAxis; + +public class FanModel extends BakedModel { + + public FanModel(Sprite sprite) { + RenderUtil.Vertex[] dorito = createDorito(sprite); + MatrixStack matrices = new MatrixStack(); + for (int d = 0; d < 12; d++) { + matrices.push(); + + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(30 * d)); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(15)); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(15 * d)); + matrices.translate(2.9F, 0, 0); + for (RenderUtil.Vertex corner : dorito) { + var position = corner.position(matrices.peek().getPositionMatrix()); + addVertex(position.x, position.y(), position.z(), corner.texture().x, corner.texture().y); + } + matrices.pop(); + } + } + + static RenderUtil.Vertex[] createDorito(Sprite sprite) { + float chunkSize = 1F; + float baseLength = 0.8F; + float uLength = sprite.getMaxU() - sprite.getMinU(); + return new RenderUtil.Vertex[]{ + new RenderUtil.Vertex(-chunkSize, -chunkSize * baseLength, 0, sprite.getMinU() + uLength * baseLength, sprite.getMinV()), + new RenderUtil.Vertex( chunkSize, 0, 0, sprite.getMaxU(), sprite.getMaxV()), + new RenderUtil.Vertex(-chunkSize, chunkSize * baseLength, 0, sprite.getMinU(), sprite.getMinV()), + new RenderUtil.Vertex(-chunkSize * 3, 0, 0, sprite.getMinU(), sprite.getMaxV()) + }; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/model/VertexLightSource.java b/src/main/java/com/minelittlepony/unicopia/client/render/model/VertexLightSource.java new file mode 100644 index 00000000..86184540 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/model/VertexLightSource.java @@ -0,0 +1,44 @@ +package com.minelittlepony.unicopia.client.render.model; + +import org.joml.Vector4f; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; + +public class VertexLightSource { + private final ClientWorld world; + private final Long2ObjectMap lightCache = new Long2ObjectOpenHashMap<>(); + + public VertexLightSource(ClientWorld world) { + this.world = world; + } + + public void tick() { + lightCache.clear(); + } + + public int getLight(Vector4f vertexPosition, int light) { + return lightCache.computeIfAbsent(getBlockPosition(vertexPosition), this::getLight); + } + + @SuppressWarnings("deprecation") + private int getLight(long p) { + final BlockPos pos = BlockPos.fromLong(p); + return world.isChunkLoaded(pos) ? WorldRenderer.getLightmapCoordinates(world, pos) : 0; + } + + private long getBlockPosition(Vector4f vertexPosition) { + Vec3d cameraPos = MinecraftClient.getInstance().gameRenderer.getCamera().getPos(); + return BlockPos.asLong( + MathHelper.floor(cameraPos.x + vertexPosition.x), + MathHelper.floor(cameraPos.y + vertexPosition.y), + MathHelper.floor(cameraPos.z + vertexPosition.z) + ); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/shader/UShaders.java b/src/main/java/com/minelittlepony/unicopia/client/render/shader/UShaders.java index fd86786d..9a7d3cc7 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/shader/UShaders.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/shader/UShaders.java @@ -3,23 +3,16 @@ package com.minelittlepony.unicopia.client.render.shader; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; -import org.jetbrains.annotations.Nullable; - import com.minelittlepony.unicopia.Unicopia; import net.fabricmc.fabric.api.client.rendering.v1.CoreShaderRegistrationCallback; import net.minecraft.client.gl.ShaderProgram; import net.minecraft.client.render.VertexFormat; import net.minecraft.client.render.VertexFormats; -public final class UShaders { - @Nullable - private static Supplier renderTypePortalSurfaceProgram = register("rendertype_portal_surface", VertexFormats.POSITION_COLOR); +public interface UShaders { + Supplier RENDER_TYPE_PORTAL_SURFACE = register("rendertype_portal_surface", VertexFormats.POSITION_COLOR); - public static ShaderProgram getRenderTypePortalSurfaceProgram() { - return renderTypePortalSurfaceProgram.get(); - } - - public static void bootstrap() { } + static void bootstrap() { } static Supplier register(String name, VertexFormat format) { AtomicReference holder = new AtomicReference<>(); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalFrameBuffer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalFrameBuffer.java index 823220f5..e594b647 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalFrameBuffer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalFrameBuffer.java @@ -94,14 +94,14 @@ class PortalFrameBuffer implements AutoCloseable { BufferBuilder buffer = tessellator.getBuffer(); float uScale = (float)framebuffer.viewportWidth / (float)framebuffer.textureWidth; float vScale = (float)framebuffer.viewportHeight / (float)framebuffer.textureHeight; - RenderSystem.setShader(UShaders::getRenderTypePortalSurfaceProgram); + RenderSystem.setShader(UShaders.RENDER_TYPE_PORTAL_SURFACE); //RenderSystem.setShader(GameRenderer::getPositionTexColorProgram); RenderSystem._setShaderTexture(0, framebuffer.getColorAttachment()); buffer.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR); SphereModel.DISK.scaleUV(uScale, vScale); RenderSystem.setTextureMatrix(SphereModel.DISK.getTextureMatrix()); - SphereModel.DISK.render(matrices, buffer, 2F, 1, 1, 1, 1); + SphereModel.DISK.render(matrices, buffer, 1, 2F, 1, 1, 1, 1); tessellator.draw(); client.getTextureManager().bindTexture(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java index e1a6cd75..971f8b26 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java @@ -567,7 +567,7 @@ public class PlayerPhysics extends EntityPhysics implements Tickab if (entity.isOnGround() || !force) { BlockState steppingState = pony.asEntity().getSteppingBlockState(); if (steppingState.isIn(UTags.KICKS_UP_DUST)) { - pony.addParticle(new BlockStateParticleEffect(UParticles.DUST_CLOUD, steppingState), pony.getOrigin().down().toCenterPos(), Vec3d.ZERO); + pony.addParticle(new BlockStateParticleEffect(UParticles.DUST_CLOUD, steppingState), pony.getOrigin().toCenterPos(), Vec3d.ZERO); } else { Supplier pos = VecHelper.sphere(pony.asWorld().getRandom(), 0.5D); Supplier vel = VecHelper.sphere(pony.asWorld().getRandom(), 0.015D); From 92fd22668ec0a3792b8eddfca93bf381ce6a8fbf Mon Sep 17 00:00:00 2001 From: Sollace Date: Tue, 13 Feb 2024 17:24:05 +0000 Subject: [PATCH 14/23] Smooth motion due to airflow and wind. Should fix any bumps and sudden changes in direction/altitude when flying and overall make controlling whilst flying more manageable --- .../unicopia/entity/player/PlayerPhysics.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java index 971f8b26..2b4b93d0 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java @@ -88,6 +88,9 @@ public class PlayerPhysics extends EntityPhysics implements Tickab private final Pony pony; + private Lerp updraft = new Lerp(0); + private Lerp windStrength = new Lerp(0); + public PlayerPhysics(Pony pony) { super(pony.asEntity(), Creature.GRAVITY); this.pony = pony; @@ -364,6 +367,8 @@ public class PlayerPhysics extends EntityPhysics implements Tickab soundPlaying = false; descentRate = 0; ticksDiving = 0; + updraft.update(0, 100); + windStrength.update(0, 100); if (Abilities.RAINBOOM.canUse(pony.getCompositeRace()) && entity.isOnGround()) { pony.getMagicalReserves().getCharge().set(0); @@ -376,6 +381,8 @@ public class PlayerPhysics extends EntityPhysics implements Tickab } else { descentRate = 0; soundPlaying = false; + updraft.update(0, 100); + windStrength.update(0, 100); } if (!entity.isOnGround()) { @@ -623,12 +630,13 @@ public class PlayerPhysics extends EntityPhysics implements Tickab velocity.x += - forward * MathHelper.sin(entity.getYaw() * 0.017453292F); velocity.z += forward * MathHelper.cos(entity.getYaw() * 0.017453292F); - if (entity.getWorld().hasRain(entity.getBlockPos())) { applyTurbulance(velocity); } else { - double updraft = WeatherConditions.getUpdraft(new BlockPos.Mutable().set(entity.getBlockPos()), entity.getWorld()) / 3F; - updraft *= 1 + motion; + float targetUpdraft = (float)WeatherConditions.getUpdraft(new BlockPos.Mutable().set(entity.getBlockPos()), entity.getWorld()) / 3F; + targetUpdraft *= 1 + motion; + this.updraft.update(targetUpdraft, targetUpdraft > this.updraft.getTarget() ? 30_000 : 3000); + double updraft = this.updraft.getValue(); velocity.y += updraft; descentRate -= updraft; } @@ -702,19 +710,25 @@ public class PlayerPhysics extends EntityPhysics implements Tickab .multiply(globalEffectStrength / 100D) .multiply(1 / (1 + Math.floor(pony.getLevel().get() / 10F))); + + + + if (effectStrength * gust.getX() >= 1) { SoundEmitter.playSoundAt(entity, USounds.AMBIENT_WIND_GUST, SoundCategory.AMBIENT, 3, 1); } float weight = 1 + (EnchantmentHelper.getEquipmentLevel(UEnchantments.HEAVY, entity) * 0.8F) + (pony.getCompositeRace().canUseEarth() ? 1 : 0); - velocity.add(WeatherConditions.getAirflow(entity.getBlockPos(), entity.getWorld()), 0.04F * effectStrength); - velocity.add(Vec3d.fromPolar( - (entity.getPitch() + (float)gust.getY()) * MathHelper.RADIANS_PER_DEGREE, - (entity.getYaw() + (float)gust.getZ()) * MathHelper.RADIANS_PER_DEGREE - ), - effectStrength * (float)gust.getX() / weight - ); + Vec3d airflow = WeatherConditions.getAirflow(entity.getBlockPos(), entity.getWorld()) + .multiply(0.04F * effectStrength) + .add(Vec3d.fromPolar( + (entity.getPitch() + (float)gust.getY()) * MathHelper.RADIANS_PER_DEGREE, + (entity.getYaw() + (float)gust.getZ()) * MathHelper.RADIANS_PER_DEGREE + ).multiply(effectStrength * (float)gust.getX() / weight)); + + windStrength.update((float)airflow.length(), airflow.length() > windStrength.getValue() ? 1000 : 500); + velocity.add(airflow.normalize(), windStrength.getValue()); if (!entity.getWorld().isClient && effectStrength > 0.9F && entity.getWorld().isThundering() && entity.getWorld().random.nextInt(9000) == 0) { LightningEntity lightning = EntityType.LIGHTNING_BOLT.create(entity.getWorld()); @@ -794,6 +808,7 @@ public class PlayerPhysics extends EntityPhysics implements Tickab compound.putBoolean("isFlyingEither", isFlyingEither); compound.putInt("ticksInAir", ticksInAir); compound.putFloat("descentRate", descentRate); + compound.putFloat("updraft", updraft.getValue()); } @Override @@ -804,6 +819,7 @@ public class PlayerPhysics extends EntityPhysics implements Tickab isFlyingEither = compound.getBoolean("isFlyingEither"); ticksInAir = compound.getInt("ticksInAir"); descentRate = compound.getFloat("descentRate"); + updraft.update(compound.getFloat("updraft"), 0); entity.calculateDimensions(); } From e1d4b229ffd3df3a887d8015bce9a86ada02d63d Mon Sep 17 00:00:00 2001 From: Sollace Date: Tue, 13 Feb 2024 18:46:36 +0000 Subject: [PATCH 15/23] Fixed spells not rendering on disguised players --- .../unicopia/client/render/EntityDisguiseRenderer.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java index e130258c..19fe7fcc 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java @@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.client.render; import org.jetbrains.annotations.Nullable; +import com.minelittlepony.unicopia.client.render.spell.SpellEffectsRenderDispatcher; import com.minelittlepony.unicopia.compat.pehkui.PehkUtil; import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.behaviour.Disguise; @@ -58,6 +59,11 @@ class EntityDisguiseRenderer { PehkUtil.clearScale(ee); }); + matrices.push(); + matrices.translate(x, y, z); + SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertexConsumers, light, pony, 0, 0, tickDelta, pony.asEntity().age + tickDelta, 0, 0); + matrices.pop(); + delegate.afterEntityRender(pony, matrices, light); PehkUtil.clearScale(e); return true; From 91b8b827a696a81cedff8077a866207f06b009e5 Mon Sep 17 00:00:00 2001 From: Sollace Date: Tue, 13 Feb 2024 18:47:01 +0000 Subject: [PATCH 16/23] Damage from disguises is now passed through to their hosts --- .../unicopia/entity/Living.java | 22 ++++++++++++++++++ .../entity/behaviour/EndermanBehaviour.java | 10 ++++++++ .../entity/behaviour/EntityAppearance.java | 8 +++++-- .../unicopia/entity/behaviour/Guest.java | 9 ++++++++ .../entity/duck/LivingEntityDuck.java | 4 +++- .../unicopia/mixin/MixinLivingEntity.java | 23 +++++++++++++++++++ 6 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/entity/behaviour/Guest.java diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Living.java b/src/main/java/com/minelittlepony/unicopia/entity/Living.java index f83c07d9..0615b9da 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Living.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Living.java @@ -18,6 +18,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; +import com.minelittlepony.unicopia.entity.behaviour.Guest; import com.minelittlepony.unicopia.entity.collision.MultiBoundingBoxEntity; import com.minelittlepony.unicopia.entity.damage.MagicalDamageSource; import com.minelittlepony.unicopia.entity.duck.LivingEntityDuck; @@ -36,6 +37,7 @@ import com.minelittlepony.unicopia.server.world.DragonBreathStore; import com.minelittlepony.unicopia.util.*; import it.unimi.dsi.fastutil.floats.Float2ObjectFunction; +import net.fabricmc.fabric.api.util.TriState; import net.minecraft.block.BlockState; import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.EnchantmentHelper; @@ -44,6 +46,7 @@ import net.minecraft.entity.attribute.EntityAttribute; import net.minecraft.entity.attribute.EntityAttributeInstance; import net.minecraft.entity.attribute.EntityAttributeModifier; import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.damage.DamageTypes; import net.minecraft.entity.data.*; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.projectile.ProjectileEntity; @@ -92,6 +95,8 @@ public abstract class Living implements Equine, Caste @Nullable private Caster attacker; + @Nullable + private transient Caster host; private Optional> target = Optional.empty(); @@ -478,6 +483,19 @@ public abstract class Living implements Equine, Caste public Optional onDamage(DamageSource source, float amount) { + if ((source.getAttacker() instanceof Guest guest && guest.getHost() instanceof Living l && l == this) + || (source.getSource() instanceof Guest guest && guest.getHost() instanceof Living l && l == this)) { + var type = source.getTypeRegistryEntry(); + return Optional.of(entity.damage( + type.matchesKey(DamageTypes.FIREBALL) ? entity.getDamageSources().create(DamageTypes.UNATTRIBUTED_FIREBALL) : + type.matchesKey(DamageTypes.PLAYER_EXPLOSION) ? entity.getDamageSources().create(DamageTypes.EXPLOSION) : + new DamageSource(type, entity, entity), amount)); + } + + if (entity instanceof Guest guest && guest.getHost() instanceof Living l) { + l.asEntity().damage(source, amount); + } + if (source.isIn(DamageTypeTags.IS_LIGHTNING) && (invinsibilityTicks > 0 || tryCaptureLightning())) { return Optional.of(false); } @@ -502,6 +520,10 @@ public abstract class Living implements Equine, Caste return Optional.empty(); } + public TriState canBeHurtByWater() { + return TriState.DEFAULT; + } + public Optional chooseClimbingPos() { return getSpellSlot().get(SpellPredicate.IS_DISGUISE, false) .map(AbstractDisguiseSpell::getDisguise) diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EndermanBehaviour.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EndermanBehaviour.java index 1e1562a6..e18f3761 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EndermanBehaviour.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EndermanBehaviour.java @@ -5,11 +5,13 @@ import com.minelittlepony.unicopia.entity.Living; import net.minecraft.entity.mob.EndermanEntity; import net.minecraft.item.BlockItem; import net.minecraft.item.ItemStack; +import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.util.Hand; public class EndermanBehaviour extends EntityBehaviour { @Override public void update(Living source, EndermanEntity entity, Disguise spell) { + entity.setInvulnerable(!EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR.test(source.asEntity())); if (source.asEntity().isSneaking() || source.asEntity().isSprinting()) { entity.setTarget(entity); } else { @@ -22,5 +24,13 @@ public class EndermanBehaviour extends EntityBehaviour { } else { entity.setCarriedBlock(null); } + + //if (entity.hurtTime > 0) { + /* Vec3d teleportedPos = entity.getPos(); + + if (!teleportedPos.equals(source.asEntity().getPos())) { + source.asEntity().refreshPositionAfterTeleport(teleportedPos.x, teleportedPos.y, teleportedPos.z); + }*/ + //} } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java index 7a5d4c96..4cd359e4 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java @@ -205,8 +205,12 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi return; } - if (entity instanceof LivingEntity) { - ((LivingEntity) entity).getAttributeInstance(UEntityAttributes.ENTITY_GRAVITY_MODIFIER).clearModifiers(); + if (entity instanceof LivingEntity l) { + l.getAttributeInstance(UEntityAttributes.ENTITY_GRAVITY_MODIFIER).clearModifiers(); + } + + if (entity instanceof Guest guest) { + guest.setHost(source); } if (source.isClient()) { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Guest.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Guest.java new file mode 100644 index 00000000..3615b589 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Guest.java @@ -0,0 +1,9 @@ +package com.minelittlepony.unicopia.entity.behaviour; + +import com.minelittlepony.unicopia.ability.magic.Caster; + +public interface Guest { + void setHost(Caster host); + + Caster getHost(); +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/duck/LivingEntityDuck.java b/src/main/java/com/minelittlepony/unicopia/entity/duck/LivingEntityDuck.java index 850e673e..f126df85 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/duck/LivingEntityDuck.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/duck/LivingEntityDuck.java @@ -1,11 +1,13 @@ package com.minelittlepony.unicopia.entity.duck; +import com.minelittlepony.unicopia.entity.behaviour.Guest; + import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemStack; import net.minecraft.util.Hand; import net.minecraft.util.math.Vec3d; -public interface LivingEntityDuck { +public interface LivingEntityDuck extends Guest { void updateItemUsage(Hand hand, ItemStack stack, int time); boolean isJumping(); diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java index 27d286c6..2fb6a58d 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java @@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.mixin; import java.util.Optional; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.injection.At; @@ -18,6 +19,7 @@ import com.minelittlepony.unicopia.entity.*; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; import com.minelittlepony.unicopia.entity.duck.*; +import net.fabricmc.fabric.api.util.TriState; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.attribute.DefaultAttributeContainer; @@ -39,6 +41,8 @@ abstract class MixinLivingEntity extends Entity implements LivingEntityDuck, Equ private Optional climbingPos; private Equine caster; + @Nullable + private transient Caster host; private MixinLivingEntity() { super(null, null); } @@ -58,6 +62,17 @@ abstract class MixinLivingEntity extends Entity implements LivingEntityDuck, Equ return (Living)caster; } + @Override + @Nullable + public Caster getHost() { + return host; + } + + @Override + public void setHost(Caster host) { + this.host = host; + } + @Override @Accessor("jumping") public abstract boolean isJumping(); @@ -157,6 +172,14 @@ abstract class MixinLivingEntity extends Entity implements LivingEntityDuck, Equ get().onDamage(source, amount).ifPresent(info::setReturnValue); } + @Inject(method = "hurtByWater()Z", at = @At("HEAD"), cancellable = true) + private void onCanBeHurtByWater(CallbackInfoReturnable info) { + TriState hurtByWater = get().canBeHurtByWater(); + if (hurtByWater != TriState.DEFAULT) { + info.setReturnValue(hurtByWater.get()); + } + } + @Inject(method = "writeCustomDataToNbt(Lnet/minecraft/nbt/NbtCompound;)V", at = @At("HEAD")) private void onWriteCustomDataToTag(NbtCompound tag, CallbackInfo info) { tag.put("unicopia_caster", get().toNBT()); From 9b3cb7944acd33bcb88c9e04a4e833b1d8a68a21 Mon Sep 17 00:00:00 2001 From: Sollace Date: Tue, 13 Feb 2024 21:58:19 +0000 Subject: [PATCH 17/23] Improve the /unicopia racelist command and give better feedback when it is enabled or disabled --- .../minelittlepony/unicopia/AllowList.java | 57 +++++++++++++++++++ .../com/minelittlepony/unicopia/Race.java | 7 +-- .../unicopia/command/RacelistCommand.java | 44 +++++--------- .../resources/assets/unicopia/lang/en_us.json | 15 +++-- 4 files changed, 82 insertions(+), 41 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/AllowList.java diff --git a/src/main/java/com/minelittlepony/unicopia/AllowList.java b/src/main/java/com/minelittlepony/unicopia/AllowList.java new file mode 100644 index 00000000..f1810cd1 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/AllowList.java @@ -0,0 +1,57 @@ +package com.minelittlepony.unicopia; + +import java.util.Set; + +public class AllowList { + public static final AllowList INSTANCE = new AllowList(); + + public AllowList() { + + } + + public boolean disable() { + if (!isEnabled()) { + return false; + } + Unicopia.getConfig().speciesWhiteList.get().clear(); + Unicopia.getConfig().save(); + return true; + } + + public boolean isEnabled() { + return !Unicopia.getConfig().speciesWhiteList.get().isEmpty(); + } + + public boolean add(Race race) { + if (race.isUnset() || race.isHuman()) { + return false; + } + Set values = Unicopia.getConfig().speciesWhiteList.get(); + boolean added = values.add(race.getId().toString()); + Unicopia.getConfig().save(); + return added; + } + + public boolean remove(Race race) { + Set values = Unicopia.getConfig().speciesWhiteList.get(); + if (values.isEmpty()) { + for (Race r : Race.REGISTRY) { + if (!r.isUnset() && r != race) { + values.add(r.getId().toString()); + } + } + Unicopia.getConfig().save(); + return true; + } + boolean removed = values.remove(race.getId().toString()); + Unicopia.getConfig().save(); + return removed; + } + + public boolean permits(Race race) { + return race.isUnset() + || race.isHuman() + || !isEnabled() + || Unicopia.getConfig().speciesWhiteList.get().contains(race.getId().toString()); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/Race.java b/src/main/java/com/minelittlepony/unicopia/Race.java index 72ae4d08..a6d73d72 100644 --- a/src/main/java/com/minelittlepony/unicopia/Race.java +++ b/src/main/java/com/minelittlepony/unicopia/Race.java @@ -134,12 +134,7 @@ public record Race (Supplier compositeSupplier, Availability availabi } public boolean isPermitted(@Nullable PlayerEntity sender) { - Set whitelist = Unicopia.getConfig().speciesWhiteList.get(); - - return this == HUMAN - || isUnset() - || whitelist.isEmpty() - || whitelist.contains(getId().toString()); + return AllowList.INSTANCE.permits(this); } public Race validate(PlayerEntity sender) { diff --git a/src/main/java/com/minelittlepony/unicopia/command/RacelistCommand.java b/src/main/java/com/minelittlepony/unicopia/command/RacelistCommand.java index 30a19dd0..00d776a8 100644 --- a/src/main/java/com/minelittlepony/unicopia/command/RacelistCommand.java +++ b/src/main/java/com/minelittlepony/unicopia/command/RacelistCommand.java @@ -21,14 +21,13 @@ class RacelistCommand { .then(CommandManager.literal("show") .executes(context -> { context.getSource().sendFeedback(() -> { - Set whitelist = Unicopia.getConfig().speciesWhiteList.get(); - if (whitelist.isEmpty()) { + if (!AllowList.INSTANCE.isEnabled()) { return Text.translatable("commands.racelist.inactive"); } Set allowed = new HashSet<>(); Set unallowed = new HashSet<>(); Race.REGISTRY.forEach(race -> { - (race.isPermitted(null) ? allowed : unallowed).add(Text.translatable("commands.racelist.get.list_item", + (AllowList.INSTANCE.permits(race) ? allowed : unallowed).add(Text.translatable("commands.racelist.get.list_item", race.getDisplayName(), Text.literal(race.getId().toString()).formatted(Formatting.GRAY) )); @@ -45,44 +44,32 @@ class RacelistCommand { ) .then(CommandManager.literal("reset") .executes(context -> { - Unicopia.getConfig().speciesWhiteList.get().clear(); - Unicopia.getConfig().save(); - context.getSource().sendFeedback(() -> Text.translatable("commands.racelist.clear.success").formatted(Formatting.GREEN), false); + boolean success = AllowList.INSTANCE.disable(); + context.getSource().sendFeedback(() -> Text.translatable("commands.racelist.reset." + (success ? "success" : "fail")).formatted(Formatting.YELLOW), false); return 0; }) ) .then(CommandManager.literal("allow") .then(CommandManager.argument("race", Race.argument()).suggests(UCommandSuggestion.ALL_RACE_SUGGESTIONS) - .executes(context -> toggle(context.getSource(), context.getSource().getPlayer(), Race.fromArgument(context, "race"), "allowed", race -> { - - if (race.isUnset()) { - return false; - } - - boolean result = Unicopia.getConfig().speciesWhiteList.get().add(race.getId().toString()); - - Unicopia.getConfig().save(); - - return result; - })) + .executes(context -> toggle(context.getSource(), context.getSource().getPlayer(), Race.fromArgument(context, "race"), "allowed", AllowList.INSTANCE::add)) )) .then(CommandManager.literal("disallow") .then(CommandManager.argument("race", Race.argument()).suggests(UCommandSuggestion.ALL_RACE_SUGGESTIONS) - .executes(context -> toggle(context.getSource(), context.getSource().getPlayer(), Race.fromArgument(context, "race"), "disallowed", race -> { - boolean result = Unicopia.getConfig().speciesWhiteList.get().remove(race.getId().toString()); - - Unicopia.getConfig().save(); - - return result; - })) + .executes(context -> toggle(context.getSource(), context.getSource().getPlayer(), Race.fromArgument(context, "race"), "disallowed", AllowList.INSTANCE::remove)) )); } static int toggle(ServerCommandSource source, ServerPlayerEntity player, Race race, String action, Function func) { + boolean enabled = AllowList.INSTANCE.isEnabled(); + boolean success = func.apply(race); + + if (enabled != AllowList.INSTANCE.isEnabled()) { + source.sendFeedback(() -> Text.translatable("commands.racelist." + (enabled ? "disabled" : "enabled")).formatted(enabled ? Formatting.RED : Formatting.GREEN), false); + } + source.sendFeedback(() -> { String translationKey = "commands.racelist." + action; - - if (!func.apply(race)) { + if (!success) { if (race.isUnset()) { translationKey = "commands.racelist.illegal"; } else { @@ -90,8 +77,7 @@ class RacelistCommand { } } - Text formattedName = race.getDisplayName().copy().formatted(Formatting.GOLD); - return Text.translatable(translationKey, formattedName).formatted(Formatting.GREEN); + return Text.translatable(translationKey, race.getDisplayName().copy().formatted(Formatting.GOLD)).formatted(success ? Formatting.GREEN : Formatting.RED); }, false); return 0; } diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index ff4debfb..24d06e29 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -1327,16 +1327,19 @@ "commands.race.tell.other.alt": "%s is an ", "commands.racelist.illegal": "The default race %s cannot be used with this command.", - "commands.racelist.allowed": "Added %1$s to the whitelist.", + "commands.racelist.get.allowed": "Allowed (%s):", "commands.racelist.get.not_allowed": "Not Allowed (%s):", "commands.racelist.get.list_item": "- %s (%s)", - "commands.racelist.clear.success": "Disabled Whitelist", + "commands.racelist.reset.success": "Cleared and disabled allow list.", + "commands.racelist.reset.fail": "The allow list is not active. Doing nothing.", + "commands.racelist.inactive": "The allow list is not active. Add races with /unicopia racelist to configure it.", + "commands.racelist.enabled": "Enabled allow list", + "commands.racelist.disabled": "Disabled allow list", + "commands.racelist.allowed": "Added %1$s to the allow list.", "commands.racelist.allowed.failed": "%1$s is already allowed.", - "commands.racelist.inactive": "The allowlist is not active. Add races with /unicopia racelist allow to configure it.", - - "commands.racelist.disallowed": "Removed %1$s from the whitelist.", - "commands.racelist.disallowed.failed": "%1$s is not on the whitelist.", + "commands.racelist.disallowed": "Removed %1$s from the allow list.", + "commands.racelist.disallowed.failed": "%1$s is already not allowed.", "commands.worldtribe.success.get": "Default race for all new players is currently set to: %s", "commands.worldtribe.success.set": "Set default race for new players is now set to: %s", From e7e88c480565e39cbba89bbaa5a1270542ba7dd7 Mon Sep 17 00:00:00 2001 From: Sollace Date: Tue, 13 Feb 2024 21:58:37 +0000 Subject: [PATCH 18/23] Fixed being able to select disabled races --- .../unicopia/client/gui/TribeConfirmationScreen.java | 9 +++++++++ .../unicopia/client/gui/TribeSelectionScreen.java | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/TribeConfirmationScreen.java b/src/main/java/com/minelittlepony/unicopia/client/gui/TribeConfirmationScreen.java index bc8ea6a8..a4a82e64 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/TribeConfirmationScreen.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/TribeConfirmationScreen.java @@ -28,6 +28,9 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud { @Override protected void init() { + if (parent != null) { + parent.init(client, width, height); + } final int columnHeight = 167; final int columnWidth = 310; final int padding = 15; @@ -101,6 +104,12 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud { @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { + if (parent != null) { + context.getMatrices().push(); + context.getMatrices().translate(0, 0, -100); + parent.render(context, 0, 0, delta); + context.getMatrices().pop(); + } renderBackground(context); final int columnHeight = 180; diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/TribeSelectionScreen.java b/src/main/java/com/minelittlepony/unicopia/client/gui/TribeSelectionScreen.java index 6264eff9..3f046085 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/TribeSelectionScreen.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/TribeSelectionScreen.java @@ -157,7 +157,9 @@ public class TribeSelectionScreen extends GameGui implements HidesHud { return true; } if (keyCode == GLFW.GLFW_KEY_ENTER) { - options.get(SELECTION).onPress(); + if (options.get(SELECTION).active) { + options.get(SELECTION).onPress(); + } } return super.keyPressed(keyCode, scanCode, modifiers); From e69749300d936055aa0d9c6c30fadacfbf83a561 Mon Sep 17 00:00:00 2001 From: Sollace Date: Tue, 13 Feb 2024 23:51:21 +0000 Subject: [PATCH 19/23] Disguises now render arms in first person --- .../unicopia/client/URenderers.java | 2 +- .../unicopia/client/minelittlepony/Main.java | 6 + .../render/DisguisedArmsFeatureRenderer.java | 122 ++++++++++++++++++ .../client/render/EntityDisguiseRenderer.java | 5 +- .../entity/behaviour/EntityBehaviour.java | 3 +- .../entity/behaviour/IronGolemBehaviour.java | 18 +++ .../entity/behaviour/MobBehaviour.java | 10 ++ 7 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/DisguisedArmsFeatureRenderer.java create mode 100644 src/main/java/com/minelittlepony/unicopia/entity/behaviour/IronGolemBehaviour.java diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index e7e15b4e..4f436d79 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -89,7 +89,7 @@ public interface URenderers { AccessoryFeatureRenderer.register( BraceletFeatureRenderer::new, AmuletFeatureRenderer::new, GlassesFeatureRenderer::new, WingsFeatureRenderer::new, HornFeatureRenderer::new, IcarusWingsFeatureRenderer::new, BatWingsFeatureRenderer::new, - HeldEntityFeatureRenderer::new + HeldEntityFeatureRenderer::new, DisguisedArmsFeatureRenderer::new ); EntityRendererRegistry.register(UEntities.THROWN_ITEM, FlyingItemEntityRenderer::new); diff --git a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java index 1f6537c3..abb6659e 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java +++ b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java @@ -11,6 +11,7 @@ import com.minelittlepony.api.model.fabric.PonyModelPrepareCallback; import com.minelittlepony.api.model.gear.IGear; import com.minelittlepony.api.pony.IPony; import com.minelittlepony.api.pony.IPonyData; +import com.minelittlepony.client.render.MobRenderers; import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate; @@ -19,6 +20,7 @@ import com.minelittlepony.unicopia.util.AnimationUtil; import net.fabricmc.api.ClientModInitializer; import net.minecraft.entity.Entity; +import net.minecraft.entity.passive.AllayEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; @@ -106,6 +108,10 @@ public class Main extends MineLPDelegate implements ClientModInitializer { @Override public Race getRace(Entity entity) { + if (entity instanceof AllayEntity) { + return MobRenderers.ALLAY.get() ? Race.PEGASUS : Race.HUMAN; + } + return IPony.getManager().getPony(entity).map(IPony::race).map(Main::toUnicopiaRace).orElse(Race.UNSET); } diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/DisguisedArmsFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/DisguisedArmsFeatureRenderer.java new file mode 100644 index 00000000..9393262e --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/DisguisedArmsFeatureRenderer.java @@ -0,0 +1,122 @@ +package com.minelittlepony.unicopia.client.render; + +import com.google.common.base.MoreObjects; +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; +import com.minelittlepony.unicopia.client.FirstPersonRendererOverrides.ArmRenderer; +import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate; +import com.minelittlepony.unicopia.entity.behaviour.Disguise; +import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; +import com.mojang.blaze3d.systems.RenderSystem; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.model.ModelPart; +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.mob.ZombieEntity; +import net.minecraft.util.Arm; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.RotationAxis; + +public class DisguisedArmsFeatureRenderer implements AccessoryFeatureRenderer.Feature { + + private final MinecraftClient client = MinecraftClient.getInstance(); + + public DisguisedArmsFeatureRenderer(FeatureRendererContext> context) { + + } + + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, E entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + } + + @Override + public boolean beforeRenderArms(ArmRenderer sender, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, E entity, int light) { + Entity appearance = getAppearance(entity); + if (appearance instanceof LivingEntity l) { + float swingProgress = entity.getHandSwingProgress(MinecraftClient.getInstance().getTickDelta()); + + Hand hand = MoreObjects.firstNonNull(entity.preferredHand, Hand.MAIN_HAND); + + //renderArmHoldingItem(l, matrices, vertexConsumers, light, 0, swingProgress, Arm.RIGHT); + + boolean bothHands = l instanceof ZombieEntity; + + if (bothHands || hand == Hand.MAIN_HAND) { + if (entity.getMainHandStack().isEmpty()) { + matrices.push(); + renderArmHoldingItem(l, matrices, vertexConsumers, light, 1 - sender.getEquipProgress(Hand.MAIN_HAND, tickDelta), hand == Hand.MAIN_HAND ? swingProgress : 0, entity.getMainArm()); + matrices.pop(); + } + } + + if (bothHands || hand == Hand.OFF_HAND) { + if (entity.getOffHandStack().isEmpty()) { + matrices.push(); + renderArmHoldingItem(l, matrices, vertexConsumers, light, 1 - sender.getEquipProgress(Hand.OFF_HAND, tickDelta), hand == Hand.OFF_HAND ? swingProgress : 0, entity.getMainArm().getOpposite()); + matrices.pop(); + } + } + } + + return false; + } + + private Entity getAppearance(E entity) { + return Caster.of(entity).flatMap(caster -> caster.getSpellSlot().get(SpellPredicate.IS_DISGUISE, false)).map(Disguise.class::cast) + .flatMap(Disguise::getAppearance) + .map(EntityAppearance::getAppearance) + .orElse(null); + } + + @SuppressWarnings("unchecked") + private void renderArmHoldingItem(LivingEntity entity, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, float equipProgress, float swingProgress, Arm arm) { + if (!(client.getEntityRenderDispatcher().getRenderer(entity) instanceof LivingEntityRenderer renderer)) { + return; + } + if (!(renderer.getModel() instanceof BipedEntityModel bipedModel)) { + return; + } + + boolean bl = arm != Arm.LEFT; + float f = bl ? 1.0f : -1.0f; + float g = MathHelper.sqrt(swingProgress); + float h = -0.3f * MathHelper.sin(g * (float)Math.PI); + float i = 0.4f * MathHelper.sin(g * ((float)Math.PI * 2)); + float j = -0.4f * MathHelper.sin(swingProgress * (float)Math.PI); + matrices.translate(f * (h + 0.64000005f), i + -0.6f + equipProgress * -0.6f, j + -0.71999997f); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(f * 45.0f)); + float k = MathHelper.sin(swingProgress * swingProgress * (float)Math.PI); + float l = MathHelper.sin(g * (float)Math.PI); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(f * l * 70.0f)); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(f * k * -20.0f)); + + Identifier texture = renderer.getTexture(entity); + RenderSystem.setShaderTexture(0, texture); + matrices.translate(f * -1.0f, 3.6f, 3.5f); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(f * 120.0f)); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(200.0f)); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(f * -135.0f)); + matrices.translate(f * 5.6f, 0.0f, 0.0f); + + bipedModel.animateModel(entity, 0, 0, 0); + bipedModel.setAngles(entity, 0, 0, 0, 0, 0); + ModelPart part = bl ? bipedModel.rightArm : bipedModel.leftArm; + part.pitch = 0; + + if (MineLPDelegate.getInstance().getRace(entity).isEquine()) { + matrices.translate(0, -part.pivotY / 16F, 0); + } + + part.render(matrices, vertexConsumers.getBuffer(RenderLayer.getEntityTranslucent(texture)), light, OverlayTexture.DEFAULT_UV); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java index 19fe7fcc..1e0aba0c 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java @@ -124,12 +124,11 @@ class EntityDisguiseRenderer { } @Nullable - private BipedEntityModel getBipedModel(Entity entity) { - if (delegate.client.getEntityRenderDispatcher().getRenderer(entity) instanceof LivingEntityRenderer livingRenderer + static BipedEntityModel getBipedModel(Entity entity) { + if (MinecraftClient.getInstance().getEntityRenderDispatcher().getRenderer(entity) instanceof LivingEntityRenderer livingRenderer && livingRenderer.getModel() instanceof BipedEntityModel biped) { return biped; } return null; } - } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityBehaviour.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityBehaviour.java index b37e06df..a04113e6 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityBehaviour.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityBehaviour.java @@ -276,7 +276,8 @@ public class EntityBehaviour { static { register(PlayerBehaviour::new, EntityType.PLAYER); register(FallingBlockBehaviour::new, EntityType.FALLING_BLOCK); - register(MobBehaviour::new, EntityType.RAVAGER, EntityType.IRON_GOLEM); + register(MobBehaviour::new, EntityType.RAVAGER); + register(IronGolemBehaviour::new, EntityType.IRON_GOLEM); register(HoppingBehaviour::new, EntityType.RABBIT, EntityType.SLIME, EntityType.MAGMA_CUBE); register(TraderBehaviour::new, EntityType.VILLAGER, EntityType.WANDERING_TRADER); register(SteedBehaviour::new, EntityType.HORSE, EntityType.DONKEY, EntityType.SKELETON_HORSE, EntityType.ZOMBIE_HORSE); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/IronGolemBehaviour.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/IronGolemBehaviour.java new file mode 100644 index 00000000..f0ba9640 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/IronGolemBehaviour.java @@ -0,0 +1,18 @@ +package com.minelittlepony.unicopia.entity.behaviour; + +import com.minelittlepony.unicopia.entity.player.Pony; + +import net.minecraft.entity.passive.IronGolemEntity; +import net.minecraft.item.Items; +import net.minecraft.util.Hand; + +public class IronGolemBehaviour extends MobBehaviour { + @Override + public void update(Pony player, IronGolemEntity entity, Disguise spell) { + super.update(player, entity, spell); + boolean hasPoppy = player.asEntity().getStackInHand(Hand.MAIN_HAND).isOf(Items.POPPY); + if (hasPoppy != entity.getLookingAtVillagerTicks() > 0) { + entity.setLookingAtVillager(hasPoppy); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/MobBehaviour.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/MobBehaviour.java index a56ae3d9..450b8a61 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/MobBehaviour.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/MobBehaviour.java @@ -5,6 +5,9 @@ import com.minelittlepony.unicopia.util.TraceHelper; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.passive.IronGolemEntity; +import net.minecraft.item.Items; +import net.minecraft.util.Hand; public class MobBehaviour extends EntityBehaviour { @@ -23,6 +26,13 @@ public class MobBehaviour extends EntityBehaviour { entity.tryAttack(target); target.setAttacker(player.asEntity()); } + + if (entity instanceof IronGolemEntity i) { + boolean hasPoppy = player.asEntity().getStackInHand(Hand.MAIN_HAND).isOf(Items.POPPY); + if (hasPoppy != i.getLookingAtVillagerTicks() > 0) { + i.setLookingAtVillager(hasPoppy); + } + } } protected LivingEntity findTarget(Pony player, T entity) { From 1c32d71d4652185e261e02659bb9a8165a485281 Mon Sep 17 00:00:00 2001 From: Sollace Date: Wed, 14 Feb 2024 23:46:25 +0000 Subject: [PATCH 20/23] Improve disguise arm rendering --- .../render/DisguisedArmsFeatureRenderer.java | 135 ++++++++++++++---- 1 file changed, 108 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/DisguisedArmsFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/DisguisedArmsFeatureRenderer.java index 9393262e..c130779c 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/DisguisedArmsFeatureRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/DisguisedArmsFeatureRenderer.java @@ -1,5 +1,14 @@ package com.minelittlepony.unicopia.client.render; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.jetbrains.annotations.Nullable; + import com.google.common.base.MoreObjects; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; @@ -8,6 +17,7 @@ import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate; import com.minelittlepony.unicopia.entity.behaviour.Disguise; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.datafixers.util.Pair; import net.minecraft.client.MinecraftClient; import net.minecraft.client.model.ModelPart; @@ -17,13 +27,20 @@ import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.entity.LivingEntityRenderer; import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.client.render.entity.model.EntityModelLayers; +import net.minecraft.client.render.entity.model.EntityModelPartNames; +import net.minecraft.client.render.entity.model.SinglePartEntityModel; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.mob.ZombieEntity; +import net.minecraft.entity.passive.IronGolemEntity; import net.minecraft.util.Arm; import net.minecraft.util.Hand; import net.minecraft.util.Identifier; +import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.RotationAxis; @@ -31,6 +48,33 @@ public class DisguisedArmsFeatureRenderer implements Acc private final MinecraftClient client = MinecraftClient.getInstance(); + private static final Map, Identifier> OVERLAY_TEXTURES = Map.of( + EntityType.DROWNED, new Identifier("textures/entity/zombie/drowned_outer_layer.png"), + EntityType.STRAY, new Identifier("textures/entity/skeleton/stray_overlay.png") + ); + + private final Function, Set>> overlayModelCache = Util.memoize(type -> { + return EntityModelLayers.getLayers() + .filter(layer -> layer.getId().equals(EntityType.getId(type)) && !"main".equals(layer.getName())) + .map(MinecraftClient.getInstance().getEntityModelLoader()::getModelPart) + .map(model -> { + ModelPart arms = getPart(model, EntityModelPartNames.ARMS).orElse(null); + ModelPart leftArm = getPart(model, EntityModelPartNames.LEFT_ARM) + .or(() -> getPart(model, EntityModelPartNames.LEFT_FRONT_LEG)) + .orElse(arms); + ModelPart rightArm = getPart(model, EntityModelPartNames.RIGHT_ARM) + .or(() -> getPart(model, EntityModelPartNames.RIGHT_FRONT_LEG)) + .orElse(arms); + return leftArm != null && rightArm != null ? Pair.of(leftArm, rightArm) : null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + }); + + private static Optional getPart(ModelPart part, String childName) { + return part.hasChild(childName) ? Optional.of(part.getChild(childName)) : Optional.empty(); + } + public DisguisedArmsFeatureRenderer(FeatureRendererContext> context) { } @@ -43,13 +87,11 @@ public class DisguisedArmsFeatureRenderer implements Acc public boolean beforeRenderArms(ArmRenderer sender, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, E entity, int light) { Entity appearance = getAppearance(entity); if (appearance instanceof LivingEntity l) { - float swingProgress = entity.getHandSwingProgress(MinecraftClient.getInstance().getTickDelta()); + float swingProgress = entity.getHandSwingProgress(tickDelta); Hand hand = MoreObjects.firstNonNull(entity.preferredHand, Hand.MAIN_HAND); - //renderArmHoldingItem(l, matrices, vertexConsumers, light, 0, swingProgress, Arm.RIGHT); - - boolean bothHands = l instanceof ZombieEntity; + boolean bothHands = l instanceof ZombieEntity || l instanceof IronGolemEntity; if (bothHands || hand == Hand.MAIN_HAND) { if (entity.getMainHandStack().isEmpty()) { @@ -78,45 +120,84 @@ public class DisguisedArmsFeatureRenderer implements Acc .orElse(null); } + @SuppressWarnings("unchecked") + @Nullable + private ModelPart getArmModel(@Nullable EntityModel model, boolean right) { + + if (model instanceof BipedEntityModel bipedModel) { + return right ? bipedModel.rightArm : bipedModel.leftArm; + } + if (model instanceof SinglePartEntityModel quad) { + ModelPart arms = (ModelPart)quad.getChild(EntityModelPartNames.ARMS).orElse((ModelPart)null); + return (ModelPart)quad.getChild(right ? EntityModelPartNames.RIGHT_ARM : EntityModelPartNames.LEFT_ARM) + .or(() -> quad.getChild(right ? EntityModelPartNames.RIGHT_FRONT_LEG : EntityModelPartNames.LEFT_FRONT_LEG)) + .orElse(arms); + } + + return null; + } + @SuppressWarnings("unchecked") private void renderArmHoldingItem(LivingEntity entity, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, float equipProgress, float swingProgress, Arm arm) { if (!(client.getEntityRenderDispatcher().getRenderer(entity) instanceof LivingEntityRenderer renderer)) { return; } - if (!(renderer.getModel() instanceof BipedEntityModel bipedModel)) { + + boolean right = arm != Arm.LEFT; + EntityModel model = renderer.getModel(); + @Nullable + ModelPart part = getArmModel(model, right); + + if (part == null) { return; } - boolean bl = arm != Arm.LEFT; - float f = bl ? 1.0f : -1.0f; - float g = MathHelper.sqrt(swingProgress); - float h = -0.3f * MathHelper.sin(g * (float)Math.PI); - float i = 0.4f * MathHelper.sin(g * ((float)Math.PI * 2)); - float j = -0.4f * MathHelper.sin(swingProgress * (float)Math.PI); - matrices.translate(f * (h + 0.64000005f), i + -0.6f + equipProgress * -0.6f, j + -0.71999997f); - matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(f * 45.0f)); - float k = MathHelper.sin(swingProgress * swingProgress * (float)Math.PI); - float l = MathHelper.sin(g * (float)Math.PI); - matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(f * l * 70.0f)); - matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(f * k * -20.0f)); + model.animateModel(entity, 0, 0, 0); + model.setAngles(entity, 0, 0, 0, 0, client.getTickDelta()); - Identifier texture = renderer.getTexture(entity); - RenderSystem.setShaderTexture(0, texture); - matrices.translate(f * -1.0f, 3.6f, 3.5f); - matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(f * 120.0f)); - matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(200.0f)); - matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(f * -135.0f)); - matrices.translate(f * 5.6f, 0.0f, 0.0f); + float signum = right ? 1 : -1; + float srtSwingProgress = MathHelper.sqrt(swingProgress); + float xOffset = -0.3F * MathHelper.sin(srtSwingProgress * MathHelper.PI); + float yOffset = 0.4F * MathHelper.sin(srtSwingProgress * (MathHelper.TAU)); + float swingAmount = -0.4F * MathHelper.sin(swingProgress * MathHelper.PI); + matrices.translate(signum * (xOffset + 0.64000005F), yOffset + -0.6F + equipProgress * -0.6F, swingAmount + -0.71999997f); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(signum * 45)); + float zRot = MathHelper.sin(swingProgress * swingProgress * MathHelper.PI); + float yRot = MathHelper.sin(srtSwingProgress * MathHelper.PI); - bipedModel.animateModel(entity, 0, 0, 0); - bipedModel.setAngles(entity, 0, 0, 0, 0, 0); - ModelPart part = bl ? bipedModel.rightArm : bipedModel.leftArm; + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(signum * yRot * 70)); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(signum * zRot * -20)); + matrices.translate(signum * -1, 3.6F, 3.5F); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(signum * 120)); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(200)); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(signum * -135)); + matrices.translate(signum * 5.6F, 0, 0); + + if (entity instanceof IronGolemEntity golem) { + int attackTicks = golem.getAttackTicksLeft(); + if (attackTicks > 0) { + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(-signum * part.pitch * MathHelper.DEGREES_PER_RADIAN - 90 * signum)); + } + } part.pitch = 0; if (MineLPDelegate.getInstance().getRace(entity).isEquine()) { matrices.translate(0, -part.pivotY / 16F, 0); } + Identifier texture = renderer.getTexture(entity); + RenderSystem.setShaderTexture(0, texture); part.render(matrices, vertexConsumers.getBuffer(RenderLayer.getEntityTranslucent(texture)), light, OverlayTexture.DEFAULT_UV); + + Identifier overlayTexture = OVERLAY_TEXTURES.get(entity.getType()); + if (overlayTexture != null) { + overlayModelCache.apply(entity.getType()).forEach(arms -> { + ModelPart armPart = right ? arms.getSecond() : arms.getFirst(); + armPart.copyTransform(part); + + RenderSystem.setShaderTexture(0, overlayTexture); + part.render(matrices, vertexConsumers.getBuffer(RenderLayer.getEntityTranslucent(overlayTexture)), light, OverlayTexture.DEFAULT_UV); + }); + } } } From 1c2560c91027d8e10066b3ed21fda26fddb85092 Mon Sep 17 00:00:00 2001 From: Sollace Date: Wed, 14 Feb 2024 23:46:40 +0000 Subject: [PATCH 21/23] Fixed issues when disguising as a friendly creeper --- .../unicopia/entity/mob/FriendlyCreeperEntity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/FriendlyCreeperEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/FriendlyCreeperEntity.java index 01e128d5..785dad5f 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/FriendlyCreeperEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/FriendlyCreeperEntity.java @@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.entity.Creature; import com.minelittlepony.unicopia.entity.Equine; import com.minelittlepony.unicopia.entity.ai.FleeExplosionGoal; +import com.minelittlepony.unicopia.entity.behaviour.Guest; import net.minecraft.block.BlockState; import net.minecraft.client.render.entity.feature.SkinOverlayOwner; @@ -200,7 +201,7 @@ public class FriendlyCreeperEntity extends TameableEntity implements SkinOverlay lastHugTime = hugTime; - if (!isTamed()) { + if (!isTamed() && ((Guest)this).getHost() == null) { if (isConverting()) { if (++hugTime >= 100) { if (!getWorld().isClient) { From c0cff599638dd14390313ef1c0b320c3c086e3a7 Mon Sep 17 00:00:00 2001 From: Sollace Date: Wed, 14 Feb 2024 23:57:01 +0000 Subject: [PATCH 22/23] Fix issues disguising as crystal shards and prevent disguises from dropping items --- .../unicopia/entity/behaviour/Guest.java | 6 +++++ .../unicopia/entity/duck/EntityDuck.java | 3 ++- .../entity/duck/LivingEntityDuck.java | 4 +-- .../entity/mob/CrystalShardsEntity.java | 9 ++++--- .../entity/mob/StationaryObjectEntity.java | 1 - .../unicopia/mixin/MixinEntity.java | 25 +++++++++++++++++++ .../unicopia/mixin/MixinLivingEntity.java | 14 ----------- 7 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Guest.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Guest.java index 3615b589..2fd67cd2 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Guest.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Guest.java @@ -2,8 +2,14 @@ package com.minelittlepony.unicopia.entity.behaviour; import com.minelittlepony.unicopia.ability.magic.Caster; +import net.minecraft.entity.Entity; + public interface Guest { void setHost(Caster host); Caster getHost(); + + static boolean hasHost(Entity entity) { + return ((Guest)entity).getHost() != null; + } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/duck/EntityDuck.java b/src/main/java/com/minelittlepony/unicopia/entity/duck/EntityDuck.java index 659a3a24..fbe95209 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/duck/EntityDuck.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/duck/EntityDuck.java @@ -3,13 +3,14 @@ package com.minelittlepony.unicopia.entity.duck; import java.util.Set; import com.minelittlepony.unicopia.compat.pehkui.PehkuiEntityExtensions; +import com.minelittlepony.unicopia.entity.behaviour.Guest; import net.minecraft.entity.Entity; import net.minecraft.entity.Entity.RemovalReason; import net.minecraft.fluid.Fluid; import net.minecraft.registry.tag.TagKey; -public interface EntityDuck extends LavaAffine, PehkuiEntityExtensions { +public interface EntityDuck extends LavaAffine, PehkuiEntityExtensions, Guest { Set> getSubmergedFluidTags(); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/duck/LivingEntityDuck.java b/src/main/java/com/minelittlepony/unicopia/entity/duck/LivingEntityDuck.java index f126df85..a65c2a25 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/duck/LivingEntityDuck.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/duck/LivingEntityDuck.java @@ -1,13 +1,11 @@ package com.minelittlepony.unicopia.entity.duck; -import com.minelittlepony.unicopia.entity.behaviour.Guest; - import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemStack; import net.minecraft.util.Hand; import net.minecraft.util.math.Vec3d; -public interface LivingEntityDuck extends Guest { +public interface LivingEntityDuck extends EntityDuck { void updateItemUsage(Hand hand, ItemStack stack, int time); boolean isJumping(); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/CrystalShardsEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/CrystalShardsEntity.java index c23327d5..7a87feed 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/CrystalShardsEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/CrystalShardsEntity.java @@ -8,6 +8,7 @@ import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.USounds; +import com.minelittlepony.unicopia.entity.behaviour.Guest; import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.particle.ParticleUtils; @@ -172,9 +173,11 @@ public class CrystalShardsEntity extends StationaryObjectEntity { } } - if (isDead() || isInvalid(getWorld(), getBlockPos(), getAttachmentFace())) { - kill(); - ParticleUtils.spawnParticles(ParticleTypes.CLOUD, this, 10); + if (!Guest.hasHost(this)) { + if (isDead() || isInvalid(getWorld(), getBlockPos(), getAttachmentFace())) { + kill(); + ParticleUtils.spawnParticles(ParticleTypes.CLOUD, this, 10); + } } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/StationaryObjectEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/StationaryObjectEntity.java index 3e89f204..8b0c61e8 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/StationaryObjectEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/StationaryObjectEntity.java @@ -59,7 +59,6 @@ public abstract class StationaryObjectEntity extends Entity implements UDamageSo } protected void onHurt() { - } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java index 90febf2d..986e9bf5 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java @@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.mixin; import java.util.Set; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.injection.At; @@ -12,18 +13,35 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import com.minelittlepony.unicopia.entity.duck.LavaAffine; import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.Race; +import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.entity.Equine; import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.duck.EntityDuck; import net.minecraft.entity.Entity; +import net.minecraft.entity.ItemEntity; import net.minecraft.entity.Entity.PositionUpdater; import net.minecraft.entity.Entity.RemovalReason; import net.minecraft.fluid.Fluid; +import net.minecraft.item.ItemStack; import net.minecraft.registry.tag.TagKey; @Mixin(Entity.class) abstract class MixinEntity implements EntityDuck { + @Nullable + private transient Caster host; + + @Override + @Nullable + public Caster getHost() { + return host; + } + + @Override + public void setHost(Caster host) { + this.host = host; + } + @Override @Accessor("submergedFluidTag") public abstract Set> getSubmergedFluidTags(); @@ -83,4 +101,11 @@ abstract class MixinEntity implements EntityDuck { info.cancel(); } } + + @Inject(method = "dropStack(Lnet/minecraft/item/ItemStack;F)Lnet/minecraft/entity/ItemEntity;", at = @At("HEAD"), cancellable = true) + private void onDropStack(ItemStack stack, float yOffset, CallbackInfoReturnable info) { + if (getHost() != null) { + info.setReturnValue(null); + } + } } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java index 2fb6a58d..f71ef863 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java @@ -2,7 +2,6 @@ package com.minelittlepony.unicopia.mixin; import java.util.Optional; -import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.injection.At; @@ -41,8 +40,6 @@ abstract class MixinLivingEntity extends Entity implements LivingEntityDuck, Equ private Optional climbingPos; private Equine caster; - @Nullable - private transient Caster host; private MixinLivingEntity() { super(null, null); } @@ -62,17 +59,6 @@ abstract class MixinLivingEntity extends Entity implements LivingEntityDuck, Equ return (Living)caster; } - @Override - @Nullable - public Caster getHost() { - return host; - } - - @Override - public void setHost(Caster host) { - this.host = host; - } - @Override @Accessor("jumping") public abstract boolean isJumping(); From ae8068ccb40aa540bc6e4abdbfbb91bc715407ce Mon Sep 17 00:00:00 2001 From: Sollace Date: Thu, 15 Feb 2024 14:17:02 +0000 Subject: [PATCH 23/23] Fix build --- .../unicopia/entity/Living.java | 6 ++-- .../unicopia/entity/behaviour/Guest.java | 28 +++++++++++++++++-- .../entity/mob/CrystalShardsEntity.java | 2 +- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Living.java b/src/main/java/com/minelittlepony/unicopia/entity/Living.java index 0615b9da..463a5136 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Living.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Living.java @@ -483,8 +483,8 @@ public abstract class Living implements Equine, Caste public Optional onDamage(DamageSource source, float amount) { - if ((source.getAttacker() instanceof Guest guest && guest.getHost() instanceof Living l && l == this) - || (source.getSource() instanceof Guest guest && guest.getHost() instanceof Living l && l == this)) { + if (Guest.of(source.getAttacker()).hostIs(this) + || Guest.of(source.getSource()).hostIs(this)) { var type = source.getTypeRegistryEntry(); return Optional.of(entity.damage( type.matchesKey(DamageTypes.FIREBALL) ? entity.getDamageSources().create(DamageTypes.UNATTRIBUTED_FIREBALL) : @@ -492,7 +492,7 @@ public abstract class Living implements Equine, Caste new DamageSource(type, entity, entity), amount)); } - if (entity instanceof Guest guest && guest.getHost() instanceof Living l) { + if (Guest.of(entity).getHost() instanceof Living l) { l.asEntity().damage(source, amount); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Guest.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Guest.java index 2fd67cd2..7d432c2d 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Guest.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Guest.java @@ -1,15 +1,37 @@ package com.minelittlepony.unicopia.entity.behaviour; +import org.jetbrains.annotations.Nullable; + import com.minelittlepony.unicopia.ability.magic.Caster; import net.minecraft.entity.Entity; public interface Guest { - void setHost(Caster host); + Guest NULL = new Guest() { + @Override + public void setHost(@Nullable Caster host) { } + @Nullable + @Override + public Caster getHost() { + return null; + } + }; + + void setHost(@Nullable Caster host); + + @Nullable Caster getHost(); - static boolean hasHost(Entity entity) { - return ((Guest)entity).getHost() != null; + static Guest of(@Nullable Entity entity) { + return entity == null ? NULL : (Guest)entity; + } + + default boolean hasHost() { + return getHost() != null; + } + + default boolean hostIs(Caster self) { + return getHost() == self; } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/CrystalShardsEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/CrystalShardsEntity.java index 7a87feed..12ff12ee 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/CrystalShardsEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/CrystalShardsEntity.java @@ -173,7 +173,7 @@ public class CrystalShardsEntity extends StationaryObjectEntity { } } - if (!Guest.hasHost(this)) { + if (!Guest.of(this).hasHost()) { if (isDead() || isInvalid(getWorld(), getBlockPos(), getAttachmentFace())) { kill(); ParticleUtils.spawnParticles(ParticleTypes.CLOUD, this, 10);