mirror of
https://github.com/Sollace/Unicopia.git
synced 2025-02-08 06:26:43 +01:00
wip
This commit is contained in:
parent
8a85f0709e
commit
16a7b96f81
27 changed files with 675 additions and 9 deletions
|
@ -22,6 +22,8 @@ import com.minelittlepony.unicopia.command.Commands;
|
||||||
import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate;
|
import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate;
|
||||||
import com.minelittlepony.unicopia.container.SpellbookChapterLoader;
|
import com.minelittlepony.unicopia.container.SpellbookChapterLoader;
|
||||||
import com.minelittlepony.unicopia.container.UScreenHandlers;
|
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.damage.UDamageTypes;
|
||||||
import com.minelittlepony.unicopia.entity.effect.UPotions;
|
import com.minelittlepony.unicopia.entity.effect.UPotions;
|
||||||
import com.minelittlepony.unicopia.entity.mob.UEntities;
|
import com.minelittlepony.unicopia.entity.mob.UEntities;
|
||||||
|
@ -83,11 +85,7 @@ public class Unicopia implements ModInitializer {
|
||||||
});
|
});
|
||||||
NocturnalSleepManager.bootstrap();
|
NocturnalSleepManager.bootstrap();
|
||||||
|
|
||||||
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(TreeTypeLoader.INSTANCE);
|
registerServerDataReloaders(ResourceManagerHelper.get(ResourceType.SERVER_DATA));
|
||||||
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);
|
|
||||||
|
|
||||||
UGameEvents.bootstrap();
|
UGameEvents.bootstrap();
|
||||||
UBlocks.bootstrap();
|
UBlocks.bootstrap();
|
||||||
|
@ -98,6 +96,7 @@ public class Unicopia implements ModInitializer {
|
||||||
USounds.bootstrap();
|
USounds.bootstrap();
|
||||||
Race.bootstrap();
|
Race.bootstrap();
|
||||||
SpellType.bootstrap();
|
SpellType.bootstrap();
|
||||||
|
AfflictionType.bootstrap();
|
||||||
Abilities.bootstrap();
|
Abilities.bootstrap();
|
||||||
UScreenHandlers.bootstrap();
|
UScreenHandlers.bootstrap();
|
||||||
UWorldGen.bootstrap();
|
UWorldGen.bootstrap();
|
||||||
|
@ -105,6 +104,15 @@ public class Unicopia implements ModInitializer {
|
||||||
UDamageTypes.bootstrap();
|
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 {
|
public interface SidedAccess {
|
||||||
Optional<Pony> getPony();
|
Optional<Pony> getPony();
|
||||||
|
|
||||||
|
|
|
@ -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<Affliction> 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<Text> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<T extends Affliction>(Codec<T> codec, Identifier id, PacketReader<T> reader) {
|
||||||
|
public static final String DEFAULT_ID = "unicopia:apply_status_effect";
|
||||||
|
public static final Registry<AfflictionType<?>> REGISTRY = RegistryUtils.createDefaulted(Unicopia.id("affliction_type"), DEFAULT_ID);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static final Codec<Affliction> CODEC = Codecs.JSON_ELEMENT.<Affliction>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<Affliction>)type.codec()).encodeStart(JsonOps.INSTANCE, thing).map(json -> {
|
||||||
|
if (json.isJsonObject()) {
|
||||||
|
json.getAsJsonObject().addProperty("type", type.id().toString());
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
public static final AfflictionType<Affliction> EMPTY = register("empty", Codec.unit(Affliction.EMPTY), buffer -> Affliction.EMPTY);
|
||||||
|
public static final AfflictionType<Affliction> MANY = register("many", CompoundAffliction.CODEC, CompoundAffliction::new);
|
||||||
|
public static final AfflictionType<StatusEffectAffliction> APPLY_STATUS_EFFECT = register("apply_status_effect", StatusEffectAffliction.CODEC, StatusEffectAffliction::new);
|
||||||
|
public static final AfflictionType<MultiplyHungerAffliction> MULTIPLY_HUNGER = register("multiply_hunger", MultiplyHungerAffliction.CODEC, MultiplyHungerAffliction::new);
|
||||||
|
public static final AfflictionType<ClearLoveSicknessAffliction> CLEAR_LOVE_SICKNESS = register("clear_love_sickness", ClearLoveSicknessAffliction.CODEC, buffer -> ClearLoveSicknessAffliction.INSTANCE);
|
||||||
|
|
||||||
|
static <T extends Affliction> AfflictionType<T> register(String name, Codec<T> codec, PacketReader<T> reader) {
|
||||||
|
return Registry.register(REGISTRY, Unicopia.id(name), new AfflictionType<>(codec, Unicopia.id(name), reader));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void bootstrap() { }
|
||||||
|
}
|
23
src/main/java/com/minelittlepony/unicopia/diet/Ailment.java
Normal file
23
src/main/java/com/minelittlepony/unicopia/diet/Ailment.java
Normal file
|
@ -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<Ailment> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<ClearLoveSicknessAffliction> 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) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Affliction> afflictions;
|
||||||
|
private final Text name;
|
||||||
|
|
||||||
|
public CompoundAffliction(List<Affliction> 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<Text> 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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Multiplier> multipliers,
|
||||||
|
List<Effect> effects
|
||||||
|
) {
|
||||||
|
public static final Codec<DietProfile> 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<Multiplier> findMultiplier(ItemStack stack) {
|
||||||
|
return multipliers.stream().filter(m -> m.test(stack)).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Effect> findEffect(ItemStack stack) {
|
||||||
|
return effects.stream().filter(m -> m.test(stack)).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Multiplier(
|
||||||
|
Set<TagKey<Item>> tags,
|
||||||
|
float hunger,
|
||||||
|
float saturation
|
||||||
|
) implements Predicate<ItemStack> {
|
||||||
|
public static final Codec<Set<TagKey<Item>>> TAGS_CODEC = Codec.list(TagKey.unprefixedCodec(RegistryKeys.ITEM)).xmap(
|
||||||
|
l -> l.stream().distinct().collect(Collectors.toSet()),
|
||||||
|
set -> new ArrayList<>(set)
|
||||||
|
);
|
||||||
|
public static final Codec<Multiplier> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Void> reload(Synchronizer sync, ResourceManager manager,
|
||||||
|
Profiler prepareProfiler, Profiler applyProfiler,
|
||||||
|
Executor prepareExecutor, Executor applyExecutor) {
|
||||||
|
|
||||||
|
var dietsLoadTask = loadData(manager, prepareExecutor, "diets/races").thenApplyAsync(data -> {
|
||||||
|
Map<Race, DietProfile> 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<Map<Identifier, JsonElement>> loadData(ResourceManager manager, Executor prepareExecutor, String path) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
Map<Identifier, JsonElement> results = new HashMap<>();
|
||||||
|
JsonDataLoader.load(manager, path, Resources.GSON, results);
|
||||||
|
return results;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
56
src/main/java/com/minelittlepony/unicopia/diet/Effect.java
Normal file
56
src/main/java/com/minelittlepony/unicopia/diet/Effect.java
Normal file
|
@ -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<Item> tag,
|
||||||
|
Optional<FoodComponent> foodComponent,
|
||||||
|
Ailment ailment
|
||||||
|
) implements Predicate<ItemStack> {
|
||||||
|
public static final Codec<Effect> 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<FoodComponent> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<MultiplyHungerAffliction> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<Race, DietProfile> diets;
|
||||||
|
private final List<Effect> 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<Race, DietProfile> diets, List<Effect> 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<DietProfile> getDiet(Race race) {
|
||||||
|
return Optional.ofNullable(diets.get(race));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Effect> getEffects(ItemStack stack) {
|
||||||
|
return effects.stream().filter(effect -> effect.test(stack)).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Effect> getEffects(ItemStack stack, Pony pony) {
|
||||||
|
return getDiet(pony.getObservedSpecies()).flatMap(diet -> diet.findEffect(stack)).or(() -> getEffects(stack));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Multiplier> getMultiplier(ItemStack stack, Pony pony) {
|
||||||
|
return getDiet(pony.getObservedSpecies()).flatMap(diet -> diet.findMultiplier(stack));
|
||||||
|
}
|
||||||
|
}
|
39
src/main/java/com/minelittlepony/unicopia/diet/Range.java
Normal file
39
src/main/java/com/minelittlepony/unicopia/diet/Range.java
Normal file
|
@ -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<Range> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<StatusEffectAffliction> 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"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -94,7 +94,6 @@ public interface Toxin extends Affliction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
static Toxin of(Text name, Affliction affliction) {
|
static Toxin of(Text name, Affliction affliction) {
|
||||||
return new Toxin() {
|
return new Toxin() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -6,6 +6,7 @@ import com.minelittlepony.unicopia.InteractionManager;
|
||||||
import com.minelittlepony.unicopia.ability.data.tree.TreeTypeLoader;
|
import com.minelittlepony.unicopia.ability.data.tree.TreeTypeLoader;
|
||||||
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
|
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
|
||||||
import com.minelittlepony.unicopia.container.SpellbookChapterLoader;
|
import com.minelittlepony.unicopia.container.SpellbookChapterLoader;
|
||||||
|
import com.minelittlepony.unicopia.diet.PonyDiets;
|
||||||
import com.sollace.fabwork.api.packets.Packet;
|
import com.sollace.fabwork.api.packets.Packet;
|
||||||
|
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
@ -15,13 +16,15 @@ import net.minecraft.util.Identifier;
|
||||||
public record MsgServerResources (
|
public record MsgServerResources (
|
||||||
Map<Identifier, SpellTraits> traits,
|
Map<Identifier, SpellTraits> traits,
|
||||||
Map<Identifier, ?> chapters,
|
Map<Identifier, ?> chapters,
|
||||||
Map<Identifier, TreeTypeLoader.TreeTypeDef> treeTypes
|
Map<Identifier, TreeTypeLoader.TreeTypeDef> treeTypes,
|
||||||
|
PonyDiets diets
|
||||||
) implements Packet<PlayerEntity> {
|
) implements Packet<PlayerEntity> {
|
||||||
public MsgServerResources() {
|
public MsgServerResources() {
|
||||||
this(
|
this(
|
||||||
SpellTraits.all(),
|
SpellTraits.all(),
|
||||||
SpellbookChapterLoader.INSTANCE.getChapters(),
|
SpellbookChapterLoader.INSTANCE.getChapters(),
|
||||||
TreeTypeLoader.INSTANCE.getEntries()
|
TreeTypeLoader.INSTANCE.getEntries(),
|
||||||
|
PonyDiets.getinstance()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +32,8 @@ public record MsgServerResources (
|
||||||
this(
|
this(
|
||||||
buffer.readMap(PacketByteBuf::readIdentifier, SpellTraits::fromPacket),
|
buffer.readMap(PacketByteBuf::readIdentifier, SpellTraits::fromPacket),
|
||||||
InteractionManager.instance().readChapters(buffer),
|
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(traits, PacketByteBuf::writeIdentifier, (r, v) -> v.write(r));
|
||||||
buffer.writeMap(chapters, PacketByteBuf::writeIdentifier, (r, v) -> ((SpellbookChapterLoader.Chapter)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));
|
buffer.writeMap(treeTypes, PacketByteBuf::writeIdentifier, (r, v) -> v.write(r));
|
||||||
|
diets.toBuffer(buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import com.minelittlepony.unicopia.client.UnicopiaClient;
|
||||||
import com.minelittlepony.unicopia.client.gui.TribeSelectionScreen;
|
import com.minelittlepony.unicopia.client.gui.TribeSelectionScreen;
|
||||||
import com.minelittlepony.unicopia.client.gui.spellbook.ClientChapters;
|
import com.minelittlepony.unicopia.client.gui.spellbook.ClientChapters;
|
||||||
import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.Chapter;
|
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.mob.UEntities;
|
||||||
import com.minelittlepony.unicopia.entity.player.Pony;
|
import com.minelittlepony.unicopia.entity.player.Pony;
|
||||||
import com.minelittlepony.unicopia.network.*;
|
import com.minelittlepony.unicopia.network.*;
|
||||||
|
@ -97,6 +98,7 @@ public class ClientNetworkHandlerImpl {
|
||||||
SpellTraits.load(packet.traits());
|
SpellTraits.load(packet.traits());
|
||||||
ClientChapters.load((Map<Identifier, Chapter>)packet.chapters());
|
ClientChapters.load((Map<Identifier, Chapter>)packet.chapters());
|
||||||
TreeTypes.load(packet.treeTypes());
|
TreeTypes.load(packet.treeTypes());
|
||||||
|
PonyDiets.load(packet.diets());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePlayerAnimation(PlayerEntity sender, MsgPlayerAnimationChange packet) {
|
private void handlePlayerAnimation(PlayerEntity sender, MsgPlayerAnimationChange packet) {
|
||||||
|
|
|
@ -374,6 +374,7 @@
|
||||||
"item.minecraft.lingering_potion.effect.unicopia.tribe_swap_hippogriff": "Lingering Potion of Hippogriff Metamorphosis",
|
"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",
|
"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",
|
"potion.potency.6": "VII",
|
||||||
|
|
||||||
"spell.unicopia.frost": "Frost",
|
"spell.unicopia.frost": "Frost",
|
||||||
|
|
Loading…
Reference in a new issue