diff --git a/build.gradle b/build.gradle index c0f5a85c..ac4634c2 100644 --- a/build.gradle +++ b/build.gradle @@ -95,6 +95,7 @@ dependencies { modLocalRuntime "com.ptsmods:devlogin:2.0" modLocalRuntime "maven.modrinth:emoji-type:1.0.3" + modLocalRuntime "maven.modrinth:lazydfu:0.1.2" modLocalRuntime ("io.vram:canvas-fabric-mc118:${project.canvas_version}") { exclude group: "net.fabricmc.fabric-api" diff --git a/src/main/java/io/wispforest/affinity/blockentity/impl/BrewingCauldronBlockEntity.java b/src/main/java/io/wispforest/affinity/blockentity/impl/BrewingCauldronBlockEntity.java index 3e096aa8..c026498a 100644 --- a/src/main/java/io/wispforest/affinity/blockentity/impl/BrewingCauldronBlockEntity.java +++ b/src/main/java/io/wispforest/affinity/blockentity/impl/BrewingCauldronBlockEntity.java @@ -129,15 +129,16 @@ public void tickServer() { world.playSound(null, pos, SoundEvents.BLOCK_BREWING_STAND_BREW, SoundCategory.BLOCKS, 1, 1); + this.storedPotion = cachedRecipe.craftPotion(items); + for (var ingredient : cachedRecipe.getItemInputs()) { for (int i = 0; i < items.size(); i++) { - if (!ingredient.test(items.get(i))) continue; + if (!ingredient.ingredient().test(items.get(i))) continue; items.set(i, ItemStack.EMPTY); break; } } - this.storedPotion = new PotionMixture(cachedRecipe.getPotionOutput()); this.markDirty(true); processTick = 0; diff --git a/src/main/java/io/wispforest/affinity/compat/rei/PotionMixingDisplay.java b/src/main/java/io/wispforest/affinity/compat/rei/PotionMixingDisplay.java index 1cdf1533..ea7c5dc2 100644 --- a/src/main/java/io/wispforest/affinity/compat/rei/PotionMixingDisplay.java +++ b/src/main/java/io/wispforest/affinity/compat/rei/PotionMixingDisplay.java @@ -23,7 +23,7 @@ public class PotionMixingDisplay implements Display { public PotionMixingDisplay(PotionMixingRecipe recipe) { final var inputBuilder = new ImmutableList.Builder(); - recipe.getItemInputs().forEach(ingredient -> inputBuilder.add(EntryIngredients.ofIngredient(ingredient))); + recipe.getItemInputs().forEach(ingredient -> inputBuilder.add(EntryIngredients.ofIngredient(ingredient.ingredient()))); inputs = inputBuilder.build(); final var potionStack = new ItemStack(Items.POTION); diff --git a/src/main/java/io/wispforest/affinity/component/AffinityComponents.java b/src/main/java/io/wispforest/affinity/component/AffinityComponents.java index 46f097e3..8600d1f0 100644 --- a/src/main/java/io/wispforest/affinity/component/AffinityComponents.java +++ b/src/main/java/io/wispforest/affinity/component/AffinityComponents.java @@ -9,6 +9,7 @@ import dev.onyxstudios.cca.api.v3.entity.RespawnCopyStrategy; import io.wispforest.affinity.Affinity; import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; public class AffinityComponents implements EntityComponentInitializer, ChunkComponentInitializer { @@ -21,11 +22,15 @@ public class AffinityComponents implements EntityComponentInitializer, ChunkComp public static final ComponentKey ENTITY_FLAGS = ComponentRegistry.getOrCreate(Affinity.id("entity_flags"), EntityFlagComponent.class); + public static final ComponentKey TRANSPORTATION = + ComponentRegistry.getOrCreate(Affinity.id("transportation"), TransportationComponent.class); + @Override public void registerEntityComponentFactories(EntityComponentFactoryRegistry registry) { registry.registerForPlayers(GLOWING_COLOR, GlowingColorComponent::new, RespawnCopyStrategy.NEVER_COPY); registry.registerForPlayers(PLAYER_AETHUM, PlayerAethumComponent::new, RespawnCopyStrategy.ALWAYS_COPY); registry.registerFor(Entity.class, ENTITY_FLAGS, entity -> new EntityFlagComponent()); + registry.registerFor(LivingEntity.class, TRANSPORTATION, player -> new TransportationComponent()); } @Override diff --git a/src/main/java/io/wispforest/affinity/component/TransportationComponent.java b/src/main/java/io/wispforest/affinity/component/TransportationComponent.java new file mode 100644 index 00000000..67e3479a --- /dev/null +++ b/src/main/java/io/wispforest/affinity/component/TransportationComponent.java @@ -0,0 +1,45 @@ +package io.wispforest.affinity.component; + +import dev.onyxstudios.cca.api.v3.component.Component; +import io.wispforest.owo.util.VectorSerializer; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class TransportationComponent implements Component { + private Identifier world = World.OVERWORLD.getValue(); + private Vec3d pos = Vec3d.ZERO; + + public TransportationComponent() { + + } + + @Override + public void readFromNbt(NbtCompound tag) { + world = new Identifier(tag.getString("World")); + pos = VectorSerializer.get(tag, "Pos"); + } + + @Override + public void writeToNbt(NbtCompound tag) { + tag.putString("World", world.toString()); + VectorSerializer.store(pos, tag, "Pos"); + } + + public Identifier getWorld() { + return world; + } + + public void setWorld(Identifier world) { + this.world = world; + } + + public Vec3d getPos() { + return pos; + } + + public void setPos(Vec3d pos) { + this.pos = pos; + } +} diff --git a/src/main/java/io/wispforest/affinity/item/EchoShardItem.java b/src/main/java/io/wispforest/affinity/item/EchoShardItem.java new file mode 100644 index 00000000..56f45be0 --- /dev/null +++ b/src/main/java/io/wispforest/affinity/item/EchoShardItem.java @@ -0,0 +1,55 @@ +package io.wispforest.affinity.item; + +import io.wispforest.affinity.object.AffinityItems; +import io.wispforest.owo.util.NbtKey; +import io.wispforest.owo.util.VectorSerializer; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import net.minecraft.util.TypedActionResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class EchoShardItem extends Item { + public static final NbtKey POS = new NbtKey<>("Pos", NbtKey.Type.of(NbtElement.LIST_TYPE, VectorSerializer::get, (tag, s, vec3d) -> VectorSerializer.store(vec3d, tag, s))); + public static final NbtKey WORLD = new NbtKey<>("World", NbtKey.Type.of(NbtElement.STRING_TYPE, (tag, s) -> new Identifier(tag.getString(s)), (tag, s, id) -> tag.putString(s, id.toString()))); + + public EchoShardItem() { + super(AffinityItems.settings(0)); + } + + public static void formatLocationTooltip(NbtCompound data, List tooltip) { + var pos = POS.get(data); + var targetWorld = WORLD.get(data); + var worldText = new TranslatableText("dimension." + targetWorld.getNamespace() + "." + targetWorld.getPath()); + + tooltip.add(new TranslatableText("text.affinity.echo_shard_location", worldText, (int)pos.x, (int)pos.y, (int)pos.z)); + } + + @Override + public TypedActionResult use(World world, PlayerEntity user, Hand hand) { + ItemStack stack = user.getStackInHand(hand); + + POS.put(stack.getOrCreateNbt(), user.getPos()); + WORLD.put(stack.getOrCreateNbt(), world.getRegistryKey().getValue()); + + return TypedActionResult.success(stack, world.isClient); + } + + @Override + public void appendTooltip(ItemStack stack, @Nullable World world, List tooltip, TooltipContext context) { + if (stack.hasNbt() && stack.getNbt().contains("Pos", NbtElement.LIST_TYPE)) { + formatLocationTooltip(stack.getNbt(), tooltip); + } + } +} diff --git a/src/main/java/io/wispforest/affinity/misc/EntityTeleporter.java b/src/main/java/io/wispforest/affinity/misc/EntityTeleporter.java new file mode 100644 index 00000000..81c378d1 --- /dev/null +++ b/src/main/java/io/wispforest/affinity/misc/EntityTeleporter.java @@ -0,0 +1,38 @@ +package io.wispforest.affinity.misc; + +import io.wispforest.owo.ops.WorldOps; +import net.minecraft.entity.Entity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.Vec3d; + +public final class EntityTeleporter { + private EntityTeleporter() { + + } + + @SuppressWarnings("unchecked") + public static E teleport(E entity, ServerWorld to, Vec3d pos, float yaw, float pitch) { + if (entity instanceof ServerPlayerEntity player) { + WorldOps.teleportToWorld(player, to, pos, yaw, pitch); + } + + if (entity.getEntityWorld() != to) { + entity.detach(); + E newEntity = (E) entity.getType().create(to); + + newEntity.copyFrom(entity); + newEntity.refreshPositionAndAngles(pos.x, pos.y, pos.z, yaw, pitch); + newEntity.setHeadYaw(yaw); + to.onDimensionChanged(newEntity); + entity.remove(Entity.RemovalReason.CHANGED_DIMENSION); + + return newEntity; + } else { + entity.refreshPositionAndAngles(pos.x, pos.y, pos.z, yaw, pitch); + entity.setHeadYaw(yaw); + + return entity; + } + } +} diff --git a/src/main/java/io/wispforest/affinity/misc/MixinHooks.java b/src/main/java/io/wispforest/affinity/misc/MixinHooks.java index d7568903..83361022 100644 --- a/src/main/java/io/wispforest/affinity/misc/MixinHooks.java +++ b/src/main/java/io/wispforest/affinity/misc/MixinHooks.java @@ -3,12 +3,15 @@ import io.wispforest.affinity.Affinity; import io.wispforest.affinity.enchantment.impl.BerserkerEnchantment; import io.wispforest.affinity.enchantment.template.AffinityDamageEnchantment; +import io.wispforest.affinity.statuseffects.AffinityStatusEffect; import io.wispforest.affinity.statuseffects.ImpendingDoomStatusEffect; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; import net.minecraft.potion.PotionUtil; import net.minecraft.text.Text; import net.minecraft.util.Identifier; @@ -48,4 +51,10 @@ public static boolean isDoomPotion(ItemStack stack) { return PotionUtil.getPotion(stack) == Registry.POTION.get(IMPENDING_DOOM_ID); } + public static void tryInvokePotionApplied(StatusEffectInstance effect, LivingEntity target, NbtCompound data) { + if (effect.getEffectType() instanceof AffinityStatusEffect ase) { + ase.onPotionApplied(target, data); + } + } + } diff --git a/src/main/java/io/wispforest/affinity/misc/ServerTaskScheduler.java b/src/main/java/io/wispforest/affinity/misc/ServerTaskScheduler.java new file mode 100644 index 00000000..7b693d8f --- /dev/null +++ b/src/main/java/io/wispforest/affinity/misc/ServerTaskScheduler.java @@ -0,0 +1,26 @@ +package io.wispforest.affinity.misc; + +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.minecraft.server.MinecraftServer; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public final class ServerTaskScheduler { + private static final List> TASKS = new ArrayList<>(); + + static { + ServerTickEvents.END_SERVER_TICK.register(server -> { + for (var task : TASKS) { + task.accept(server); + } + + TASKS.clear(); + }); + } + + public static void scheduleTask(Consumer task) { + TASKS.add(task); + } +} diff --git a/src/main/java/io/wispforest/affinity/misc/potion/PotionMixture.java b/src/main/java/io/wispforest/affinity/misc/potion/PotionMixture.java index 4ee65d63..9133353a 100644 --- a/src/main/java/io/wispforest/affinity/misc/potion/PotionMixture.java +++ b/src/main/java/io/wispforest/affinity/misc/potion/PotionMixture.java @@ -23,18 +23,20 @@ */ public class PotionMixture { - public static final PotionMixture EMPTY = new PotionMixture(Potions.EMPTY, ImmutableList.of(), true); + public static final PotionMixture EMPTY = new PotionMixture(Potions.EMPTY, ImmutableList.of(), true, null); public static final Potion DUBIOUS_POTION = new Potion("dubious"); private final Potion basePotion; private final List effects; private final boolean pure; private final int color; + private final NbtCompound extraNbt; - public PotionMixture(Potion basePotion) { + public PotionMixture(Potion basePotion, NbtCompound extraNbt) { this.basePotion = basePotion; this.effects = ImmutableList.of(); this.pure = true; + this.extraNbt = extraNbt; final var colorEffects = new ArrayList<>(effects); if (basePotion != Potions.EMPTY) colorEffects.addAll(basePotion.getEffects()); @@ -42,10 +44,11 @@ public PotionMixture(Potion basePotion) { this.color = PotionUtil.getColor(colorEffects); } - public PotionMixture(Potion basePotion, List effects, boolean pure) { + public PotionMixture(Potion basePotion, List effects, boolean pure, NbtCompound extraNbt) { this.basePotion = basePotion; this.effects = ImmutableList.copyOf(effects); this.pure = pure; + this.extraNbt = extraNbt; final var colorEffects = new ArrayList<>(effects); if (basePotion != Potions.EMPTY) colorEffects.addAll(basePotion.getEffects()); @@ -62,14 +65,14 @@ public PotionMixture mix(PotionMixture other) { effects.addAll(basePotion.getEffects()); effects.addAll(other.basePotion.getEffects()); - return new PotionMixture(Potions.EMPTY, effects, false); + return new PotionMixture(Potions.EMPTY, effects, false, null); } public static PotionMixture fromStack(ItemStack stack) { final var potion = PotionUtil.getPotion(stack); final var effects = PotionUtil.getCustomPotionEffects(stack); - return new PotionMixture(potion, effects, true); + return new PotionMixture(potion, effects, true, stack.hasNbt() && stack.getNbt().contains("ExtraPotionNbt", NbtElement.COMPOUND_TYPE) ? stack.getNbt().getCompound("ExtraPotionNbt") : null); } public static PotionMixture fromNbt(NbtCompound nbt) { @@ -89,7 +92,13 @@ public static PotionMixture fromNbt(NbtCompound nbt) { } } - return new PotionMixture(potion, effects, nbt.getBoolean("Pure")); + NbtCompound extraNbt = null; + + if (nbt.contains("ExtraNbt", NbtElement.COMPOUND_TYPE)) { + extraNbt = nbt.getCompound("ExtraNbt"); + } + + return new PotionMixture(potion, effects, nbt.getBoolean("Pure"), extraNbt); } public NbtCompound toNbt() { @@ -126,6 +135,10 @@ public ItemStack toStack() { PotionUtil.setPotion(stack, DUBIOUS_POTION); } + if (extraNbt != null) { + stack.getOrCreateNbt().put("ExtraPotionNbt", extraNbt); + } + return stack; } @@ -145,6 +158,10 @@ public Potion basePotion() { return basePotion; } + public NbtCompound extraNbt() { + return extraNbt; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/io/wispforest/affinity/misc/quack/ExtendedAreaEffectCloudEntity.java b/src/main/java/io/wispforest/affinity/misc/quack/ExtendedAreaEffectCloudEntity.java new file mode 100644 index 00000000..f6df49be --- /dev/null +++ b/src/main/java/io/wispforest/affinity/misc/quack/ExtendedAreaEffectCloudEntity.java @@ -0,0 +1,7 @@ +package io.wispforest.affinity.misc.quack; + +import net.minecraft.nbt.NbtCompound; + +public interface ExtendedAreaEffectCloudEntity { + void affinity$setExtraPotionNbt(NbtCompound tag); +} diff --git a/src/main/java/io/wispforest/affinity/misc/recipe/PotionMixingRecipe.java b/src/main/java/io/wispforest/affinity/misc/recipe/PotionMixingRecipe.java index 0d076964..8e59478f 100644 --- a/src/main/java/io/wispforest/affinity/misc/recipe/PotionMixingRecipe.java +++ b/src/main/java/io/wispforest/affinity/misc/recipe/PotionMixingRecipe.java @@ -6,6 +6,7 @@ import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.inventory.Inventory; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; import net.minecraft.potion.Potion; import net.minecraft.potion.Potions; import net.minecraft.recipe.*; @@ -15,16 +16,17 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.stream.Stream; public class PotionMixingRecipe implements Recipe { - private final List itemInputs; + private final List itemInputs; private final List effectInputs; private final Potion output; private final Identifier id; - public PotionMixingRecipe(Identifier id, List itemInputs, List effectInputs, Potion output) { + public PotionMixingRecipe(Identifier id, List itemInputs, List effectInputs, Potion output) { this.id = id; this.itemInputs = itemInputs; @@ -49,7 +51,7 @@ public static Optional getMatching(RecipeManager manager, Po for (var recipe : manager.listAllOfType(Type.INSTANCE)) { - final var effectInputs = inputMixture.effects().stream().map(StatusEffectInstance::getEffectType).toList(); + final var effectInputs = Stream.concat(inputMixture.effects().stream(), inputMixture.basePotion().getEffects().stream()).map(StatusEffectInstance::getEffectType).toList(); final var itemInputs = new ConcurrentLinkedQueue<>(inputStacks.stream().filter(stack -> !stack.isEmpty()).toList()); if (effectInputs.size() != recipe.effectInputs.size() || itemInputs.size() != recipe.itemInputs.size()) continue; @@ -58,7 +60,7 @@ public static Optional getMatching(RecipeManager manager, Po for (var ingredient : recipe.itemInputs) { for (var stack : itemInputs) { - if (!ingredient.test(stack)) continue; + if (!ingredient.ingredient.test(stack)) continue; itemInputs.remove(stack); confirmedItemInputs++; @@ -99,7 +101,28 @@ public Potion getPotionOutput() { return output; } - public List getItemInputs() { + public PotionMixture craftPotion(List inputStacks) { + NbtCompound extraNbt = null; + + for (var ingredient : itemInputs) { + if (!ingredient.copyNbt) continue; + + for (var stack : inputStacks) { + if (!ingredient.ingredient.test(stack)) continue; + + if (extraNbt == null) extraNbt = new NbtCompound(); + + if (stack.hasNbt()) + extraNbt.copyFrom(stack.getNbt()); + + break; + } + } + + return new PotionMixture(getPotionOutput(), extraNbt); + } + + public List getItemInputs() { return itemInputs; } @@ -129,4 +152,7 @@ private Type() {} public static final Identifier ID = Affinity.id("potion_mixing"); } + public record InputData(Ingredient ingredient, boolean copyNbt) { + + } } diff --git a/src/main/java/io/wispforest/affinity/misc/recipe/PotionMixingRecipeSerializer.java b/src/main/java/io/wispforest/affinity/misc/recipe/PotionMixingRecipeSerializer.java index 90eeca38..4d2bbed3 100644 --- a/src/main/java/io/wispforest/affinity/misc/recipe/PotionMixingRecipeSerializer.java +++ b/src/main/java/io/wispforest/affinity/misc/recipe/PotionMixingRecipeSerializer.java @@ -31,9 +31,15 @@ public PotionMixingRecipe read(Identifier id, JsonObject json) { inputEffects.add(Registry.STATUS_EFFECT.getOrEmpty(Identifier.tryParse(element.getAsString())).orElseThrow(() -> new JsonSyntaxException("Invalid status effect: " + element.getAsString()))); } - final var itemInputs = new ArrayList(); + final var itemInputs = new ArrayList(); for (var element : itemInputsJson) { - itemInputs.add(Ingredient.fromJson(element)); + var ingredient = Ingredient.fromJson(element); + + boolean copyNbt = false; + if (element.isJsonObject()) + copyNbt = JsonHelper.getBoolean(element.getAsJsonObject(), "copy_nbt", false); + + itemInputs.add(new PotionMixingRecipe.InputData(ingredient, copyNbt)); } return new PotionMixingRecipe(id, itemInputs, inputEffects, outputPotion); @@ -44,7 +50,7 @@ public PotionMixingRecipe read(Identifier id, PacketByteBuf buf) { final var potion = Registry.POTION.get(buf.readVarInt()); final var effectInputs = buf.readCollection(value -> new ArrayList<>(), buf1 -> Registry.STATUS_EFFECT.get(buf1.readVarInt())); - final var itemInputs = buf.readCollection(value -> new ArrayList<>(), Ingredient::fromPacket); + final var itemInputs = buf.readCollection(value -> new ArrayList<>(), buf1 -> new PotionMixingRecipe.InputData(Ingredient.fromPacket(buf1), false)); return new PotionMixingRecipe(id, itemInputs, effectInputs, potion); } @@ -54,6 +60,6 @@ public void write(PacketByteBuf buf, PotionMixingRecipe recipe) { buf.writeVarInt(Registry.POTION.getRawId(recipe.getPotionOutput())); buf.writeCollection(recipe.getEffectInputs(), (buf1, effect) -> buf1.writeVarInt(Registry.STATUS_EFFECT.getRawId(effect))); - buf.writeCollection(recipe.getItemInputs(), (buf1, ingredient) -> ingredient.write(buf1)); + buf.writeCollection(recipe.getItemInputs(), (buf1, ingredient) -> ingredient.ingredient().write(buf1)); } } diff --git a/src/main/java/io/wispforest/affinity/mixin/AreaEffectCloudEntityMixin.java b/src/main/java/io/wispforest/affinity/mixin/AreaEffectCloudEntityMixin.java new file mode 100644 index 00000000..47d03f38 --- /dev/null +++ b/src/main/java/io/wispforest/affinity/mixin/AreaEffectCloudEntityMixin.java @@ -0,0 +1,37 @@ +package io.wispforest.affinity.mixin; + +import io.wispforest.affinity.misc.MixinHooks; +import io.wispforest.affinity.misc.quack.ExtendedAreaEffectCloudEntity; +import net.minecraft.entity.AreaEffectCloudEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.potion.Potion; +import org.spongepowered.asm.mixin.Final; +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.ModifyArg; + +import java.util.List; + +@Mixin(AreaEffectCloudEntity.class) +public class AreaEffectCloudEntityMixin implements ExtendedAreaEffectCloudEntity { + @Shadow @Final private List effects; + @Shadow private Potion potion; + private NbtCompound affinity$extraPotionNbt; + + public void affinity$setExtraPotionNbt(NbtCompound tag) { + affinity$extraPotionNbt = tag; + } + + @ModifyArg(method = "tick", at = @At(value = "INVOKE", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"), index = 0) + private Object doPotionApplication(Object e) { + var entity = (LivingEntity) e; + + potion.getEffects().forEach(x -> MixinHooks.tryInvokePotionApplied(x, entity, affinity$extraPotionNbt)); + effects.forEach(x -> MixinHooks.tryInvokePotionApplied(x, entity, affinity$extraPotionNbt)); + + return e; + } +} diff --git a/src/main/java/io/wispforest/affinity/mixin/ArrowEntityMixin.java b/src/main/java/io/wispforest/affinity/mixin/ArrowEntityMixin.java new file mode 100644 index 00000000..0ac3f55b --- /dev/null +++ b/src/main/java/io/wispforest/affinity/mixin/ArrowEntityMixin.java @@ -0,0 +1,32 @@ +package io.wispforest.affinity.mixin; + +import io.wispforest.affinity.misc.MixinHooks; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.projectile.ArrowEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.potion.Potion; +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; + +@Mixin(ArrowEntity.class) +public class ArrowEntityMixin { + @Shadow private Potion potion; + private NbtCompound affinity$extraPotionNbt; + + @Inject(method = "initFromStack", at = @At("RETURN")) + private void addExtraData(ItemStack stack, CallbackInfo ci) { + if (stack.hasNbt() && stack.getNbt().contains("ExtraPotionNbt", NbtElement.COMPOUND_TYPE)) { + affinity$extraPotionNbt = stack.getSubNbt("ExtraPotionNbt"); + } + } + + @Inject(method = "onHit", at = @At(value = "INVOKE", target = "Lnet/minecraft/potion/Potion;getEffects()Ljava/util/List;")) + private void doPotionApplication(LivingEntity target, CallbackInfo ci) { + potion.getEffects().forEach(x -> MixinHooks.tryInvokePotionApplied(x, target, affinity$extraPotionNbt)); + } +} diff --git a/src/main/java/io/wispforest/affinity/mixin/BrewingRecipeRegistryMixin.java b/src/main/java/io/wispforest/affinity/mixin/BrewingRecipeRegistryMixin.java index fac2c4d2..f4cf29e2 100644 --- a/src/main/java/io/wispforest/affinity/mixin/BrewingRecipeRegistryMixin.java +++ b/src/main/java/io/wispforest/affinity/mixin/BrewingRecipeRegistryMixin.java @@ -2,6 +2,7 @@ import io.wispforest.affinity.misc.MixinHooks; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtElement; import net.minecraft.recipe.BrewingRecipeRegistry; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -17,4 +18,11 @@ private static void weDontSplashDoom(ItemStack input, ItemStack ingredient, Call cir.setReturnValue(false); } + @Inject(method = "craft", at = @At("RETURN")) + private static void addExtraData(ItemStack ingredient, ItemStack input, CallbackInfoReturnable cir) { + if (input.hasNbt() && input.getNbt().contains("ExtraPotionNbt", NbtElement.COMPOUND_TYPE)) { + cir.getReturnValue().getOrCreateNbt().put("ExtraPotionNbt", input.getSubNbt("ExtraPotionNbt")); + } + } + } diff --git a/src/main/java/io/wispforest/affinity/mixin/PotionEntityMixin.java b/src/main/java/io/wispforest/affinity/mixin/PotionEntityMixin.java new file mode 100644 index 00000000..944d106d --- /dev/null +++ b/src/main/java/io/wispforest/affinity/mixin/PotionEntityMixin.java @@ -0,0 +1,44 @@ +package io.wispforest.affinity.mixin; + +import io.wispforest.affinity.misc.MixinHooks; +import io.wispforest.affinity.misc.quack.ExtendedAreaEffectCloudEntity; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.projectile.thrown.PotionEntity; +import net.minecraft.entity.projectile.thrown.ThrownItemEntity; +import net.minecraft.nbt.NbtElement; +import net.minecraft.potion.PotionUtil; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(PotionEntity.class) +public abstract class PotionEntityMixin extends ThrownItemEntity { + public PotionEntityMixin(EntityType entityType, World world) { + super(entityType, world); + } + + @ModifyArg(method = "applySplashPotion", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/projectile/thrown/PotionEntity;squaredDistanceTo(Lnet/minecraft/entity/Entity;)D")) + private Entity doPotionApplication(Entity otherEntity) { + if (!(otherEntity instanceof LivingEntity target)) return otherEntity; + + var stack = getStack(); + + PotionUtil.getPotionEffects(stack).forEach(x -> MixinHooks.tryInvokePotionApplied(x, target, stack.getSubNbt("ExtraPotionNbt"))); + + return otherEntity; + } + + @ModifyArg(method = "applyLingeringPotion", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;spawnEntity(Lnet/minecraft/entity/Entity;)Z")) + private Entity addExtraData(Entity entity) { + var stack = getStack(); + + if (stack.hasNbt() && stack.getNbt().contains("ExtraPotionNbt", NbtElement.COMPOUND_TYPE)) { + ((ExtendedAreaEffectCloudEntity) entity).affinity$setExtraPotionNbt(stack.getSubNbt("ExtraPotionNbt")); + } + + return entity; + } +} diff --git a/src/main/java/io/wispforest/affinity/mixin/PotionItemMixin.java b/src/main/java/io/wispforest/affinity/mixin/PotionItemMixin.java index 6ece796b..92363002 100644 --- a/src/main/java/io/wispforest/affinity/mixin/PotionItemMixin.java +++ b/src/main/java/io/wispforest/affinity/mixin/PotionItemMixin.java @@ -1,10 +1,12 @@ package io.wispforest.affinity.mixin; import io.wispforest.affinity.component.AffinityComponents; +import io.wispforest.affinity.misc.MixinHooks; import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemStack; import net.minecraft.item.PotionItem; import net.minecraft.nbt.NbtElement; +import net.minecraft.potion.PotionUtil; import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -21,4 +23,9 @@ private void injectColor(ItemStack stack, World world, LivingEntity user, Callba AffinityComponents.GLOWING_COLOR.get(user).setColor(nbt.getString("Color")); } + @Inject(method = "finishUsing", at = @At("HEAD")) + private void doPotionApplication(ItemStack stack, World world, LivingEntity user, CallbackInfoReturnable cir) { + PotionUtil.getPotionEffects(stack).forEach(x -> MixinHooks.tryInvokePotionApplied(x, user, stack.getSubNbt("ExtraPotionNbt"))); + } + } diff --git a/src/main/java/io/wispforest/affinity/mixin/TippedArrowRecipeMixin.java b/src/main/java/io/wispforest/affinity/mixin/TippedArrowRecipeMixin.java new file mode 100644 index 00000000..72e24050 --- /dev/null +++ b/src/main/java/io/wispforest/affinity/mixin/TippedArrowRecipeMixin.java @@ -0,0 +1,21 @@ +package io.wispforest.affinity.mixin; + +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtElement; +import net.minecraft.recipe.TippedArrowRecipe; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(TippedArrowRecipe.class) +public class TippedArrowRecipeMixin { + @Inject(method = "craft(Lnet/minecraft/inventory/CraftingInventory;)Lnet/minecraft/item/ItemStack;", at = @At(value = "RETURN", ordinal = 1)) + private void addExtraData(CraftingInventory craftingInventory, CallbackInfoReturnable cir) { + ItemStack arrowStack = craftingInventory.getStack(1 + craftingInventory.getWidth()); + + if (arrowStack.hasNbt() && arrowStack.getNbt().contains("ExtraPotionNbt", NbtElement.COMPOUND_TYPE)) + cir.getReturnValue().getOrCreateNbt().put("ExtraPotionNbt", arrowStack.getSubNbt("ExtraPotionNbt")); + } +} diff --git a/src/main/java/io/wispforest/affinity/object/AffinityItems.java b/src/main/java/io/wispforest/affinity/object/AffinityItems.java index e459112f..119e5271 100644 --- a/src/main/java/io/wispforest/affinity/object/AffinityItems.java +++ b/src/main/java/io/wispforest/affinity/object/AffinityItems.java @@ -49,6 +49,8 @@ public class AffinityItems implements ItemRegistryContainer { public static final Item AZALEA_BOAT = new BoatItem(AffinityBlocks.AZALEA_BOAT_TYPE, new OwoItemSettings().tab(1).group(Affinity.AFFINITY_GROUP).maxCount(1)); + public static final Item ECHO_SHARD = new EchoShardItem(); + public static OwoItemSettings settings(int tab) { return new OwoItemSettings().tab(tab).group(Affinity.AFFINITY_GROUP); } diff --git a/src/main/java/io/wispforest/affinity/object/AffinityParticleSystems.java b/src/main/java/io/wispforest/affinity/object/AffinityParticleSystems.java index ab6a3e2e..d0576eab 100644 --- a/src/main/java/io/wispforest/affinity/object/AffinityParticleSystems.java +++ b/src/main/java/io/wispforest/affinity/object/AffinityParticleSystems.java @@ -52,6 +52,11 @@ public class AffinityParticleSystems { ClientParticles.spawnPrecise(ParticleTypes.FALLING_NECTAR, world, pos, 1, 1.5, 1); }); + public static final ParticleSystem TRANSPORTATION_CLOUD = CONTROLLER.register(Void.class, (world, pos, data) -> { + ClientParticles.setParticleCount(20); + ClientParticles.spawn(ParticleTypes.EFFECT, world, pos, 0.5); + }); + public record DissolveData(ItemStack suckWhat, Vec3d suckWhere, int duration, int particleMaxAge) {} public record LineData(Vec3d target, int color) {} diff --git a/src/main/java/io/wispforest/affinity/object/AffinityStatusEffects.java b/src/main/java/io/wispforest/affinity/object/AffinityStatusEffects.java index 4da4882d..c6df43f9 100644 --- a/src/main/java/io/wispforest/affinity/object/AffinityStatusEffects.java +++ b/src/main/java/io/wispforest/affinity/object/AffinityStatusEffects.java @@ -28,6 +28,7 @@ public class AffinityStatusEffects { public static final StatusEffect DRIPPING = new DrippingStatusEffect(StatusEffectCategory.NEUTRAL, 0xa4568d); public static final StatusEffect IMPENDING_DOOM = new ImpendingDoomStatusEffect(StatusEffectCategory.HARMFUL, 0x000000); public static final StatusEffect BASTION_REGENERATION = new BastionRegeneration(StatusEffectCategory.BENEFICIAL, 0xfd5c5b); + public static final StatusEffect TRANSPORTATION = new TransportationStatusEffect(StatusEffectCategory.BENEFICIAL, 0xc9b6b3); public static final StatusEffect STEADFAST = new AffinityStatusEffect(StatusEffectCategory.BENEFICIAL, 0x222222) .addAttributeModifier( @@ -41,6 +42,7 @@ public static void register() { registerEffectAndPotions(STEADFAST, "steadfast", 4800, true, true); registerEffectAndPotions(FREEZING, "freezing", 600, true, true); registerEffectAndPotions(IMPENDING_DOOM, "impending_doom", 1200, false, false); + registerEffectAndPotions(TRANSPORTATION, "transportation", 6000, true, false); registerEffectAndPotions(FLIGHT, "flight", 2400, true, false); diff --git a/src/main/java/io/wispforest/affinity/statuseffects/AffinityStatusEffect.java b/src/main/java/io/wispforest/affinity/statuseffects/AffinityStatusEffect.java index 90a9646d..15761770 100644 --- a/src/main/java/io/wispforest/affinity/statuseffects/AffinityStatusEffect.java +++ b/src/main/java/io/wispforest/affinity/statuseffects/AffinityStatusEffect.java @@ -1,11 +1,18 @@ package io.wispforest.affinity.statuseffects; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.effect.StatusEffect; import net.minecraft.entity.effect.StatusEffectCategory; +import net.minecraft.nbt.NbtCompound; +import org.jetbrains.annotations.Nullable; public class AffinityStatusEffect extends StatusEffect { public AffinityStatusEffect(StatusEffectCategory type, int color) { super(type, color); } + + public void onPotionApplied(LivingEntity target, @Nullable NbtCompound extraData) { + + } } diff --git a/src/main/java/io/wispforest/affinity/statuseffects/TransportationStatusEffect.java b/src/main/java/io/wispforest/affinity/statuseffects/TransportationStatusEffect.java new file mode 100644 index 00000000..6650e06f --- /dev/null +++ b/src/main/java/io/wispforest/affinity/statuseffects/TransportationStatusEffect.java @@ -0,0 +1,83 @@ +package io.wispforest.affinity.statuseffects; + +import io.wispforest.affinity.component.AffinityComponents; +import io.wispforest.affinity.component.TransportationComponent; +import io.wispforest.affinity.item.EchoShardItem; +import io.wispforest.affinity.misc.EntityTeleporter; +import io.wispforest.affinity.misc.ServerTaskScheduler; +import io.wispforest.affinity.object.AffinityParticleSystems; +import io.wispforest.affinity.object.AffinityStatusEffects; +import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.attribute.AttributeContainer; +import net.minecraft.entity.effect.StatusEffectCategory; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.potion.PotionUtil; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryKey; +import org.jetbrains.annotations.Nullable; + +public class TransportationStatusEffect extends AffinityStatusEffect { + public TransportationStatusEffect(StatusEffectCategory category, int color) { + super(category, color); + } + + static { + ItemTooltipCallback.EVENT.register((stack, context, lines) -> { + if (!PotionUtil.getPotionEffects(stack).stream().anyMatch(x -> x.getEffectType() == AffinityStatusEffects.TRANSPORTATION)) return; + if (!stack.hasNbt() || !stack.getNbt().contains("ExtraPotionNbt", NbtElement.COMPOUND_TYPE)) return; + + EchoShardItem.formatLocationTooltip(stack.getSubNbt("ExtraPotionNbt"), lines); + }); + } + + public static void createCloudFor(LivingEntity entity) { + AffinityParticleSystems.TRANSPORTATION_CLOUD.spawn(entity.world, entity.getPos()); + } + + @Override + public void onRemoved(final LivingEntity entity, AttributeContainer attributes, int amplifier) { + if (entity.world.isClient) return; + + ServerTaskScheduler.scheduleTask(server -> { + var e = entity; + + TransportationComponent component = AffinityComponents.TRANSPORTATION.get(e); + ServerWorld w = server.getWorld(RegistryKey.of(Registry.WORLD_KEY, component.getWorld())); + Vec3d pos = component.getPos(); + createCloudFor(e); + e = EntityTeleporter.teleport(e, w, pos, e.getYaw(), e.getPitch()); + createCloudFor(e); + e.addStatusEffect(new StatusEffectInstance(StatusEffects.NAUSEA, 5 * 20)); + }); + } + + @Override + public void onPotionApplied(LivingEntity target, @Nullable NbtCompound extraData) { + if (extraData == null) return; + if (target.world.isClient) return; + + if (target.hasStatusEffect(AffinityStatusEffects.TRANSPORTATION)) + target.removeStatusEffectInternal(AffinityStatusEffects.TRANSPORTATION); + + var component = AffinityComponents.TRANSPORTATION.get(target); + component.setPos(target.getPos()); + component.setWorld(target.getWorld().getRegistryKey().getValue()); + + var pos = EchoShardItem.POS.get(extraData); + var targetWorldId = EchoShardItem.WORLD.get(extraData); + var targetWorld = target.getServer().getWorld(RegistryKey.of(Registry.WORLD_KEY, targetWorldId)); + + ServerTaskScheduler.scheduleTask(server -> { + createCloudFor(target); + var newEntity = EntityTeleporter.teleport(target, targetWorld, pos, target.getYaw(), target.getPitch()); + createCloudFor(newEntity); + newEntity.addStatusEffect(new StatusEffectInstance(StatusEffects.NAUSEA, 5 * 20)); + }); + } +} diff --git a/src/main/resources/affinity.mixins.json b/src/main/resources/affinity.mixins.json index 166b9f8c..a25c6c31 100644 --- a/src/main/resources/affinity.mixins.json +++ b/src/main/resources/affinity.mixins.json @@ -8,6 +8,8 @@ "AbstractBlockMixin$AbstractBlockStateMixin", "ActiveTargetGoalMixin", "AnvilScreenHandlerMixin", + "AreaEffectCloudEntityMixin", + "ArrowEntityMixin", "BoatEntityTypeMixin", "BrewingRecipeRegistryMixin", "EnchantmentHelperMixin", @@ -19,11 +21,13 @@ "MobEntityMixin", "MobSpawnerLogicMixin", "PlayerEntityMixin", + "PotionEntityMixin", "PotionItemMixin", "PotionUtilMixin", "StatusEffectMixin", "StatusEffectUtilMixin", "ThrowablePotionItemMixin", + "TippedArrowRecipeMixin", "TrackTargetGoalMixin", "access.BlockEntityTypeAccessor", "access.FilledMapItemInvoker", diff --git a/src/main/resources/assets/affinity/lang/en_us.json b/src/main/resources/assets/affinity/lang/en_us.json index 399742a9..8d920de0 100644 --- a/src/main/resources/assets/affinity/lang/en_us.json +++ b/src/main/resources/assets/affinity/lang/en_us.json @@ -65,11 +65,13 @@ "item.affinity.collection_staff": "Collection Staff", "item.affinity.aethum_flux_bottle": "Bottle of Aethum Flux", "item.affinity.resplendent_gem": "Resplendent Gem", + "item.affinity.echo_shard": "Echo Shard", "text.affinity.attuned_shard_range": "Range: ", "text.affinity.attuned_shard_range.value": "%d blocks", "text.affinity.attuned_shard_max_transfer": "Rate: ", "text.affinity.attuned_shard_max_transfer.value": "%d af/t", + "text.affinity.echo_shard_location": "Linked to %s @ %s, %s, %s", "item.affinity.mildly_attuned_amethyst_shard": "Mildly Attuned Amethyst Shard", "item.affinity.fairly_attuned_amethyst_shard": "Fairly Attuned Amethyst Shard", "item.affinity.greatly_attuned_amethyst_shard": "Greatly Attuned Amethyst Shard", @@ -146,6 +148,12 @@ "death.attack.doom_potion": "%1$s got ripped and torn", "death.attack.threw_doom_potion": "%1$s discovered a very powerful mechanic that sadly had to be disabled", + "effect.affinity.transportation": "Transportation", + "item.minecraft.potion.effect.transportation": "Potion of Transportation", + "item.minecraft.splash_potion.effect.transportation": "Splash Potion of Transportation", + "item.minecraft.lingering_potion.effect.transportation": "Lingering Potion of Transportation", + "item.minecraft.tipped_arrow.effect.transportation": "Arrow of Transportation", + "item.minecraft.potion.effect.dubious": "Dubious Potion", "item.minecraft.splash_potion.effect.dubious": "Twhicc Splashy Potion", "item.minecraft.lingering_potion.effect.dubious": "Twhicc Goes-Everyhwere Potion", @@ -168,5 +176,9 @@ "item.minecraft.potion.effect.hunger": "Potion of Enervation", "item.minecraft.splash_potion.effect.hunger": "Splash Potion of Enervation", "item.minecraft.lingering_potion.effect.hunger": "Lingering Potion of Enervation", - "item.minecraft.tipped_arrow.effect.hunger": "Arrow of Enervation" + "item.minecraft.tipped_arrow.effect.hunger": "Arrow of Enervation", + + "dimension.minecraft.overworld": "Overworld", + "dimension.minecraft.the_nether": "The Nether", + "dimension.minecraft.the_end": "The End" } \ No newline at end of file diff --git a/src/main/resources/assets/affinity/models/item/echo_shard.json b/src/main/resources/assets/affinity/models/item/echo_shard.json new file mode 100644 index 00000000..b4007183 --- /dev/null +++ b/src/main/resources/assets/affinity/models/item/echo_shard.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "affinity:item/echo_shard" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/affinity/textures/item/echo_shard.png b/src/main/resources/assets/affinity/textures/item/echo_shard.png new file mode 100644 index 00000000..cf11dbea Binary files /dev/null and b/src/main/resources/assets/affinity/textures/item/echo_shard.png differ diff --git a/src/main/resources/data/affinity/recipes/potion_mixing/transportation.json b/src/main/resources/data/affinity/recipes/potion_mixing/transportation.json new file mode 100644 index 00000000..e166479e --- /dev/null +++ b/src/main/resources/data/affinity/recipes/potion_mixing/transportation.json @@ -0,0 +1,16 @@ +{ + "type": "affinity:potion_mixing", + "output": "affinity:transportation", + "effect_inputs": [ + "minecraft:speed" + ], + "item_inputs": [ + { + "item": "minecraft:ender_pearl" + }, + { + "item": "affinity:echo_shard", + "copy_nbt": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index a8caf292..537063c3 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -58,7 +58,8 @@ "affinity:glowing_color", "affinity:player_aethum", "affinity:chunk_aethum", - "affinity:entity_flags" + "affinity:entity_flags", + "affinity:transportation" ] } }