From 16a7b96f815cc7863a54265262658b080c7194ec Mon Sep 17 00:00:00 2001 From: Sollace Date: Tue, 28 Nov 2023 15:26:55 +0000 Subject: [PATCH] wip --- .../com/minelittlepony/unicopia/Unicopia.java | 18 ++-- .../unicopia/diet/Affliction.java | 70 ++++++++++++++++ .../unicopia/diet/AfflictionType.java | 44 ++++++++++ .../minelittlepony/unicopia/diet/Ailment.java | 23 ++++++ .../diet/ClearLoveSicknessAffliction.java | 35 ++++++++ .../unicopia/diet/CompoundAffliction.java | 50 +++++++++++ .../unicopia/diet/DietProfile.java | 82 +++++++++++++++++++ .../unicopia/diet/DietsLoader.java | 74 +++++++++++++++++ .../minelittlepony/unicopia/diet/Effect.java | 56 +++++++++++++ .../diet/MultiplyHungerAffliction.java | 43 ++++++++++ .../unicopia/diet/PonyDiets.java | 57 +++++++++++++ .../minelittlepony/unicopia/diet/Range.java | 39 +++++++++ .../unicopia/diet/StatusEffectAffliction.java | 78 ++++++++++++++++++ .../unicopia/item/toxin/Toxin.java | 1 - .../unicopia/network/MsgServerResources.java | 11 ++- .../handler/ClientNetworkHandlerImpl.java | 2 + .../resources/assets/unicopia/lang/en_us.json | 1 + .../unicopia/diets/{ => races}/alicorn.json | 0 .../data/unicopia/diets/{ => races}/bat.json | 0 .../diets/{ => races}/changeling.json | 0 .../unicopia/diets/{ => races}/earth.json | 0 .../diets/{ => races}/hippogriff.json | 0 .../unicopia/diets/{ => races}/human.json | 0 .../unicopia/diets/{ => races}/kirin.json | 0 .../unicopia/diets/{ => races}/pegasus.json | 0 .../unicopia/diets/{ => races}/seapony.json | 0 .../unicopia/diets/{ => races}/unicorn.json | 0 27 files changed, 675 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/diet/Affliction.java create mode 100644 src/main/java/com/minelittlepony/unicopia/diet/AfflictionType.java create mode 100644 src/main/java/com/minelittlepony/unicopia/diet/Ailment.java create mode 100644 src/main/java/com/minelittlepony/unicopia/diet/ClearLoveSicknessAffliction.java create mode 100644 src/main/java/com/minelittlepony/unicopia/diet/CompoundAffliction.java create mode 100644 src/main/java/com/minelittlepony/unicopia/diet/DietProfile.java create mode 100644 src/main/java/com/minelittlepony/unicopia/diet/DietsLoader.java create mode 100644 src/main/java/com/minelittlepony/unicopia/diet/Effect.java create mode 100644 src/main/java/com/minelittlepony/unicopia/diet/MultiplyHungerAffliction.java create mode 100644 src/main/java/com/minelittlepony/unicopia/diet/PonyDiets.java create mode 100644 src/main/java/com/minelittlepony/unicopia/diet/Range.java create mode 100644 src/main/java/com/minelittlepony/unicopia/diet/StatusEffectAffliction.java rename src/main/resources/data/unicopia/diets/{ => races}/alicorn.json (100%) rename src/main/resources/data/unicopia/diets/{ => races}/bat.json (100%) rename src/main/resources/data/unicopia/diets/{ => races}/changeling.json (100%) rename src/main/resources/data/unicopia/diets/{ => races}/earth.json (100%) rename src/main/resources/data/unicopia/diets/{ => races}/hippogriff.json (100%) rename src/main/resources/data/unicopia/diets/{ => races}/human.json (100%) rename src/main/resources/data/unicopia/diets/{ => races}/kirin.json (100%) rename src/main/resources/data/unicopia/diets/{ => races}/pegasus.json (100%) rename src/main/resources/data/unicopia/diets/{ => races}/seapony.json (100%) rename src/main/resources/data/unicopia/diets/{ => races}/unicorn.json (100%) diff --git a/src/main/java/com/minelittlepony/unicopia/Unicopia.java b/src/main/java/com/minelittlepony/unicopia/Unicopia.java index 4b1f6f3a..e683b561 100644 --- a/src/main/java/com/minelittlepony/unicopia/Unicopia.java +++ b/src/main/java/com/minelittlepony/unicopia/Unicopia.java @@ -22,6 +22,8 @@ import com.minelittlepony.unicopia.command.Commands; import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate; import com.minelittlepony.unicopia.container.SpellbookChapterLoader; import com.minelittlepony.unicopia.container.UScreenHandlers; +import com.minelittlepony.unicopia.diet.AfflictionType; +import com.minelittlepony.unicopia.diet.DietsLoader; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.effect.UPotions; import com.minelittlepony.unicopia.entity.mob.UEntities; @@ -83,11 +85,7 @@ public class Unicopia implements ModInitializer { }); NocturnalSleepManager.bootstrap(); - ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(TreeTypeLoader.INSTANCE); - ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(UEnchantments.POISONED_JOKE); - ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(new TraitLoader()); - ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(StateMapLoader.INSTANCE); - ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(SpellbookChapterLoader.INSTANCE); + registerServerDataReloaders(ResourceManagerHelper.get(ResourceType.SERVER_DATA)); UGameEvents.bootstrap(); UBlocks.bootstrap(); @@ -98,6 +96,7 @@ public class Unicopia implements ModInitializer { USounds.bootstrap(); Race.bootstrap(); SpellType.bootstrap(); + AfflictionType.bootstrap(); Abilities.bootstrap(); UScreenHandlers.bootstrap(); UWorldGen.bootstrap(); @@ -105,6 +104,15 @@ public class Unicopia implements ModInitializer { UDamageTypes.bootstrap(); } + private void registerServerDataReloaders(ResourceManagerHelper registry) { + registry.registerReloadListener(TreeTypeLoader.INSTANCE); + registry.registerReloadListener(UEnchantments.POISONED_JOKE); + registry.registerReloadListener(new TraitLoader()); + registry.registerReloadListener(StateMapLoader.INSTANCE); + registry.registerReloadListener(SpellbookChapterLoader.INSTANCE); + registry.registerReloadListener(new DietsLoader()); + } + public interface SidedAccess { Optional getPony(); diff --git a/src/main/java/com/minelittlepony/unicopia/diet/Affliction.java b/src/main/java/com/minelittlepony/unicopia/diet/Affliction.java new file mode 100644 index 00000000..d3d69b3b --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/diet/Affliction.java @@ -0,0 +1,70 @@ +package com.minelittlepony.unicopia.diet; + +import java.util.List; + +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.text.Text; +import net.minecraft.util.dynamic.Codecs; + +public interface Affliction { + Text NO_EFFECT_TEXT = Text.of("No Effect"); + Affliction EMPTY = new Affliction() { + @Override + public void afflict(PlayerEntity player, ItemStack stack) { } + + @Override + public Text getName() { + return NO_EFFECT_TEXT; + } + + @Override + public AfflictionType getType() { + return AfflictionType.EMPTY; + } + + @Override + public void toBuffer(PacketByteBuf buffer) { } + }; + Codec CODEC = Codecs.xor(Codec.list(AfflictionType.CODEC) + .mapResult(null) + .xmap( + afflictions -> { + afflictions.removeIf(f -> f.getType() == AfflictionType.EMPTY); + return switch (afflictions.size()) { + case 0 -> EMPTY; + case 1 -> afflictions.get(0); + default -> new CompoundAffliction(afflictions); + }; + }, + affliction -> ((CompoundAffliction)affliction).afflictions + ), AfflictionType.CODEC).xmap( + either -> either.left().or(either::right).get(), + affliction -> affliction instanceof CompoundAffliction ? Either.left(affliction) : Either.right(affliction) + ); + + void afflict(PlayerEntity player, ItemStack stack); + + default void appendTooltip(List tooltip) { + tooltip.add(getName()); + } + + Text getName(); + + AfflictionType getType(); + + void toBuffer(PacketByteBuf buffer); + + static void write(PacketByteBuf buffer, Affliction affliction) { + buffer.writeIdentifier(affliction.getType().id()); + affliction.toBuffer(buffer); + } + + static Affliction read(PacketByteBuf buffer) { + return AfflictionType.REGISTRY.get(buffer.readIdentifier()).reader().apply(buffer); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/diet/AfflictionType.java b/src/main/java/com/minelittlepony/unicopia/diet/AfflictionType.java new file mode 100644 index 00000000..5fae39f1 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/diet/AfflictionType.java @@ -0,0 +1,44 @@ +package com.minelittlepony.unicopia.diet; + +import com.google.gson.JsonObject; +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.util.RegistryUtils; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; + +import net.minecraft.network.PacketByteBuf.PacketReader; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; +import net.minecraft.util.dynamic.Codecs; + +public record AfflictionType(Codec codec, Identifier id, PacketReader reader) { + public static final String DEFAULT_ID = "unicopia:apply_status_effect"; + public static final Registry> REGISTRY = RegistryUtils.createDefaulted(Unicopia.id("affliction_type"), DEFAULT_ID); + @SuppressWarnings("unchecked") + public static final Codec CODEC = Codecs.JSON_ELEMENT.flatXmap(json -> { + JsonObject obj = json.getAsJsonObject(); + return Identifier.validate(obj.has("type") ? obj.get("type").getAsString() : AfflictionType.DEFAULT_ID).flatMap(type -> { + return AfflictionType.REGISTRY.get(type).codec().parse(JsonOps.INSTANCE, json); + }); + }, thing -> { + AfflictionType type = thing.getType(); + return ((Codec)type.codec()).encodeStart(JsonOps.INSTANCE, thing).map(json -> { + if (json.isJsonObject()) { + json.getAsJsonObject().addProperty("type", type.id().toString()); + } + return json; + }); + }); + + public static final AfflictionType EMPTY = register("empty", Codec.unit(Affliction.EMPTY), buffer -> Affliction.EMPTY); + public static final AfflictionType MANY = register("many", CompoundAffliction.CODEC, CompoundAffliction::new); + public static final AfflictionType APPLY_STATUS_EFFECT = register("apply_status_effect", StatusEffectAffliction.CODEC, StatusEffectAffliction::new); + public static final AfflictionType MULTIPLY_HUNGER = register("multiply_hunger", MultiplyHungerAffliction.CODEC, MultiplyHungerAffliction::new); + public static final AfflictionType CLEAR_LOVE_SICKNESS = register("clear_love_sickness", ClearLoveSicknessAffliction.CODEC, buffer -> ClearLoveSicknessAffliction.INSTANCE); + + static AfflictionType register(String name, Codec codec, PacketReader reader) { + return Registry.register(REGISTRY, Unicopia.id(name), new AfflictionType<>(codec, Unicopia.id(name), reader)); + } + + public static void bootstrap() { } +} diff --git a/src/main/java/com/minelittlepony/unicopia/diet/Ailment.java b/src/main/java/com/minelittlepony/unicopia/diet/Ailment.java new file mode 100644 index 00000000..f16ae703 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/diet/Ailment.java @@ -0,0 +1,23 @@ +package com.minelittlepony.unicopia.diet; + +import com.minelittlepony.unicopia.item.toxin.Toxicity; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.network.PacketByteBuf; + +public record Ailment(Toxicity toxicity, Affliction effects) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Toxicity.CODEC.fieldOf("toxicity").forGetter(Ailment::toxicity), + Affliction.CODEC.fieldOf("effects").forGetter(Ailment::effects) + ).apply(instance, Ailment::new)); + + public Ailment(PacketByteBuf buffer) { + this(Toxicity.byName(buffer.readString()), Affliction.read(buffer)); + } + + public void toBuffer(PacketByteBuf buffer) { + buffer.writeString(toxicity.name()); + Affliction.write(buffer, effects); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/diet/ClearLoveSicknessAffliction.java b/src/main/java/com/minelittlepony/unicopia/diet/ClearLoveSicknessAffliction.java new file mode 100644 index 00000000..17bbea7d --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/diet/ClearLoveSicknessAffliction.java @@ -0,0 +1,35 @@ +package com.minelittlepony.unicopia.diet; + +import com.minelittlepony.unicopia.entity.effect.UEffects; +import com.mojang.serialization.Codec; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.text.Text; + +public final class ClearLoveSicknessAffliction implements Affliction { + public static final ClearLoveSicknessAffliction INSTANCE = new ClearLoveSicknessAffliction(); + public static final Codec CODEC = Codec.unit(INSTANCE); + + @Override + public AfflictionType getType() { + return AfflictionType.CLEAR_LOVE_SICKNESS; + } + + @Override + public void afflict(PlayerEntity player, ItemStack stack) { + player.heal(stack.isFood() ? stack.getItem().getFoodComponent().getHunger() : 1); + player.removeStatusEffect(StatusEffects.NAUSEA); + player.removeStatusEffect(UEffects.FOOD_POISONING); + } + + @Override + public Text getName() { + return Text.literal("Love"); + } + + @Override + public void toBuffer(PacketByteBuf buffer) { + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/diet/CompoundAffliction.java b/src/main/java/com/minelittlepony/unicopia/diet/CompoundAffliction.java new file mode 100644 index 00000000..ba04a0db --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/diet/CompoundAffliction.java @@ -0,0 +1,50 @@ +package com.minelittlepony.unicopia.diet; + +import java.util.List; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.text.Text; + +class CompoundAffliction implements Affliction { + public final List afflictions; + private final Text name; + + public CompoundAffliction(List afflictions) { + this.afflictions = afflictions; + name = afflictions.stream().map(Affliction::getName).reduce(null, (a, b) -> { + return a == null ? b : a.copy().append(" + ").append(b); + }); + } + + public CompoundAffliction(PacketByteBuf buffer) { + this(buffer.readList(Affliction::read)); + } + + @Override + public void toBuffer(PacketByteBuf buffer) { + buffer.writeCollection(afflictions, Affliction::write); + } + + @Override + public AfflictionType getType() { + return AfflictionType.MANY; + } + + @Override + public void appendTooltip(List tooltip) { + afflictions.forEach(i -> i.appendTooltip(tooltip)); + } + + @Override + public Text getName() { + return name; + } + + + @Override + public void afflict(PlayerEntity player, ItemStack stack) { + afflictions.forEach(i -> i.afflict(player, stack)); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/diet/DietProfile.java b/src/main/java/com/minelittlepony/unicopia/diet/DietProfile.java new file mode 100644 index 00000000..bb14b3e4 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/diet/DietProfile.java @@ -0,0 +1,82 @@ +package com.minelittlepony.unicopia.diet; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.tag.TagKey; + +public record DietProfile( + float defaultMultiplier, + float foragingMultiplier, + List multipliers, + List effects + ) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.FLOAT.fieldOf("default_multiplier").forGetter(DietProfile::defaultMultiplier), + Codec.FLOAT.fieldOf("foraging_multiplier").forGetter(DietProfile::foragingMultiplier), + Codec.list(Multiplier.CODEC).fieldOf("multipliers").forGetter(DietProfile::multipliers), + Codec.list(Effect.CODEC).fieldOf("effects").forGetter(DietProfile::effects) + ).apply(instance, DietProfile::new)); + + public DietProfile(PacketByteBuf buffer) { + this(buffer.readFloat(), buffer.readFloat(), buffer.readList(Multiplier::new), buffer.readList(Effect::new)); + } + + public void toBuffer(PacketByteBuf buffer) { + buffer.writeFloat(defaultMultiplier); + buffer.writeFloat(foragingMultiplier); + buffer.writeCollection(multipliers, (b, t) -> t.toBuffer(b)); + buffer.writeCollection(effects, (b, t) -> t.toBuffer(b)); + } + + public Optional findMultiplier(ItemStack stack) { + return multipliers.stream().filter(m -> m.test(stack)).findFirst(); + } + + public Optional findEffect(ItemStack stack) { + return effects.stream().filter(m -> m.test(stack)).findFirst(); + } + + public record Multiplier( + Set> tags, + float hunger, + float saturation + ) implements Predicate { + public static final Codec>> TAGS_CODEC = Codec.list(TagKey.unprefixedCodec(RegistryKeys.ITEM)).xmap( + l -> l.stream().distinct().collect(Collectors.toSet()), + set -> new ArrayList<>(set) + ); + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + TAGS_CODEC.fieldOf("tags").forGetter(Multiplier::tags), + Codec.FLOAT.fieldOf("hunger").forGetter(Multiplier::hunger), + Codec.FLOAT.fieldOf("saturation").forGetter(Multiplier::saturation) + ).apply(instance, Multiplier::new)); + + public Multiplier(PacketByteBuf buffer) { + this(buffer.readCollection(HashSet::new, p -> TagKey.of(RegistryKeys.ITEM, p.readIdentifier())), buffer.readFloat(), buffer.readFloat()); + } + + @Override + public boolean test(ItemStack stack) { + return tags.stream().anyMatch(tag -> stack.isIn(tag)); + } + + public void toBuffer(PacketByteBuf buffer) { + buffer.writeCollection(tags, (p, t) -> p.writeIdentifier(t.id())); + buffer.writeFloat(hunger); + buffer.writeFloat(saturation); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/diet/DietsLoader.java b/src/main/java/com/minelittlepony/unicopia/diet/DietsLoader.java new file mode 100644 index 00000000..35801d78 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/diet/DietsLoader.java @@ -0,0 +1,74 @@ +package com.minelittlepony.unicopia.diet; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import org.slf4j.Logger; + +import com.google.gson.JsonElement; +import com.minelittlepony.unicopia.Race; +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.util.Resources; +import com.mojang.logging.LogUtils; +import com.mojang.serialization.JsonOps; + +import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; +import net.minecraft.resource.JsonDataLoader; +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; +import net.minecraft.util.profiler.Profiler; + +public class DietsLoader implements IdentifiableResourceReloadListener { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final Identifier ID = Unicopia.id("diets"); + + @Override + public Identifier getFabricId() { + return ID; + } + + @Override + public CompletableFuture reload(Synchronizer sync, ResourceManager manager, + Profiler prepareProfiler, Profiler applyProfiler, + Executor prepareExecutor, Executor applyExecutor) { + + var dietsLoadTask = loadData(manager, prepareExecutor, "diets/races").thenApplyAsync(data -> { + Map profiles = new HashMap<>(); + for (var entry : data.entrySet()) { + Identifier id = entry.getKey(); + Race.REGISTRY.getOrEmpty(id).ifPresentOrElse(race -> DietProfile.CODEC.parse(JsonOps.INSTANCE, entry.getValue()) + .resultOrPartial(LOGGER::error) + .ifPresent(profile -> profiles.put(race, profile)), () -> LOGGER.warn("Skipped diet for unknown race: " + id)); + } + return profiles; + }, applyExecutor); + + var effectsLoadTask = loadData(manager, prepareExecutor, "diets/food_effects").thenApplyAsync(data -> data.values().stream() + .map(value -> Effect.CODEC.parse(JsonOps.INSTANCE, value) + .resultOrPartial(LOGGER::error)) + .filter(Optional::isPresent) + .map(Optional::get) + .toList()); + + var future = CompletableFuture.allOf(dietsLoadTask, effectsLoadTask); + sync.getClass(); + return future.thenRunAsync(() -> { + PonyDiets.load(new PonyDiets( + dietsLoadTask.getNow(Map.of()), + effectsLoadTask.getNow(List.of()) + )); + }, applyExecutor); + } + + private static CompletableFuture> loadData(ResourceManager manager, Executor prepareExecutor, String path) { + return CompletableFuture.supplyAsync(() -> { + Map results = new HashMap<>(); + JsonDataLoader.load(manager, path, Resources.GSON, results); + return results; + }); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/diet/Effect.java b/src/main/java/com/minelittlepony/unicopia/diet/Effect.java new file mode 100644 index 00000000..49a3c249 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/diet/Effect.java @@ -0,0 +1,56 @@ +package com.minelittlepony.unicopia.diet; + +import java.util.Optional; +import java.util.function.Predicate; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.tag.TagKey; + +public record Effect( + TagKey tag, + Optional foodComponent, + Ailment ailment +) implements Predicate { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + TagKey.unprefixedCodec(RegistryKeys.ITEM).fieldOf("tag").forGetter(Effect::tag), + FoodComponent.CODEC.optionalFieldOf("food_component").forGetter(Effect::foodComponent), + Ailment.CODEC.fieldOf("ailment").forGetter(Effect::ailment) + ).apply(instance, Effect::new)); + + public Effect(PacketByteBuf buffer) { + this(TagKey.of(RegistryKeys.ITEM, buffer.readIdentifier()), buffer.readOptional(FoodComponent::new), new Ailment(buffer)); + } + + public void toBuffer(PacketByteBuf buffer) { + buffer.writeIdentifier(tag.id()); + buffer.writeOptional(foodComponent, (b, f) -> f.toBuffer(b)); + ailment.toBuffer(buffer); + } + + @Override + public boolean test(ItemStack stack) { + return stack.isIn(tag); + } + + public record FoodComponent (float hunger, float saturation) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.FLOAT.fieldOf("hunger").forGetter(FoodComponent::hunger), + Codec.FLOAT.fieldOf("saturation").forGetter(FoodComponent::saturation) + ).apply(instance, FoodComponent::new)); + + public FoodComponent(PacketByteBuf buffer) { + this(buffer.readFloat(), buffer.readFloat()); + } + + public void toBuffer(PacketByteBuf buffer) { + buffer.writeFloat(hunger); + buffer.writeFloat(saturation); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/diet/MultiplyHungerAffliction.java b/src/main/java/com/minelittlepony/unicopia/diet/MultiplyHungerAffliction.java new file mode 100644 index 00000000..d503ff7e --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/diet/MultiplyHungerAffliction.java @@ -0,0 +1,43 @@ +package com.minelittlepony.unicopia.diet; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.FoodComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.text.Text; + +public record MultiplyHungerAffliction(float multiplier) implements Affliction { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.FLOAT.fieldOf("multiplier").forGetter(MultiplyHungerAffliction::multiplier) + ).apply(instance, MultiplyHungerAffliction::new)); + + public MultiplyHungerAffliction(PacketByteBuf buffer) { + this(buffer.readFloat()); + } + + @Override + public void toBuffer(PacketByteBuf buffer) { + buffer.writeFloat(multiplier); + } + + @Override + public AfflictionType getType() { + return AfflictionType.MULTIPLY_HUNGER; + } + + @Override + public void afflict(PlayerEntity player, ItemStack stack) { + FoodComponent food = stack.getItem().getFoodComponent(); + player.getHungerManager().setFoodLevel((int)(food.getHunger() * multiplier)); + player.getHungerManager().setSaturationLevel(food.getSaturationModifier() * multiplier); + } + + @Override + public Text getName() { + return Text.translatable("Lose %s%% hunger", multiplier * 100); + } + +} diff --git a/src/main/java/com/minelittlepony/unicopia/diet/PonyDiets.java b/src/main/java/com/minelittlepony/unicopia/diet/PonyDiets.java new file mode 100644 index 00000000..5ef1a7e8 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/diet/PonyDiets.java @@ -0,0 +1,57 @@ +package com.minelittlepony.unicopia.diet; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import com.minelittlepony.unicopia.Race; +import com.minelittlepony.unicopia.diet.DietProfile.Multiplier; +import com.minelittlepony.unicopia.entity.player.Pony; + +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; + +public class PonyDiets { + private final Map diets; + private final List effects; + + static PonyDiets INSTANCE = new PonyDiets(Map.of(), List.of()); + + public static PonyDiets getinstance() { + return INSTANCE; + } + + public static void load(PonyDiets diets) { + INSTANCE = diets; + } + + PonyDiets(Map diets, List effects) { + this.diets = diets; + this.effects = effects; + } + + public PonyDiets(PacketByteBuf buffer) { + this(buffer.readMap(b -> b.readRegistryValue(Race.REGISTRY), DietProfile::new), buffer.readList(Effect::new)); + } + + public void toBuffer(PacketByteBuf buffer) { + buffer.writeMap(diets, (b, r) -> b.writeRegistryValue(Race.REGISTRY, r), (b, e) -> e.toBuffer(b)); + buffer.writeCollection(effects, (b, e) -> e.toBuffer(b)); + } + + public Optional getDiet(Race race) { + return Optional.ofNullable(diets.get(race)); + } + + public Optional getEffects(ItemStack stack) { + return effects.stream().filter(effect -> effect.test(stack)).findFirst(); + } + + public Optional getEffects(ItemStack stack, Pony pony) { + return getDiet(pony.getObservedSpecies()).flatMap(diet -> diet.findEffect(stack)).or(() -> getEffects(stack)); + } + + public Optional getMultiplier(ItemStack stack, Pony pony) { + return getDiet(pony.getObservedSpecies()).flatMap(diet -> diet.findMultiplier(stack)); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/diet/Range.java b/src/main/java/com/minelittlepony/unicopia/diet/Range.java new file mode 100644 index 00000000..5b33c3a8 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/diet/Range.java @@ -0,0 +1,39 @@ +package com.minelittlepony.unicopia.diet; + +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.dynamic.Codecs; + +public record Range(int min, int max) { + public static final Codec CODEC = Codecs.xor( + Codec.INT.xmap(value -> Range.of(value, -1), range -> range.min()), + RecordCodecBuilder.create(instance -> instance.group( + Codec.INT.fieldOf("min").forGetter(Range::min), + Codec.INT.fieldOf("max").forGetter(Range::max) + ).apply(instance, Range::of)) + ).xmap(either -> either.left().or(either::right).get(), l -> Either.right(l)); + + public static Range of(int min, int max) { + return new Range(min, max); + } + + public static Range of(PacketByteBuf buffer) { + return of(buffer.readInt(), buffer.readInt()); + } + + public void toBuffer(PacketByteBuf buffer) { + buffer.writeInt(min); + buffer.writeInt(max); + } + + public int getTicks(int currentTicks) { + return clamp((min * 20) + currentTicks); + } + + public int clamp(int value) { + return max > 0 ? Math.min(value, max * 20) : value; + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/diet/StatusEffectAffliction.java b/src/main/java/com/minelittlepony/unicopia/diet/StatusEffectAffliction.java new file mode 100644 index 00000000..9005b5dc --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/diet/StatusEffectAffliction.java @@ -0,0 +1,78 @@ +package com.minelittlepony.unicopia.diet; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.entity.attribute.EntityAttributes; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.registry.Registries; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.StringHelper; +import net.minecraft.util.math.MathHelper; + +public record StatusEffectAffliction(Identifier effect, Range seconds, Range amplifier, int chance) implements Affliction { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Identifier.CODEC.fieldOf("effect").forGetter(StatusEffectAffliction::effect), + Range.CODEC.fieldOf("seconds").forGetter(StatusEffectAffliction::seconds), + Range.CODEC.optionalFieldOf("amplifier", Range.of(0, -1)).forGetter(StatusEffectAffliction::amplifier), + Codec.INT.optionalFieldOf("chance", 0).forGetter(StatusEffectAffliction::chance) + ).apply(instance, StatusEffectAffliction::new)); + + public StatusEffectAffliction(PacketByteBuf buffer) { + this(buffer.readIdentifier(), Range.of(buffer), Range.of(buffer), buffer.readInt()); + } + + public void toBuffer(PacketByteBuf buffer) { + buffer.writeIdentifier(effect); + seconds.toBuffer(buffer); + amplifier.toBuffer(buffer); + buffer.writeInt(chance); + } + + @Override + public AfflictionType getType() { + return AfflictionType.APPLY_STATUS_EFFECT; + } + + @Override + public void afflict(PlayerEntity player, ItemStack stack) { + if (chance > 0 && player.getWorld().random.nextInt(chance) > 0) { + return; + } + Registries.STATUS_EFFECT.getOrEmpty(effect).ifPresent(effect -> { + float health = player.getHealth(); + StatusEffectInstance current = player.getStatusEffect(effect); + player.addStatusEffect(new StatusEffectInstance(effect, + seconds.getTicks(current == null ? 0 : current.getDuration()), + amplifier.getTicks(current == null ? 0 : current.getAmplifier()) + )); + // keep original health + if (effect.getAttributeModifiers().containsKey(EntityAttributes.GENERIC_MAX_HEALTH)) { + player.setHealth(MathHelper.clamp(health, 0, player.getMaxHealth())); + } + }); + } + + @Override + public Text getName() { + return Registries.STATUS_EFFECT.getOrEmpty(effect).map(effect -> { + MutableText text = effect.getName().copy(); + + if (amplifier.min() > 0) { + text = Text.translatable("potion.withAmplifier", text, Text.translatable("potion.potency." + (amplifier.min() * 20))); + } + + text = Text.translatable("potion.withDuration", text, StringHelper.formatTicks(seconds.min() * 20)); + + if (chance > 0) { + text = Text.translatable("potion.withChance", chance, text); + } + return (Text)text; + }).orElse(Text.of("No Effect")); + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/item/toxin/Toxin.java b/src/main/java/com/minelittlepony/unicopia/item/toxin/Toxin.java index 36cbe1f8..6866a7b8 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/toxin/Toxin.java +++ b/src/main/java/com/minelittlepony/unicopia/item/toxin/Toxin.java @@ -94,7 +94,6 @@ public interface Toxin extends Affliction { }; } - @Deprecated static Toxin of(Text name, Affliction affliction) { return new Toxin() { @Override diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgServerResources.java b/src/main/java/com/minelittlepony/unicopia/network/MsgServerResources.java index bd08c145..dd3d8a27 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgServerResources.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgServerResources.java @@ -6,6 +6,7 @@ import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.ability.data.tree.TreeTypeLoader; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.container.SpellbookChapterLoader; +import com.minelittlepony.unicopia.diet.PonyDiets; import com.sollace.fabwork.api.packets.Packet; import net.minecraft.entity.player.PlayerEntity; @@ -15,13 +16,15 @@ import net.minecraft.util.Identifier; public record MsgServerResources ( Map traits, Map chapters, - Map treeTypes + Map treeTypes, + PonyDiets diets ) implements Packet { public MsgServerResources() { this( SpellTraits.all(), SpellbookChapterLoader.INSTANCE.getChapters(), - TreeTypeLoader.INSTANCE.getEntries() + TreeTypeLoader.INSTANCE.getEntries(), + PonyDiets.getinstance() ); } @@ -29,7 +32,8 @@ public record MsgServerResources ( this( buffer.readMap(PacketByteBuf::readIdentifier, SpellTraits::fromPacket), InteractionManager.instance().readChapters(buffer), - buffer.readMap(PacketByteBuf::readIdentifier, TreeTypeLoader.TreeTypeDef::new) + buffer.readMap(PacketByteBuf::readIdentifier, TreeTypeLoader.TreeTypeDef::new), + new PonyDiets(buffer) ); } @@ -38,5 +42,6 @@ public record MsgServerResources ( buffer.writeMap(traits, PacketByteBuf::writeIdentifier, (r, v) -> v.write(r)); buffer.writeMap(chapters, PacketByteBuf::writeIdentifier, (r, v) -> ((SpellbookChapterLoader.Chapter)v).write(r)); buffer.writeMap(treeTypes, PacketByteBuf::writeIdentifier, (r, v) -> v.write(r)); + diets.toBuffer(buffer); } } diff --git a/src/main/java/com/minelittlepony/unicopia/network/handler/ClientNetworkHandlerImpl.java b/src/main/java/com/minelittlepony/unicopia/network/handler/ClientNetworkHandlerImpl.java index a9e25256..d2dd56cd 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/handler/ClientNetworkHandlerImpl.java +++ b/src/main/java/com/minelittlepony/unicopia/network/handler/ClientNetworkHandlerImpl.java @@ -15,6 +15,7 @@ import com.minelittlepony.unicopia.client.UnicopiaClient; import com.minelittlepony.unicopia.client.gui.TribeSelectionScreen; import com.minelittlepony.unicopia.client.gui.spellbook.ClientChapters; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.Chapter; +import com.minelittlepony.unicopia.diet.PonyDiets; import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.network.*; @@ -97,6 +98,7 @@ public class ClientNetworkHandlerImpl { SpellTraits.load(packet.traits()); ClientChapters.load((Map)packet.chapters()); TreeTypes.load(packet.treeTypes()); + PonyDiets.load(packet.diets()); } private void handlePlayerAnimation(PlayerEntity sender, MsgPlayerAnimationChange packet) { diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 6f45d142..12db0baf 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -374,6 +374,7 @@ "item.minecraft.lingering_potion.effect.unicopia.tribe_swap_hippogriff": "Lingering Potion of Hippogriff Metamorphosis", "item.minecraft.tipped_arrow.effect.unicopia.tribe_swap_hippogriff": "Arrow of Hippogriff Metamorphosis", + "potion.withChance": "1 in %s chance of %s", "potion.potency.6": "VII", "spell.unicopia.frost": "Frost", diff --git a/src/main/resources/data/unicopia/diets/alicorn.json b/src/main/resources/data/unicopia/diets/races/alicorn.json similarity index 100% rename from src/main/resources/data/unicopia/diets/alicorn.json rename to src/main/resources/data/unicopia/diets/races/alicorn.json diff --git a/src/main/resources/data/unicopia/diets/bat.json b/src/main/resources/data/unicopia/diets/races/bat.json similarity index 100% rename from src/main/resources/data/unicopia/diets/bat.json rename to src/main/resources/data/unicopia/diets/races/bat.json diff --git a/src/main/resources/data/unicopia/diets/changeling.json b/src/main/resources/data/unicopia/diets/races/changeling.json similarity index 100% rename from src/main/resources/data/unicopia/diets/changeling.json rename to src/main/resources/data/unicopia/diets/races/changeling.json diff --git a/src/main/resources/data/unicopia/diets/earth.json b/src/main/resources/data/unicopia/diets/races/earth.json similarity index 100% rename from src/main/resources/data/unicopia/diets/earth.json rename to src/main/resources/data/unicopia/diets/races/earth.json diff --git a/src/main/resources/data/unicopia/diets/hippogriff.json b/src/main/resources/data/unicopia/diets/races/hippogriff.json similarity index 100% rename from src/main/resources/data/unicopia/diets/hippogriff.json rename to src/main/resources/data/unicopia/diets/races/hippogriff.json diff --git a/src/main/resources/data/unicopia/diets/human.json b/src/main/resources/data/unicopia/diets/races/human.json similarity index 100% rename from src/main/resources/data/unicopia/diets/human.json rename to src/main/resources/data/unicopia/diets/races/human.json diff --git a/src/main/resources/data/unicopia/diets/kirin.json b/src/main/resources/data/unicopia/diets/races/kirin.json similarity index 100% rename from src/main/resources/data/unicopia/diets/kirin.json rename to src/main/resources/data/unicopia/diets/races/kirin.json diff --git a/src/main/resources/data/unicopia/diets/pegasus.json b/src/main/resources/data/unicopia/diets/races/pegasus.json similarity index 100% rename from src/main/resources/data/unicopia/diets/pegasus.json rename to src/main/resources/data/unicopia/diets/races/pegasus.json diff --git a/src/main/resources/data/unicopia/diets/seapony.json b/src/main/resources/data/unicopia/diets/races/seapony.json similarity index 100% rename from src/main/resources/data/unicopia/diets/seapony.json rename to src/main/resources/data/unicopia/diets/races/seapony.json diff --git a/src/main/resources/data/unicopia/diets/unicorn.json b/src/main/resources/data/unicopia/diets/races/unicorn.json similarity index 100% rename from src/main/resources/data/unicopia/diets/unicorn.json rename to src/main/resources/data/unicopia/diets/races/unicorn.json