Merge branch '1.20.1' into 1.20.2

# Conflicts:
#	README.md
#	src/main/java/com/minelittlepony/unicopia/client/gui/TribeConfirmationScreen.java
#	src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java
This commit is contained in:
Sollace 2024-02-15 16:24:09 +00:00
commit cef99f4a67
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
91 changed files with 961 additions and 189 deletions

View file

@ -43,7 +43,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.
@ -57,21 +57,24 @@ 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!
If you're playing as a flying species, or just like having nice things, try building a weather vane.
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)
@ -88,7 +91,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`

View file

@ -48,19 +48,22 @@
### Понифицированные картины
Ведь что это был бы за пони-мод, если бы в нём не было этого? У каждой расы есть хотя бы один рисунок, представляющий её,
Ведь что это был бы за пони-мод, если бы в нём _не_ было этого? У каждой расы есть хотя бы один рисунок, представляющий её,
так что покажите свою гордость и поднимите флаг!
Дисклеймер: Радужных флагов нет (пока)
Дисклеймер: Радужных флагов (пока) нет
### Природные явления
- Воздушный поток (плохо) влияет на пегасов, остерегайтесь летать во время грозы! Там может быть опасно!
Если вы играете за летающий вид или просто любите приятные вещи, попробуйте построить метеорологическую жилу.
- Воздушный поток
Пегасы, остерегайтесь полётов во время грозы! Это может быть опасно!
Если вы играете за летающий вид или просто любите приятные вещи, попробуйте построить погодную жилу.
Она показывает фактическое, абсолютно реальное, а не плохо смоделированное направление ветра в вашем мире Minecraft. Только учтите,
что направление и сила ветра ситуативны (и плохи), и будут отличаться в зависимости от того, где вы находитесь и на какой высоте.
что направление и сила ветра ситуативны, и будут отличаться в зависимости от того, где вы находитесь и на какой высоте.
- Горячий воздух поднимает
Нет, это не плохой фильм про "Звездные войны", это реальная механика. Песок и лава придают летающим видам дополнительную подъёмную силу.
Вода - наоборот. Попробуйте! А вообще, не стоит, я не хочу чтобы вы утонули.

View file

@ -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<String> values = Unicopia.getConfig().speciesWhiteList.get();
boolean added = values.add(race.getId().toString());
Unicopia.getConfig().save();
return added;
}
public boolean remove(Race race) {
Set<String> 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());
}
}

View file

@ -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;
@ -133,20 +134,16 @@ public record Race (Supplier<Composite> compositeSupplier, Availability availabi
}
public boolean isPermitted(@Nullable PlayerEntity sender) {
Set<String> whitelist = Unicopia.getConfig().speciesWhiteList.get();
return isUnset()
|| whitelist.isEmpty()
|| whitelist.contains(getId().toString());
return AllowList.INSTANCE.permits(this);
}
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;

View file

@ -61,7 +61,7 @@ public class EarthPonyKickAbility implements Ability<Pos> {
@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");
}

View file

@ -70,8 +70,9 @@ public class EarthPonyStompAbility implements Ability<Hit> {
@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");
}

View file

@ -19,7 +19,7 @@ public class TimeChangeAbility implements Ability<Rot> {
@Override
public boolean canUse(Race.Composite race) {
return race.pseudo() == Race.UNICORN;
return Ability.super.canUse(race) || race.pseudo() == Race.UNICORN;
}
@Override

View file

@ -43,9 +43,10 @@ public class ToggleFlightAbility implements Ability<Hit> {
@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");
}

View file

@ -50,6 +50,10 @@ public final class SpellReference<T extends Spell> 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<T extends Spell> 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);
}
}

View file

@ -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<Text> 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<? extends MutableText> 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);
}));
}
}

View file

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

View file

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

View file

@ -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,13 @@ 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();
}
final int columnHeight = 180;
final int columnWidth = 310;
final int segmentWidth = 123;

View file

@ -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) {
@ -159,18 +156,21 @@ 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);
}
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;

View file

@ -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;
@ -218,7 +218,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
List<Text> 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);

View file

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

View file

@ -10,6 +10,7 @@ import com.minelittlepony.api.events.PonyModelPrepareCallback;
import com.minelittlepony.api.model.*;
import com.minelittlepony.api.model.gear.Gear;
import com.minelittlepony.api.pony.PonyData;
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;
@ -18,6 +19,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;
@ -105,6 +107,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 com.minelittlepony.api.pony.Pony.getManager().getPony(entity).map(com.minelittlepony.api.pony.Pony::race).map(Main::toUnicopiaRace).orElse(Race.HUMAN);
}

View file

@ -27,6 +27,7 @@ public abstract class AbstractBillboardParticle extends AbstractGeometryBasedPar
RenderSystem.disableCull();
RenderSystem.enableBlend();
RenderSystem.enableDepthTest();
RenderSystem.defaultBlendFunc();
Vec3d cam = camera.getPos();

View file

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

View file

@ -0,0 +1,203 @@
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;
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 com.mojang.datafixers.util.Pair;
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.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;
public class DisguisedArmsFeatureRenderer<E extends LivingEntity> implements AccessoryFeatureRenderer.Feature<E> {
private final MinecraftClient client = MinecraftClient.getInstance();
private static final Map<EntityType<?>, 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<EntityType<?>, Set<Pair<ModelPart, ModelPart>>> 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<ModelPart> getPart(ModelPart part, String childName) {
return part.hasChild(childName) ? Optional.of(part.getChild(childName)) : Optional.empty();
}
public DisguisedArmsFeatureRenderer(FeatureRendererContext<E, ? extends BipedEntityModel<E>> 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(tickDelta);
Hand hand = MoreObjects.firstNonNull(entity.preferredHand, Hand.MAIN_HAND);
boolean bothHands = l instanceof ZombieEntity || l instanceof IronGolemEntity;
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")
@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;
}
boolean right = arm != Arm.LEFT;
EntityModel<Entity> model = renderer.getModel();
@Nullable
ModelPart part = getArmModel(model, right);
if (part == null) {
return;
}
model.animateModel(entity, 0, 0, 0);
model.setAngles(entity, 0, 0, 0, 0, client.getTickDelta());
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);
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);
});
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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<ShaderProgram> renderTypePortalSurfaceProgram = register("rendertype_portal_surface", VertexFormats.POSITION_COLOR);
public interface UShaders {
Supplier<ShaderProgram> 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<ShaderProgram> register(String name, VertexFormat format) {
AtomicReference<ShaderProgram> holder = new AtomicReference<>();

View file

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

View file

@ -51,7 +51,7 @@ public class ShieldSpellRenderer extends SpellRenderer<ShieldSpell> {
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.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);

View file

@ -33,6 +33,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) {
@ -53,7 +58,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<MagicReserves, MagicReserves.Bar> getter;

View file

@ -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,38 +18,58 @@ class RacelistCommand {
static LiteralArgumentBuilder<ServerCommandSource> create() {
return CommandManager.literal("racelist").requires(s -> s.hasPermissionLevel(3))
.then(CommandManager.literal("allow")
.then(CommandManager.argument("race", Race.argument())
.executes(context -> toggle(context.getSource(), context.getSource().getPlayer(), Race.fromArgument(context, "race"), "allowed", race -> {
if (race.isUnset()) {
return false;
.then(CommandManager.literal("show")
.executes(context -> {
context.getSource().sendFeedback(() -> {
if (!AllowList.INSTANCE.isEnabled()) {
return Text.translatable("commands.racelist.inactive");
}
Set<MutableText> allowed = new HashSet<>();
Set<MutableText> unallowed = new HashSet<>();
Race.REGISTRY.forEach(race -> {
(AllowList.INSTANCE.permits(race) ? allowed : unallowed).add(Text.translatable("commands.racelist.get.list_item",
race.getDisplayName(),
Text.literal(race.getId().toString()).formatted(Formatting.GRAY)
));
});
boolean result = Unicopia.getConfig().speciesWhiteList.get().add(race.getId().toString());
Unicopia.getConfig().save();
return result;
}))
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 -> {
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", AllowList.INSTANCE::add))
))
.then(CommandManager.literal("disallow")
.then(CommandManager.argument("race", Race.argument())
.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;
}))
.then(CommandManager.argument("race", Race.argument()).suggests(UCommandSuggestion.ALL_RACE_SUGGESTIONS)
.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<Race, Boolean> 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 {
@ -54,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;
}

View file

@ -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()) {

View file

@ -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<ServerCommandSource> ALL_RACE_SUGGESTIONS = suggestFromRegistry(Race.REGISTRY_KEY);
public static final SuggestionProvider<ServerCommandSource> ALLOWED_RACE_SUGGESTIONS = suggestFromRegistry(Race.REGISTRY_KEY, (context, race) -> race.isPermitted(context.getSource().getPlayer()));
public static <T> SuggestionProvider<ServerCommandSource> suggestFromRegistry(RegistryKey<? extends Registry<T>> registryKey, @Nullable BiPredicate<CommandContext<ServerCommandSource>, T> filter) {
return (context, builder) -> {
Registry<T> 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 <T> SuggestionProvider<ServerCommandSource> suggestFromRegistry(RegistryKey<? extends Registry<T>> registryKey) {
return suggestFromRegistry(registryKey, null);
}
public static <T> CompletableFuture<Suggestions> suggestIdentifiers(Iterable<T> candidates, Function<T, Identifier> 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 <T> void forEachMatching(Iterable<T> candidates, String input, Function<T, Identifier> idFunc, Consumer<T> 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);
}
}
}
}
}

View file

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

View file

@ -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<T extends LivingEntity> implements Equine<T>, Caste
@Nullable
private Caster<?> attacker;
@Nullable
private transient Caster<?> host;
private Optional<Living<?>> target = Optional.empty();
@ -476,6 +481,19 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
public Optional<Boolean> onDamage(DamageSource source, float amount) {
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) :
type.matchesKey(DamageTypes.PLAYER_EXPLOSION) ? entity.getDamageSources().create(DamageTypes.EXPLOSION) :
new DamageSource(type, entity, entity), amount));
}
if (Guest.of(entity).getHost() instanceof Living l) {
l.asEntity().damage(source, amount);
}
if (source.isIn(DamageTypeTags.IS_LIGHTNING) && (invinsibilityTicks > 0 || tryCaptureLightning())) {
return Optional.of(false);
}
@ -500,6 +518,10 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
return Optional.empty();
}
public TriState canBeHurtByWater() {
return TriState.DEFAULT;
}
public Optional<BlockPos> chooseClimbingPos() {
return getSpellSlot().get(SpellPredicate.IS_DISGUISE, false)
.map(AbstractDisguiseSpell::getDisguise)

View file

@ -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()) {

View file

@ -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<EndermanEntity> {
@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<EndermanEntity> {
} 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);
}*/
//}
}
}

View file

@ -204,12 +204,18 @@ 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()) {
source.asWorld().spawnEntity(entity);
} else {
entity.setId(source.asEntity().getId());
}
}

View file

@ -276,7 +276,8 @@ public class EntityBehaviour<T extends Entity> {
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);

View file

@ -0,0 +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 {
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 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;
}
}

View file

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

View file

@ -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<T extends MobEntity> extends EntityBehaviour<T> {
@ -23,6 +26,13 @@ public class MobBehaviour<T extends MobEntity> extends EntityBehaviour<T> {
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) {

View file

@ -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<TagKey<Fluid>> getSubmergedFluidTags();

View file

@ -5,7 +5,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.Hand;
import net.minecraft.util.math.Vec3d;
public interface LivingEntityDuck {
public interface LivingEntityDuck extends EntityDuck {
void updateItemUsage(Hand hand, ItemStack stack, int time);
boolean isJumping();

View file

@ -10,6 +10,8 @@ public interface RotatedView {
boolean hasTransform();
void setMirrorEntityStatuses(boolean enable);
default void pushRotation(int y) {
getRotations().add(y);
}

View file

@ -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.of(this).hasHost()) {
if (isDead() || isInvalid(getWorld(), getBlockPos(), getAttachmentFace())) {
kill();
ParticleUtils.spawnParticles(ParticleTypes.CLOUD, this, 10);
}
}
}

View file

@ -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) {

View file

@ -59,7 +59,6 @@ public abstract class StationaryObjectEntity extends Entity implements UDamageSo
}
protected void onHurt() {
}
@Override

View file

@ -88,6 +88,9 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> 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<PlayerEntity> 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<PlayerEntity> implements Tickab
} else {
descentRate = 0;
soundPlaying = false;
updraft.update(0, 100);
windStrength.update(0, 100);
}
if (!entity.isOnGround()) {
@ -567,7 +574,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> 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<Vec3d> pos = VecHelper.sphere(pony.asWorld().getRandom(), 0.5D);
Supplier<Vec3d> vel = VecHelper.sphere(pony.asWorld().getRandom(), 0.015D);
@ -623,12 +630,13 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> 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<PlayerEntity> 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<PlayerEntity> 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<PlayerEntity> implements Tickab
isFlyingEither = compound.getBoolean("isFlyingEither");
ticksInAir = compound.getInt("ticksInAir");
descentRate = compound.getFloat("descentRate");
updraft.update(compound.getFloat("updraft"), 0);
entity.calculateDimensions();
}

View file

@ -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;
@ -231,7 +231,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, 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<PlayerEntity> implements Copyable<Pony>, 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() {
@ -919,7 +919,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, 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);
}
}

View file

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

View file

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

View file

@ -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<DefaultedList<ItemStack>> combinedInventory) {
List<DefaultedList<ItemStack>> 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<DefaultedList<ItemStack>> combinedInventory) {
public static InventorySnapshot EMPTY = new InventorySnapshot(List.of());
public boolean empty() {
return combinedInventory.isEmpty();
}
public void restoreInto(List<DefaultedList<ItemStack>> 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);
}
}
}
}
}
}

View file

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

View file

@ -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<TagKey<Fluid>> 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<ItemEntity> info) {
if (getHost() != null) {
info.setReturnValue(null);
}
}
}

View file

@ -18,6 +18,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;
@ -157,6 +158,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<Boolean> 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());

View file

@ -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<DefaultedList<ItemStack>> combinedInventory;
@Nullable
private List<DefaultedList<ItemStack>> 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;
}
}
}

View file

@ -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<Integer> rotations = new Stack<>();
private boolean mirrorEntityStatuses;
@Override
public Stack<Integer> 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);

View file

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

View file

@ -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)) {

View file

@ -31,7 +31,7 @@ public class SpellNetworkedReference<T extends Spell> implements NetworkedRefere
@Override
public boolean fromNbt(NbtCompound comp) {
dirty = false;
currentValue.fromNBT(comp);
currentValue.fromNBT(comp, owner.isClient());
return isDirty();
}

View file

@ -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.",
@ -1326,16 +1327,23 @@
"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.allowed.failed": "%1$s is already whitelisted.",
"commands.racelist.disallowed": "Removed %1$s from the whitelist.",
"commands.racelist.disallowed.failed": "%1$s is not on 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.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 <allow|disallow> <race> 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.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",
"commands.disguise.usage": "/disguise <player> <entity> [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.",

View file

@ -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 <race>, чтобы настроить его.",
"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 <player> <entity> [nbt]",
"commands.disguise.notfound": "Идентификатор сущности \"%s\" не существует.",
"commands.disguise.removed": "Ваша маскировка была удалена.",
"commands.disguise.removed.self": "Удалена собственная маскировка.",

View file

@ -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": "天马徽章",
@ -142,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": "让穿戴者享受短暂的飞行乐趣",
@ -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": "魔虹苹果",
@ -681,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": "但结束不是全部,因为每个结束的尽头都是另一个开始。",
@ -1309,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 <race> 指令来添加种族和配置种族。",
"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": "移除了自己的伪装。",
@ -1443,6 +1466,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 从藤蔓上掉下来时忘了自己会飞",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 B

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 B

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 431 B

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View file

@ -67,6 +67,7 @@
"client.MixinBackgroundRenderer",
"client.MixinCamera",
"client.MixinClientWorld",
"client.MixinClientPlayNetworkHandler",
"client.MixinEntityRenderDispatcher",
"client.MixinGameRenderer",
"client.MixinHeldItemRenderer",