Change how item traits are defined to make my life easier

This commit is contained in:
Sollace 2022-01-11 12:22:24 +02:00
parent dccce36445
commit 23211ba7b1
2 changed files with 123 additions and 39 deletions

View file

@ -1,6 +1,7 @@
package com.minelittlepony.unicopia.ability.magic.spell.trait; package com.minelittlepony.unicopia.ability.magic.spell.trait;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
@ -17,6 +18,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.client.gui.ItemTraitsTooltipRenderer; import com.minelittlepony.unicopia.client.gui.ItemTraitsTooltipRenderer;
import com.minelittlepony.unicopia.util.InventoryUtil; import com.minelittlepony.unicopia.util.InventoryUtil;
@ -32,7 +34,6 @@ import net.minecraft.network.PacketByteBuf;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.registry.Registry;
public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> { public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
public static final SpellTraits EMPTY = new SpellTraits(Map.of()); public static final SpellTraits EMPTY = new SpellTraits(Map.of());
@ -73,6 +74,10 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
return traits.isEmpty(); return traits.isEmpty();
} }
public boolean isPresent() {
return !isEmpty();
}
public boolean includes(SpellTraits other) { public boolean includes(SpellTraits other) {
return other.stream().allMatch(pair -> { return other.stream().allMatch(pair -> {
return get(pair.getKey()) >= pair.getValue(); return get(pair.getKey()) >= pair.getValue();
@ -142,6 +147,19 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
return this == other || other instanceof SpellTraits && Objects.equals(traits, ((SpellTraits) other).traits); return this == other || other instanceof SpellTraits && Objects.equals(traits, ((SpellTraits) other).traits);
} }
public static SpellTraits union(SpellTraits a, SpellTraits b) {
if (a.isEmpty()) {
return b;
}
if (b.isEmpty()) {
return a;
}
Map<Trait, Float> traits = new HashMap<>();
combine(traits, a.traits);
combine(traits, b.traits);
return traits.isEmpty() ? EMPTY : new SpellTraits(traits);
}
public static SpellTraits union(SpellTraits...many) { public static SpellTraits union(SpellTraits...many) {
Map<Trait, Float> traits = new HashMap<>(); Map<Trait, Float> traits = new HashMap<>();
for (SpellTraits i : many) { for (SpellTraits i : many) {
@ -165,7 +183,7 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
} }
public static SpellTraits of(Item item) { public static SpellTraits of(Item item) {
return TraitLoader.INSTANCE.values.getOrDefault(Registry.ITEM.getId(item), EMPTY); return TraitLoader.INSTANCE.getTraits(item);
} }
public static SpellTraits of(Block block) { public static SpellTraits of(Block block) {
@ -221,6 +239,17 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
return Optional.of(new SpellTraits(entries)); return Optional.of(new SpellTraits(entries));
} }
public static Optional<SpellTraits> fromString(String traits) {
return fromEntries(Arrays.stream(traits.split(" ")).map(a -> a.split(":")).map(pair -> {
Trait key = Trait.fromName(pair[0]).orElse(null);
if (key == null) {
Unicopia.LOGGER.warn("Skipping unknown trait {}", pair[0]);
return null;
}
return Map.entry(key, Float.parseFloat(pair[1]));
}));
}
public static Stream<Map.Entry<Trait, Float>> streamFromNbt(NbtCompound traits) { public static Stream<Map.Entry<Trait, Float>> streamFromNbt(NbtCompound traits) {
return traits.getKeys().stream().map(key -> { return traits.getKeys().stream().map(key -> {
Trait trait = Trait.fromId(key).orElse(null); Trait trait = Trait.fromId(key).orElse(null);

View file

@ -3,44 +3,55 @@ package com.minelittlepony.unicopia.ability.magic.spell.trait;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.util.Resources; import com.minelittlepony.unicopia.util.Resources;
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
import net.minecraft.item.Item;
import net.minecraft.resource.Resource; import net.minecraft.resource.Resource;
import net.minecraft.resource.ResourceManager; import net.minecraft.resource.ResourceManager;
import net.minecraft.resource.SinglePreparationResourceReloader; import net.minecraft.resource.SinglePreparationResourceReloader;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException;
import net.minecraft.util.JsonHelper; import net.minecraft.util.JsonHelper;
import net.minecraft.util.profiler.Profiler; import net.minecraft.util.profiler.Profiler;
import net.minecraft.util.registry.Registry;
public class TraitLoader extends SinglePreparationResourceReloader<Map<Identifier, SpellTraits>> implements IdentifiableResourceReloadListener { public class TraitLoader extends SinglePreparationResourceReloader<Multimap<Identifier, TraitLoader.TraitStream>> implements IdentifiableResourceReloadListener {
private static final Identifier ID = new Identifier("unicopia", "data/traits"); private static final Identifier ID = new Identifier("unicopia", "data/traits");
private static final TypeToken<Map<String, String>> TYPE = new TypeToken<>() {};
public static final TraitLoader INSTANCE = new TraitLoader(); public static final TraitLoader INSTANCE = new TraitLoader();
Map<Identifier, SpellTraits> values = new HashMap<>(); private Map<Identifier, SpellTraits> values = new HashMap<>();
@Override @Override
public Identifier getFabricId() { public Identifier getFabricId() {
return ID; return ID;
} }
public SpellTraits getTraits(Item item) {
return values.getOrDefault(Registry.ITEM.getId(item), SpellTraits.EMPTY);
}
@Override @Override
protected Map<Identifier, SpellTraits> prepare(ResourceManager manager, Profiler profiler) { protected Multimap<Identifier, TraitStream> prepare(ResourceManager manager, Profiler profiler) {
profiler.startTick(); profiler.startTick();
Map<Identifier, SpellTraits> prepared = new HashMap<>(); Multimap<Identifier, TraitStream> prepared = HashMultimap.create();
for (Identifier path : new HashSet<>(manager.findResources("traits", p -> p.endsWith(".json")))) { for (Identifier path : new HashSet<>(manager.findResources("traits", p -> p.endsWith(".json")))) {
profiler.push(path.toString()); profiler.push(path.toString());
@ -49,49 +60,93 @@ public class TraitLoader extends SinglePreparationResourceReloader<Map<Identifie
profiler.push(resource.getResourcePackName()); profiler.push(resource.getResourcePackName());
try (InputStreamReader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) { try (InputStreamReader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) {
Map<String, String> data = JsonHelper.deserialize(Resources.GSON, reader, TYPE); JsonObject data = JsonHelper.deserialize(Resources.GSON, reader, JsonObject.class);
data.forEach((name, set) -> { TraitStream set = TraitStream.of(path, resource.getResourcePackName(), data);
if (set.isEmpty()) {
return;
}
try { if (set.replace()) {
Identifier id = new Identifier(name); prepared.removeAll(path);
SpellTraits.fromEntries(Arrays.stream(set.split(" ")).map(a -> a.split(":")).map(pair -> {
Trait key = Trait.fromName(pair[0]).orElse(null);
if (key == null) {
Unicopia.LOGGER.warn("Failed to load trait entry for item {} in {}. {} is not a valid trait", id, resource.getResourcePackName(), pair[0]);
return null;
} }
try { prepared.put(path, set);
return Map.entry(key, Float.parseFloat(pair[1])); } catch (JsonParseException e) {
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { Unicopia.LOGGER.error("Error reading traits file " + resource.getResourcePackName() + ":" + path, e);
Unicopia.LOGGER.warn("Failed to load trait entry for item {} in {}. {} is not a valid weighting", id, resource.getResourcePackName(), Arrays.toString(pair));
return null;
}
})).ifPresent(value -> prepared.put(id, value));
} catch (InvalidIdentifierException e) {
Unicopia.LOGGER.warn("Failed to load traits for item {} in {}.", name, resource.getResourcePackName(), e);
}
});
} finally { } finally {
profiler.pop(); profiler.pop();
} }
} }
} catch (IOException | JsonParseException e) { } catch (IOException e) {
Unicopia.LOGGER.error("Error reading traits file " + path, e);
} finally { } finally {
profiler.pop(); profiler.pop();
} }
} }
profiler.endTick();
return prepared; return prepared;
} }
@Override @Override
protected void apply(Map<Identifier, SpellTraits> prepared, ResourceManager manager, Profiler profiler) { protected void apply(Multimap<Identifier, TraitStream> prepared, ResourceManager manager, Profiler profiler) {
values = prepared; profiler.startTick();
values = prepared.values().stream()
.flatMap(TraitStream::entries)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, SpellTraits::union));
profiler.endTick();
} }
interface TraitStream {
TypeToken<Map<String, String>> TYPE = new TypeToken<>() {};
boolean replace();
Stream<Map.Entry<Identifier, SpellTraits>> entries();
static TraitStream of(Identifier id, String pack, JsonObject json) {
if (json.has("items") && json.get("items").isJsonObject()) {
return new TraitMap(JsonHelper.getBoolean(json, "replace", false),
Resources.GSON.getAdapter(TYPE).fromJsonTree(json.get("items")).entrySet().stream().collect(Collectors.toMap(
a -> Identifier.tryParse(a.getKey()),
a -> SpellTraits.fromString(a.getValue()).orElse(SpellTraits.EMPTY)
))
);
}
return new TraitSet(
JsonHelper.getBoolean(json, "replace", false),
SpellTraits.fromString(JsonHelper.getString(json, "traits")).orElse(SpellTraits.EMPTY),
StreamSupport.stream(JsonHelper.getArray(json, "items").spliterator(), false)
.map(JsonElement::getAsString)
.map(Identifier::tryParse)
.filter(item -> {
if (item == null || !Registry.ITEM.containsId(item)) {
Unicopia.LOGGER.warn("Skipping unknown item {} in {}:{}", item, pack, id);
return false;
}
return true;
})
.collect(Collectors.toSet())
);
}
record TraitMap (
boolean replace,
Map<Identifier, SpellTraits> items) implements TraitStream {
@Override
public Stream<Entry<Identifier, SpellTraits>> entries() {
return items.entrySet().stream();
}
}
record TraitSet (
boolean replace,
SpellTraits traits,
Set<Identifier> items) implements TraitStream {
@Override
public Stream<Entry<Identifier, SpellTraits>> entries() {
return items().stream().map(item -> Map.entry(item, traits()));
}
}
}
} }