diff --git a/src/main/java/com/minelittlepony/unicopia/advancement/CustomEventCriterion.java b/src/main/java/com/minelittlepony/unicopia/advancement/CustomEventCriterion.java index 5d70896d..ca2d64e6 100644 --- a/src/main/java/com/minelittlepony/unicopia/advancement/CustomEventCriterion.java +++ b/src/main/java/com/minelittlepony/unicopia/advancement/CustomEventCriterion.java @@ -1,13 +1,8 @@ package com.minelittlepony.unicopia.advancement; -import java.util.HashSet; -import java.util.Set; - import org.jetbrains.annotations.Nullable; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.entity.player.Pony; @@ -32,19 +27,10 @@ public class CustomEventCriterion extends AbstractCriterion races = new HashSet<>(); - - if (json.has("race")) { - json.get("race").getAsJsonArray().forEach(el -> { - races.add(Race.fromName(el.getAsString(), Race.EARTH)); - }); - } - return new Conditions( playerPredicate, JsonHelper.getString(json, "event"), - races, + RacePredicate.fromJson(json.get("race")), json.has("flying") ? json.get("flying").getAsBoolean() : null, JsonHelper.getInt(json, "repeats", 0) ); @@ -67,13 +53,13 @@ public class CustomEventCriterion extends AbstractCriterion races; + private final RacePredicate races; private final Boolean flying; private final int repeatCount; - public Conditions(LootContextPredicate playerPredicate, String event, Set races, Boolean flying, int repeatCount) { + public Conditions(LootContextPredicate playerPredicate, String event, RacePredicate races, Boolean flying, int repeatCount) { super(ID, playerPredicate); this.event = event; this.races = races; @@ -83,7 +69,7 @@ public class CustomEventCriterion extends AbstractCriterion 0 && count % repeatCount == 0)); } @@ -92,11 +78,7 @@ public class CustomEventCriterion extends AbstractCriterion arr.add(Race.REGISTRY.getId(r).toString())); - json.add("race", arr); - } + json.add("race", races.toJson()); if (flying != null) { json.addProperty("flying", flying); } diff --git a/src/main/java/com/minelittlepony/unicopia/advancement/RacePredicate.java b/src/main/java/com/minelittlepony/unicopia/advancement/RacePredicate.java new file mode 100644 index 00000000..c24c3eeb --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/advancement/RacePredicate.java @@ -0,0 +1,73 @@ +package com.minelittlepony.unicopia.advancement; + +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.jetbrains.annotations.Nullable; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.minelittlepony.unicopia.Race; +import com.minelittlepony.unicopia.entity.player.Pony; + +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.JsonHelper; + +public record RacePredicate(Set include, Set exclude) implements Predicate { + public static final RacePredicate EMPTY = new RacePredicate(Set.of(), Set.of()); + + public static RacePredicate fromJson(JsonElement json) { + if (json == null || json.isJsonNull()) { + return EMPTY; + } + + if (json.isJsonArray()) { + return of(getRaces(json.getAsJsonArray()), Set.of()); + } + + JsonObject root = JsonHelper.asObject(json, "race"); + return of(getRaces(root, "include"), getRaces(root, "exclude")); + } + + private static RacePredicate of(Set include, Set exclude) { + if (include.isEmpty() && exclude.isEmpty()) { + return EMPTY; + } + return new RacePredicate(include, exclude); + } + + private static @Nullable Set getRaces(JsonObject json, String field) { + return json.has(field) ? getRaces(JsonHelper.getArray(json, field)) : Set.of(); + } + + private static Set getRaces(JsonArray array) { + return array.asList() + .stream() + .map(el -> Race.fromName(el.getAsString(), Race.EARTH)) + .distinct() + .collect(Collectors.toSet()); + } + + @Override + public boolean test(ServerPlayerEntity player) { + Race race = Pony.of(player).getSpecies(); + return (include.isEmpty() || include.contains(race)) && !(!exclude.isEmpty() && exclude.contains(race)); + } + + public JsonObject toJson() { + JsonObject json = new JsonObject(); + if (!include.isEmpty()) { + JsonArray arr = new JsonArray(); + include.forEach(r -> arr.add(Race.REGISTRY.getId(r).toString())); + json.add("include", arr); + } + if (!exclude.isEmpty()) { + JsonArray arr = new JsonArray(); + exclude.forEach(r -> arr.add(Race.REGISTRY.getId(r).toString())); + json.add("exclude", arr); + } + return json; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/advancement/SendViaDragonBreathScrollCriterion.java b/src/main/java/com/minelittlepony/unicopia/advancement/SendViaDragonBreathScrollCriterion.java new file mode 100644 index 00000000..8dce0b3a --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/advancement/SendViaDragonBreathScrollCriterion.java @@ -0,0 +1,109 @@ +package com.minelittlepony.unicopia.advancement; + +import java.util.Optional; +import java.util.function.BiConsumer; + +import com.google.gson.JsonObject; +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.entity.player.Pony; + +import net.fabricmc.fabric.api.util.TriState; +import net.minecraft.advancement.criterion.AbstractCriterion; +import net.minecraft.advancement.criterion.AbstractCriterionConditions; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.predicate.entity.AdvancementEntityPredicateDeserializer; +import net.minecraft.predicate.entity.AdvancementEntityPredicateSerializer; +import net.minecraft.predicate.entity.LootContextPredicate; +import net.minecraft.predicate.item.ItemPredicate; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; +import net.minecraft.util.TypeFilter; + +public class SendViaDragonBreathScrollCriterion extends AbstractCriterion { + private static final Identifier ID = Unicopia.id("send_dragon_breath"); + + @Override + public Identifier getId() { + return ID; + } + + @Override + protected Conditions conditionsFromJson(JsonObject json, LootContextPredicate playerPredicate, AdvancementEntityPredicateDeserializer deserializer) { + return new Conditions(playerPredicate, + ItemPredicate.fromJson(json.get("item")), + JsonHelper.getBoolean(json, "is_receiving_end", false), + json.has("recipient_name") ? Optional.of(JsonHelper.getString(json, "recipient_name")) : Optional.empty(), + json.has("recipient_present") ? TriState.of(JsonHelper.getBoolean(json, "recipient_present")) : TriState.DEFAULT, + json.has("counter") ? Optional.of(JsonHelper.getString(json, "counter")) : Optional.empty(), + RacePredicate.fromJson(json.get("race")) + ); + } + + public void triggerSent(PlayerEntity player, ItemStack payload, String recipient, BiConsumer counterCallback) { + if (player instanceof ServerPlayerEntity spe) { + trigger(spe, c -> { + if (c.test(spe, payload, recipient, false)) { + c.counter.ifPresent(counter -> { + counterCallback.accept(counter, Pony.of(spe).getAdvancementProgress().compute(counter, (key, i) -> i == null ? 1 : i + 1)); + }); + return true; + } + return false; + }); + } + } + + public void triggerReceived(LivingEntity recipient, ItemStack payload) { + if (recipient instanceof ServerPlayerEntity spe) { + trigger(spe, c -> c.test(spe, payload, recipient.getDisplayName().getString(), true)); + } + } + + public static class Conditions extends AbstractCriterionConditions { + private final ItemPredicate item; + private final boolean isReceivingEnd; + private final Optional recipientName; + private final TriState recipientPresent; + private final Optional counter; + private final RacePredicate races; + + public Conditions(LootContextPredicate playerPredicate, ItemPredicate item, boolean isReceivingEnd, Optional recipient, TriState recipientPresent, Optional counter, RacePredicate races) { + super(ID, playerPredicate); + this.item = item; + this.isReceivingEnd = isReceivingEnd; + this.recipientName = recipient; + this.recipientPresent = recipientPresent; + this.counter = counter; + this.races = races; + } + + public boolean test(ServerPlayerEntity player, ItemStack payload, String recipient, boolean receiving) { + return isReceivingEnd == receiving + && races.test(player) + && item.test(payload) + && recipientName.map(expectedRecipientname -> recipient.equalsIgnoreCase(expectedRecipientname)).orElse(true) + && (recipientPresent == TriState.DEFAULT || isRecipientAbsent(player.getServerWorld(), recipient) != recipientPresent.get()); + } + + private boolean isRecipientAbsent(ServerWorld world, String recipient) { + return world.getEntitiesByType(TypeFilter.instanceOf(LivingEntity.class), e -> e.hasCustomName() && e.getCustomName().getString().equalsIgnoreCase(recipient)).isEmpty(); + } + + @Override + public JsonObject toJson(AdvancementEntityPredicateSerializer serializer) { + JsonObject json = super.toJson(serializer); + json.add("item", item.toJson()); + json.add("race", races.toJson()); + recipientName.ifPresent(recipient -> json.addProperty("recipient_name", recipient)); + if (recipientPresent != TriState.DEFAULT) { + json.addProperty("recipient_present", recipientPresent.getBoxed()); + } + counter.ifPresent(counter -> json.addProperty("counter", counter)); + return json; + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/advancement/UCriteria.java b/src/main/java/com/minelittlepony/unicopia/advancement/UCriteria.java index 2d37e2d8..05121bc8 100644 --- a/src/main/java/com/minelittlepony/unicopia/advancement/UCriteria.java +++ b/src/main/java/com/minelittlepony/unicopia/advancement/UCriteria.java @@ -5,6 +5,7 @@ import net.minecraft.advancement.criterion.Criteria; public interface UCriteria { CustomEventCriterion CUSTOM_EVENT = Criteria.register(new CustomEventCriterion()); RaceChangeCriterion PLAYER_CHANGE_RACE = Criteria.register(new RaceChangeCriterion()); + SendViaDragonBreathScrollCriterion SEND_DRAGON_BREATH = Criteria.register(new SendViaDragonBreathScrollCriterion()); CustomEventCriterion.Trigger LOOK_INTO_SUN = CUSTOM_EVENT.createTrigger("look_into_sun"); CustomEventCriterion.Trigger WEAR_SHADES = CUSTOM_EVENT.createTrigger("wear_shades"); @@ -16,8 +17,6 @@ public interface UCriteria { CustomEventCriterion.Trigger SPOOK_MOB = CUSTOM_EVENT.createTrigger("spook_mob"); CustomEventCriterion.Trigger SHED_FEATHER = CUSTOM_EVENT.createTrigger("shed_feather"); CustomEventCriterion.Trigger THROW_MUFFIN = CUSTOM_EVENT.createTrigger("throw_muffin"); - CustomEventCriterion.Trigger SEND_OATS = CUSTOM_EVENT.createTrigger("send_oats"); - CustomEventCriterion.Trigger RECEIVE_OATS = CUSTOM_EVENT.createTrigger("receive_oats"); CustomEventCriterion.Trigger BREAK_WINDOW = CUSTOM_EVENT.createTrigger("break_window"); CustomEventCriterion.Trigger KILL_PHANTOM_WHILE_FLYING = CUSTOM_EVENT.createTrigger("kill_phantom_while_flying"); CustomEventCriterion.Trigger USE_CONSUMPTION = CUSTOM_EVENT.createTrigger("use_consumption"); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Living.java b/src/main/java/com/minelittlepony/unicopia/entity/Living.java index 44d70c5f..372f06ff 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Living.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Living.java @@ -447,10 +447,7 @@ public abstract class Living implements Equine, Caste itemEntity.setPosition(randomPos); itemEntity.getWorld().spawnEntity(itemEntity); entity.getWorld().playSoundFromEntity(null, entity, USounds.ITEM_DRAGON_BREATH_ARRIVE, entity.getSoundCategory(), 1, 1); - - if (item == UItems.OATS && entity instanceof PlayerEntity player) { - UCriteria.RECEIVE_OATS.trigger(player); - } + UCriteria.SEND_DRAGON_BREATH.triggerReceived(entity, payload.copy()); } } }); diff --git a/src/main/java/com/minelittlepony/unicopia/item/DragonBreathScrollItem.java b/src/main/java/com/minelittlepony/unicopia/item/DragonBreathScrollItem.java index 36f5c11b..7c5713cf 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/DragonBreathScrollItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/DragonBreathScrollItem.java @@ -5,10 +5,12 @@ import java.util.UUID; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.server.world.DragonBreathStore; +import com.minelittlepony.unicopia.server.world.UnicopiaWorldProperties; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.Hand; import net.minecraft.util.TypedActionResult; import net.minecraft.world.World; @@ -30,10 +32,15 @@ public class DragonBreathScrollItem extends Item { stack.split(1); if (!world.isClient) { - if (payload.getItem() == UItems.OATS) { - UCriteria.SEND_OATS.trigger(player); - } - DragonBreathStore.get(world).put(stack.getName().getString(), payload.split(1)); + String recipient = stack.getName().getString(); + UCriteria.SEND_DRAGON_BREATH.triggerSent(player, payload, recipient, (counterName, count) -> { + if (count == 1 && "dings_on_celestias_head".equals(counterName)) { + UnicopiaWorldProperties properties = UnicopiaWorldProperties.forWorld((ServerWorld)world); + properties.setTangentalSkyAngle(properties.getTangentalSkyAngle() + 15); + player.playSound(USounds.Vanilla.BLOCK_ANVIL_HIT, 0.3F, 1); + } + }); + DragonBreathStore.get(world).put(recipient, payload.split(1)); } player.playSound(USounds.ITEM_DRAGON_BREATH_SCROLL_USE, 1, 1); return TypedActionResult.consume(stack); diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index db501f6a..baf6ef6c 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -892,6 +892,11 @@ "advancements.unicopia.extra_spooky.title": "Extra Spooky", "advancements.unicopia.extra_spooky.description": "Spook a mob so hard it drops a brick", + "advancements.unicopia.sweet_sweet_revenge.title": "Sweet Sweet Revenge", + "advancements.unicopia.sweet_sweet_revenge.description": "Get Celestia back for burning your eyes", + "advancements.unicopia.blasphemy.title": "Blasphemy!", + "advancements.unicopia.blasphemy.description": "Ding Celestia on the noggin. Oops!", + "advancements.unicopia.earth_route.title": "Path of the Pony", "advancements.unicopia.earth_route.description": "Join the Apple Clan", "advancements.unicopia.sticks_and_stones.title": "Sticks and Stones", diff --git a/src/main/resources/data/unicopia/advancements/recipes/pineapple_crown.json b/src/main/resources/data/unicopia/advancements/recipes/pineapple_crown.json new file mode 100644 index 00000000..13904af1 --- /dev/null +++ b/src/main/resources/data/unicopia/advancements/recipes/pineapple_crown.json @@ -0,0 +1,30 @@ +{ + "parent": "minecraft:recipes/root", + "rewards": { + "recipes": [ + "unicopia:pineapple_crown" + ] + }, + "criteria": { + "has_ingredients": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { "items": [ "unicopia:pineapple" ] } + ] + } + }, + "has_the_recipe": { + "trigger": "minecraft:recipe_unlocked", + "conditions": { + "recipe": "unicopia:pineapple_crown" + } + } + }, + "requirements": [ + [ + "has_ingredients", + "has_the_recipe" + ] + ] +} \ No newline at end of file diff --git a/src/main/resources/data/unicopia/advancements/unicopia/bat/sweet_sweet_revenge.json b/src/main/resources/data/unicopia/advancements/unicopia/bat/sweet_sweet_revenge.json new file mode 100644 index 00000000..66bbcd5f --- /dev/null +++ b/src/main/resources/data/unicopia/advancements/unicopia/bat/sweet_sweet_revenge.json @@ -0,0 +1,37 @@ +{ + "parent": "unicopia:unicopia/bat/praise_the_sun", + "display": { + "icon": { + "item": "minecraft:anvil" + }, + "title": { + "translate": "advancements.unicopia.sweet_sweet_revenge.title" + }, + "description": { + "translate": "advancements.unicopia.sweet_sweet_revenge.description" + }, + "frame": "task", + "show_toast": true, + "announce_to_chat": true, + "hidden": true + }, + "criteria": { + "ding_sun": { + "trigger": "unicopia:send_dragon_breath", + "conditions": { + "item": { + "tag": "unicopia:is_delivered_aggressively" + }, + "recipient_name": "princess celestia", + "recipient_present": false, + "counter": "dings_on_celestias_head", + "race": { + "include": [ "unicopia:bat" ] + } + } + } + }, + "requirements": [ + [ "ding_sun" ] + ] +} diff --git a/src/main/resources/data/unicopia/advancements/unicopia/earth/blasphemy.json b/src/main/resources/data/unicopia/advancements/unicopia/earth/blasphemy.json new file mode 100644 index 00000000..f3285cbd --- /dev/null +++ b/src/main/resources/data/unicopia/advancements/unicopia/earth/blasphemy.json @@ -0,0 +1,37 @@ +{ + "parent": "unicopia:unicopia/earth/earth_route", + "display": { + "icon": { + "item": "minecraft:anvil" + }, + "title": { + "translate": "advancements.unicopia.blasphemy.title" + }, + "description": { + "translate": "advancements.unicopia.blasphemy.description" + }, + "frame": "task", + "show_toast": true, + "announce_to_chat": true, + "hidden": true + }, + "criteria": { + "ding_sun": { + "trigger": "unicopia:send_dragon_breath", + "conditions": { + "item": { + "tag": "unicopia:is_delivered_aggressively" + }, + "recipient_name": "princess celestia", + "recipient_present": false, + "counter": "dings_on_celestias_head", + "race": { + "exclude": [ "unicopia:bat" ] + } + } + } + }, + "requirements": [ + [ "ding_sun" ] + ] +} diff --git a/src/main/resources/data/unicopia/advancements/unicopia/eat_pinecone.json b/src/main/resources/data/unicopia/advancements/unicopia/earth/eat_pinecone.json similarity index 91% rename from src/main/resources/data/unicopia/advancements/unicopia/eat_pinecone.json rename to src/main/resources/data/unicopia/advancements/unicopia/earth/eat_pinecone.json index f94d2c4c..43c1e8ee 100644 --- a/src/main/resources/data/unicopia/advancements/unicopia/eat_pinecone.json +++ b/src/main/resources/data/unicopia/advancements/unicopia/earth/eat_pinecone.json @@ -1,5 +1,5 @@ { - "parent": "unicopia:unicopia/root/earth/earth_route", + "parent": "unicopia:unicopia/earth/earth_route", "display": { "icon": { "item": "unicopia:pinecone" diff --git a/src/main/resources/data/unicopia/advancements/unicopia/earth/imported_oats.json b/src/main/resources/data/unicopia/advancements/unicopia/earth/imported_oats.json index ff21ca36..8c58133d 100644 --- a/src/main/resources/data/unicopia/advancements/unicopia/earth/imported_oats.json +++ b/src/main/resources/data/unicopia/advancements/unicopia/earth/imported_oats.json @@ -17,15 +17,20 @@ }, "criteria": { "send_oats": { - "trigger": "unicopia:custom", + "trigger": "unicopia:send_dragon_breath", "conditions": { - "event": "send_oats" + "item": { + "items": [ "unicopia:oats", "unicopia:imported_oats" ] + } } }, "receive_oats": { - "trigger": "unicopia:custom", + "trigger": "unicopia:send_dragon_breath", "conditions": { - "event": "receive_oats" + "item": { + "items": [ "unicopia:oats", "unicopia:imported_oats" ] + }, + "is_receiving_end": true } } },