summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorSoniEx2 <endermoneymod@gmail.com>2022-08-17 21:21:26 -0300
committerSoniEx2 <endermoneymod@gmail.com>2022-08-17 21:24:57 -0300
commit2d0b363fe3179087de59d9ef4a2d14af21d89071 (patch)
treebefdb1f281c3abd82c23d9f6855a3237170e47a5
parent731e568ad487b8f7dfbedf22eaca9b12cb3e754d (diff)
[Project] ChewStuff
Chew Necklaces for Minecraft!
-rw-r--r--LICENSE21
-rw-r--r--README.md11
-rw-r--r--build.gradle15
-rw-r--r--gradle.properties13
-rw-r--r--src/main/java/ganarchy/chewstuff/ChewComponent.java93
-rw-r--r--src/main/java/ganarchy/chewstuff/ChewComponents.java28
-rw-r--r--src/main/java/ganarchy/chewstuff/ChewStuff.java90
-rw-r--r--src/main/java/ganarchy/chewstuff/ChewableItem.java195
-rw-r--r--src/main/java/ganarchy/chewstuff/mixin/DesyncFix.java30
-rw-r--r--src/main/java/ganarchy/chewstuff/mixin/PotionMix.java67
-rw-r--r--src/main/java/net/fabricmc/example/ExampleMod.java21
-rw-r--r--src/main/java/net/fabricmc/example/mixin/ExampleMixin.java16
-rw-r--r--src/main/resources/assets/chewstuff/icon.pngbin0 -> 999 bytes
-rw-r--r--src/main/resources/assets/chewstuff/lang/en_us.json6
-rw-r--r--src/main/resources/assets/chewstuff/models/item/hard_chew.json6
-rw-r--r--src/main/resources/assets/chewstuff/models/item/medium_chew.json6
-rw-r--r--src/main/resources/assets/chewstuff/models/item/silicone.json6
-rw-r--r--src/main/resources/assets/chewstuff/models/item/soft_chew.json6
-rw-r--r--src/main/resources/assets/chewstuff/textures/item/hard_chew.pngbin0 -> 143 bytes
-rw-r--r--src/main/resources/assets/chewstuff/textures/item/medium_chew.pngbin0 -> 155 bytes
-rw-r--r--src/main/resources/assets/chewstuff/textures/item/silicone.pngbin0 -> 473 bytes
-rw-r--r--src/main/resources/assets/chewstuff/textures/item/soft_chew.pngbin0 -> 154 bytes
-rw-r--r--src/main/resources/assets/modid/icon.pngbin453 -> 0 bytes
-rw-r--r--src/main/resources/chewstuff.mixins.json (renamed from src/main/resources/modid.mixins.json)5
-rw-r--r--src/main/resources/data/chewstuff/recipes/hard_chew.json20
-rw-r--r--src/main/resources/data/chewstuff/recipes/medium_chew.json17
-rw-r--r--src/main/resources/data/chewstuff/recipes/silicone.json14
-rw-r--r--src/main/resources/data/chewstuff/recipes/soft_chew.json14
-rw-r--r--src/main/resources/data/trinkets/entities/chewstuff.json8
-rw-r--r--src/main/resources/data/trinkets/tags/items/chest/necklace.json8
-rw-r--r--src/main/resources/fabric.mod.json33
31 files changed, 684 insertions, 65 deletions
diff --git a/LICENSE b/LICENSE
index 0e259d4..bbb5704 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,3 +1,24 @@
+BSD Zero Clause License
+
+Copyright (c) 2022 Soni L.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
+- - -
+
+Based on the official fabric mod example, which is under the CC0 license.
+
+- - -
+
 Creative Commons Legal Code
 
 CC0 1.0 Universal
diff --git a/README.md b/README.md
index fd96346..cae175e 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,4 @@
-# Fabric Example Mod
+# Chewable Necklaces
 
-## Setup
-
-For setup instructions please see the [fabric wiki page](https://fabricmc.net/wiki/tutorial:setup) that relates to the IDE that you are using.
-
-## License
-
-This template is available under the CC0 license. Feel free to learn from it and incorporate it in your own projects.
+This was originally a mod about Autism Acceptance Month, but we're updating it
+outside of Autism Acceptance Month.
diff --git a/build.gradle b/build.gradle
index 7a3be5d..c7be481 100644
--- a/build.gradle
+++ b/build.gradle
@@ -16,6 +16,14 @@ repositories {
 	// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
 	// See https://docs.gradle.org/current/userguide/declaring_repositories.html
 	// for more information about repositories.
+	maven {
+		name = "TerraformersMC"
+		url = "https://maven.terraformersmc.com/"
+	}
+	maven {
+		name = "Ladysnake Libs"
+		url = "https://ladysnake.jfrog.io/artifactory/mods"
+	}
 }
 
 dependencies {
@@ -27,6 +35,13 @@ dependencies {
 	// Fabric API. This is technically optional, but you probably want it anyway.
 	modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
 
+	// Trinkets
+	modImplementation("dev.emi:trinkets:${project.trinkets_version}") {
+		exclude group: "net.fabricmc"
+		exclude group: "net.fabricmc.fabric-api"
+		exclude group: "com.terraformersmc"
+	}
+
 	// Uncomment the following line to enable the deprecated Fabric API modules. 
 	// These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time.
 
diff --git a/gradle.properties b/gradle.properties
index a0db7e9..0aef053 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,13 +4,14 @@ org.gradle.jvmargs=-Xmx1G
 # Fabric Properties
 	# check these on https://fabricmc.net/develop
 	minecraft_version=1.19.2
-	yarn_mappings=1.19.2+build.1
-	loader_version=0.14.8
+	yarn_mappings=1.19.2+build.3
+	loader_version=0.14.9
 
 # Mod Properties
-	mod_version = 1.0.0
-	maven_group = com.example
-	archives_base_name = fabric-example-mod
+	mod_version = 1.0.0-SNAPSHOT
+	maven_group = ganarchy.chewstuff.mc_1_19_2
+	archives_base_name = chewstuff-mc1.19.2
 
 # Dependencies
-	fabric_version=0.58.6+1.19.2
+	fabric_version=0.59.0+1.19.2
+	trinkets_version=3.4.0
diff --git a/src/main/java/ganarchy/chewstuff/ChewComponent.java b/src/main/java/ganarchy/chewstuff/ChewComponent.java
new file mode 100644
index 0000000..4b6dcd8
--- /dev/null
+++ b/src/main/java/ganarchy/chewstuff/ChewComponent.java
@@ -0,0 +1,93 @@
+package ganarchy.chewstuff;
+
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import dev.onyxstudios.cca.api.v3.component.ComponentV3;
+import net.minecraft.entity.effect.StatusEffect;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.nbt.NbtOps;
+import net.minecraft.util.registry.Registry;
+
+import java.util.*;
+
+/**
+ * Component for chews.
+ */
+public class ChewComponent implements ComponentV3 {
+    /**
+     * Codec for encoding/decoding this component.
+     */
+    public static final Codec<ChewComponent> CODEC = RecordCodecBuilder.create(
+        inst -> inst.group(
+            Codec.unboundedMap(
+                Registry.STATUS_EFFECT.getCodec(), Codec.INT
+            ).fieldOf("effects").forGetter(i -> i.effects),
+            Codec.list(
+                Registry.STATUS_EFFECT.getCodec()
+            ).fieldOf("applied").forGetter(i -> new ArrayList<>(i.applied)),
+            Codec.BOOL.fieldOf("sendUpdate").forGetter(i -> i.sendUpdate)
+        ).apply(inst, ChewComponent::new)
+    );
+    /**
+     * The effects we're currently consuming.
+     */
+    public Map<StatusEffect, Integer> effects;
+    /**
+     * The effects which have been applied and should be skipped.
+     */
+    public Set<StatusEffect> applied;
+    /**
+     * Whether to send updated effects to the client.
+     */
+    public boolean sendUpdate;
+
+    /**
+     * Creates a new ChewInfo with no effects.
+     */
+    public ChewComponent() {
+        this.effects = new HashMap<>();
+        this.applied = new HashSet<>();
+    }
+
+    /**
+     * Creates a new ChewInfo with the specified effects.
+     *
+     * @param effects The bound effects.
+     * @param applied The applied effects.
+     */
+    public ChewComponent(
+        Map<StatusEffect, Integer> effects,
+        List<StatusEffect> applied,
+        boolean sendUpdate
+    ) {
+        if (effects instanceof HashMap) {
+            this.effects = effects;
+        } else {
+            // ideally we'd just make the HashMap directly but DFU doesn't let
+            // you set the Map class.
+            // actually ideally we'd use IdentityHashMap but w/e.
+            this.effects = new HashMap<>(effects);
+        }
+        this.applied = new HashSet<>(applied);
+        this.sendUpdate = sendUpdate;
+    }
+
+    @Override
+    public void readFromNbt(NbtCompound tag) {
+        var info = ChewComponent.CODEC.parse(
+            NbtOps.INSTANCE,
+            tag
+        ).result().orElseGet(ChewComponent::new);
+
+        this.effects = info.effects;
+        this.applied = info.applied;
+    }
+
+    @Override
+    public void writeToNbt(NbtCompound tag) {
+        tag.copyFrom((NbtCompound) ChewComponent.CODEC.encodeStart(
+            NbtOps.INSTANCE,
+            this
+        ).result().orElseThrow());
+    }
+}
diff --git a/src/main/java/ganarchy/chewstuff/ChewComponents.java b/src/main/java/ganarchy/chewstuff/ChewComponents.java
new file mode 100644
index 0000000..427d86e
--- /dev/null
+++ b/src/main/java/ganarchy/chewstuff/ChewComponents.java
@@ -0,0 +1,28 @@
+package ganarchy.chewstuff;
+
+import dev.onyxstudios.cca.api.v3.component.ComponentKey;
+import dev.onyxstudios.cca.api.v3.component.ComponentRegistry;
+import dev.onyxstudios.cca.api.v3.entity.EntityComponentFactoryRegistry;
+import dev.onyxstudios.cca.api.v3.entity.EntityComponentInitializer;
+import dev.onyxstudios.cca.api.v3.entity.RespawnCopyStrategy;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.util.Identifier;
+
+public class ChewComponents implements EntityComponentInitializer {
+    public static final ComponentKey<ChewComponent> CHEW =
+        ComponentRegistry.getOrCreate(
+            new Identifier("chewstuff", "chew"), ChewComponent.class
+        );
+
+    @Override
+    public void registerEntityComponentFactories(
+        EntityComponentFactoryRegistry registry
+    ) {
+        registry.registerFor(
+            LivingEntity.class, CHEW, entity -> new ChewComponent()
+        );
+        registry.registerForPlayers(
+            CHEW, entity -> new ChewComponent(), RespawnCopyStrategy.ALWAYS_COPY
+        );
+    }
+}
diff --git a/src/main/java/ganarchy/chewstuff/ChewStuff.java b/src/main/java/ganarchy/chewstuff/ChewStuff.java
new file mode 100644
index 0000000..7bea0ec
--- /dev/null
+++ b/src/main/java/ganarchy/chewstuff/ChewStuff.java
@@ -0,0 +1,90 @@
+package ganarchy.chewstuff;
+
+import net.fabricmc.api.ModInitializer;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemGroup;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The mod's entry point.
+ */
+public class ChewStuff implements ModInitializer {
+    /**
+     * The ChewStuff Logger.
+     */
+    public static final Logger LOGGER = LoggerFactory.getLogger("chewstuff");
+
+    /**
+     * Silicone. Not to be confused with silicon. Crafting material for
+     * chews.
+     */
+    public static final Item SILICONE = new Item(
+        new Item.Settings().group(ItemGroup.MISC)
+    );
+    /**
+     * 1 day, in ticks.
+     */
+    public static final int DAY_MULTIPLIER = 24 * 60 * 60 * 20;
+    /**
+     * Soft chew. Lasts 5 days, 1 effect slot.
+     */
+    public static final Item SOFT_CHEW = new ChewableItem(
+        1,
+        new Item.Settings().maxDamage(5 * DAY_MULTIPLIER).group(ItemGroup.TOOLS)
+    );
+    /**
+     * Medium chew. Lasts 6 days, 2 effect slots.
+     */
+    public static final Item MEDIUM_CHEW = new ChewableItem(
+        2,
+        new Item.Settings().maxDamage(6 * DAY_MULTIPLIER).group(ItemGroup.TOOLS)
+    );
+    /**
+     * Hard chew. Lasts 7 days, 3 effect slots.
+     */
+    public static final Item HARD_CHEW = new ChewableItem(
+        3,
+        new Item.Settings().maxDamage(7 * DAY_MULTIPLIER).group(ItemGroup.TOOLS)
+    );
+    /**
+     * The stat for chew effects.
+     */
+    public static final Identifier CHEW_EFFECTS = new Identifier(
+        "chewstuff", "effects"
+    );
+
+    @Override
+    public void onInitialize() {
+        // This code runs as soon as Minecraft is in a mod-load-ready state.
+        // However, some things (like resources) may still be uninitialized.
+        // Proceed with mild caution.
+
+        LOGGER.info("This software is made with love by a queer trans person.");
+
+        Registry.register(
+            Registry.ITEM,
+            new Identifier("chewstuff", "silicone"),
+            SILICONE
+        );
+        Registry.register(
+            Registry.ITEM,
+            new Identifier("chewstuff", "soft_chew"),
+            SOFT_CHEW
+        );
+        Registry.register(
+            Registry.ITEM,
+            new Identifier("chewstuff", "medium_chew"),
+            MEDIUM_CHEW
+        );
+        Registry.register(
+            Registry.ITEM,
+            new Identifier("chewstuff", "hard_chew"),
+            HARD_CHEW
+        );
+//        Registry.register(Registry.CUSTOM_STAT, CHEW_EFFECTS, CHEW_EFFECTS);
+//        Stats.CUSTOM.getOrCreateStat(CHEW_EFFECTS, StatFormatter.DEFAULT);
+    }
+}
diff --git a/src/main/java/ganarchy/chewstuff/ChewableItem.java b/src/main/java/ganarchy/chewstuff/ChewableItem.java
new file mode 100644
index 0000000..e3c8f60
--- /dev/null
+++ b/src/main/java/ganarchy/chewstuff/ChewableItem.java
@@ -0,0 +1,195 @@
+package ganarchy.chewstuff;
+
+import dev.emi.trinkets.api.SlotReference;
+import dev.emi.trinkets.api.TrinketItem;
+import dev.emi.trinkets.api.TrinketsApi;
+import net.fabricmc.loader.api.FabricLoader;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.entity.effect.StatusEffect;
+import net.minecraft.entity.effect.StatusEffectInstance;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Wearable;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.network.packet.s2c.play.EntityStatusEffectS2CPacket;
+import net.minecraft.server.network.ServerPlayerEntity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * An item that can be chewed.
+ */
+public class ChewableItem extends TrinketItem implements Wearable {
+    /**
+     * The cooldown between activations, in ticks.
+     */
+    private static final int COOLDOWN = 5 * 20;
+    /**
+     * Number of effects to bind to.
+     */
+    private final int effects;
+
+    /**
+     * @param effects  Number of effects to bind to.
+     * @param settings The item settings.
+     */
+    public ChewableItem(int effects, Settings settings) {
+        super(settings);
+        this.effects = effects;
+    }
+
+    @Override
+    public void onEquip(ItemStack stack, SlotReference slot, LivingEntity entity) {
+        if (entity.world.isClient) {
+            return;
+        }
+        var maybeInfo = ChewComponents.CHEW.maybeGet(entity);
+        maybeInfo.ifPresent(info -> {
+            info.sendUpdate = true;
+        });
+    }
+
+    @Override
+    public void onUnequip(
+        ItemStack stack, SlotReference slot, LivingEntity entity
+    ) {
+        if (entity.world.isClient) {
+            return;
+        }
+        var maybeInfo = ChewComponents.CHEW.maybeGet(entity);
+        maybeInfo.ifPresent(info -> {
+            if (!info.effects.isEmpty()) {
+                resetEffects(entity, info);
+            }
+        });
+    }
+
+    @Override
+    public void tick(ItemStack stack, SlotReference slot, LivingEntity entity) {
+        if (entity.world.isClient) {
+            return;
+        }
+        if (entity instanceof PlayerEntity player) {
+            if (player.getItemCooldownManager().isCoolingDown(this)) {
+                return;
+            }
+        }
+        var maybeInfo = ChewComponents.CHEW.maybeGet(entity);
+        maybeInfo.ifPresent(info -> {
+            if (info.effects.isEmpty()) {
+                var effects = entity.getStatusEffects();
+                if (effects.size() >= this.effects) {
+                    effects = new ArrayList<>(effects);
+                    Collections.shuffle((List<?>) effects);
+                    info.effects = effects.stream().filter(
+                        effect -> !effect.isAmbient()
+                    ).limit(this.effects).collect(
+                        Collectors.toMap(
+                            StatusEffectInstance::getEffectType,
+                            StatusEffectInstance::getDuration,
+                            (i1, i2) -> {
+                                // don't crash the game in prod
+                                if (
+                                    FabricLoader.getInstance()
+                                        .isDevelopmentEnvironment()
+                                ) {
+                                    throw new IllegalStateException();
+                                } else {
+                                    return Integer.min(i1, i2);
+                                }
+                            },
+                            HashMap::new
+                        )
+                    );
+                    info.sendUpdate = true;
+                } else {
+                    return;
+                }
+            }
+            if (info.effects.size() != this.effects) {
+                return;
+            }
+            if (info.sendUpdate) {
+                sendEffectUpdate(entity, info);
+            }
+            var effects = info.effects.keySet();
+            for (StatusEffect effect : effects) {
+                if (info.effects.get(effect) <= 0) {
+                    continue;
+                }
+                var effectInstance = entity.getStatusEffect(effect);
+                if (effectInstance == null || effectInstance.isAmbient()) {
+                    info.effects.put(effect, 0);
+                    continue;
+                }
+                // don't wanna deal with LivingEntity internals.
+                if (effectInstance.getDuration() == 1) {
+                    continue;
+                }
+                if (!effectInstance.update(entity, () -> {})) {
+                    throw new IllegalStateException();
+                }
+            }
+            if (info.effects.values().stream().allMatch(i -> i == 0)) {
+                resetEffects(entity, info);
+            }
+            stack.damage(1, entity, livingEntity -> {
+                TrinketsApi.onTrinketBroken(stack, slot, livingEntity);
+            });
+        });
+    }
+
+    private void sendEffectUpdate(LivingEntity entity, ChewComponent info) {
+        if (entity instanceof ServerPlayerEntity player) {
+            for (StatusEffect effect : info.effects.keySet()) {
+                var instance = player.getStatusEffect(effect);
+                if (instance == null) {
+                    continue;
+                }
+                NbtCompound nbt = new NbtCompound();
+                instance.writeNbt(nbt);
+                nbt.putInt("Duration", instance.getDuration() / 2);
+                var toSend = StatusEffectInstance.fromNbt(nbt);
+                if (toSend != null) {
+                    player.networkHandler.sendPacket(
+                        new EntityStatusEffectS2CPacket(
+                            player.getId(),
+                            toSend
+                        )
+                    );
+                }
+            }
+        }
+    }
+
+    /**
+     * Stops tracking effects, enables the cooldown, and updates the player,
+     * as needed.
+     *
+     * @param entity The entity.
+     * @param info   The tracked effects.
+     */
+    private void resetEffects(LivingEntity entity, ChewComponent info) {
+        if (entity instanceof ServerPlayerEntity player) {
+            for (StatusEffect effect : info.effects.keySet()) {
+                var instance = player.getStatusEffect(effect);
+                if (instance == null) {
+                    continue;
+                }
+                player.networkHandler.sendPacket(
+                    new EntityStatusEffectS2CPacket(
+                        player.getId(),
+                        instance
+                    )
+                );
+            }
+            player.getItemCooldownManager().set(this, COOLDOWN);
+        }
+        info.effects.clear();
+        info.applied.clear();
+    }
+}
diff --git a/src/main/java/ganarchy/chewstuff/mixin/DesyncFix.java b/src/main/java/ganarchy/chewstuff/mixin/DesyncFix.java
new file mode 100644
index 0000000..7a62a41
--- /dev/null
+++ b/src/main/java/ganarchy/chewstuff/mixin/DesyncFix.java
@@ -0,0 +1,30 @@
+package ganarchy.chewstuff.mixin;
+
+import ganarchy.chewstuff.ChewComponents;
+import net.minecraft.server.network.ServerPlayerEntity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+/**
+ * The mixin for server player entities.
+ */
+@Mixin(ServerPlayerEntity.class)
+public class DesyncFix {
+    /**
+     * Checks and corrects for desyncs.
+     */
+    @Inject(
+        at = @At("HEAD"),
+        method =
+            "onStatusEffectUpgraded(Lnet/minecraft/entity/effect/StatusEffectInstance;ZLnet/minecraft/entity/Entity;)V"
+    )
+    private void chewstuff_onDesync(CallbackInfo cbinfo) {
+        var self = (ServerPlayerEntity) (Object) this;
+        var maybeInfo = ChewComponents.CHEW.maybeGet(self);
+        maybeInfo.ifPresent(info -> {
+            info.sendUpdate = true;
+        });
+    }
+}
diff --git a/src/main/java/ganarchy/chewstuff/mixin/PotionMix.java b/src/main/java/ganarchy/chewstuff/mixin/PotionMix.java
new file mode 100644
index 0000000..5ed97ab
--- /dev/null
+++ b/src/main/java/ganarchy/chewstuff/mixin/PotionMix.java
@@ -0,0 +1,67 @@
+package ganarchy.chewstuff.mixin;
+
+import ganarchy.chewstuff.ChewComponents;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.entity.effect.StatusEffectInstance;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+/**
+ * The mixin for status effect instances.
+ */
+@Mixin(StatusEffectInstance.class)
+public class PotionMix {
+    /**
+     * Checks the apply effect and cancels it when we're force-ticking it.
+     */
+    @Inject(
+        at = @At("HEAD"),
+        method = "applyUpdateEffect(Lnet/minecraft/entity/LivingEntity;)V",
+        cancellable = true
+    )
+    private void chewstuff_checkApplyEffect(
+        LivingEntity entity, CallbackInfo cbinfo
+    ) {
+        StatusEffectInstance self = (StatusEffectInstance) (Object) this;
+        var maybeInfo = ChewComponents.CHEW.maybeGet(entity);
+        maybeInfo.ifPresent(info -> {
+            var effect = self.getEffectType();
+            if (info.effects.getOrDefault(effect, 0) <= 0) {
+                return;
+            }
+            if (info.applied.contains(effect)) {
+                info.applied.remove(effect);
+                cbinfo.cancel();
+            } else {
+                info.applied.add(effect);
+            }
+        });
+    }
+
+    /**
+     * Checks for effect ticks.
+     */
+    @Inject(
+        at = @At("HEAD"),
+        method =
+            "update(Lnet/minecraft/entity/LivingEntity;Ljava/lang/Runnable;)Z"
+    )
+    private void chewstuff_checkUpdate(
+        LivingEntity entity,
+        Runnable runnable,
+        CallbackInfoReturnable<Boolean> cbinfo
+    ) {
+        StatusEffectInstance self = (StatusEffectInstance) (Object) this;
+        var maybeInfo = ChewComponents.CHEW.maybeGet(entity);
+        maybeInfo.ifPresent(info -> {
+            var effect = self.getEffectType();
+            if (info.effects.getOrDefault(effect, 0) <= 0) {
+                return;
+            }
+            info.effects.computeIfPresent(effect, (k, v) -> v - 1);
+        });
+    }
+}
diff --git a/src/main/java/net/fabricmc/example/ExampleMod.java b/src/main/java/net/fabricmc/example/ExampleMod.java
deleted file mode 100644
index a964189..0000000
--- a/src/main/java/net/fabricmc/example/ExampleMod.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package net.fabricmc.example;
-
-import net.fabricmc.api.ModInitializer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class ExampleMod implements ModInitializer {
-	// This logger is used to write text to the console and the log file.
-	// It is considered best practice to use your mod id as the logger's name.
-	// That way, it's clear which mod wrote info, warnings, and errors.
-	public static final Logger LOGGER = LoggerFactory.getLogger("modid");
-
-	@Override
-	public void onInitialize() {
-		// This code runs as soon as Minecraft is in a mod-load-ready state.
-		// However, some things (like resources) may still be uninitialized.
-		// Proceed with mild caution.
-
-		LOGGER.info("Hello Fabric world!");
-	}
-}
diff --git a/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java b/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java
deleted file mode 100644
index 356cb38..0000000
--- a/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package net.fabricmc.example.mixin;
-
-import net.fabricmc.example.ExampleMod;
-import net.minecraft.client.gui.screen.TitleScreen;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-@Mixin(TitleScreen.class)
-public class ExampleMixin {
-	@Inject(at = @At("HEAD"), method = "init()V")
-	private void init(CallbackInfo info) {
-		ExampleMod.LOGGER.info("This line is printed by an example mod mixin!");
-	}
-}
diff --git a/src/main/resources/assets/chewstuff/icon.png b/src/main/resources/assets/chewstuff/icon.png
new file mode 100644
index 0000000..b9d942a
--- /dev/null
+++ b/src/main/resources/assets/chewstuff/icon.png
Binary files differdiff --git a/src/main/resources/assets/chewstuff/lang/en_us.json b/src/main/resources/assets/chewstuff/lang/en_us.json
new file mode 100644
index 0000000..47af750
--- /dev/null
+++ b/src/main/resources/assets/chewstuff/lang/en_us.json
@@ -0,0 +1,6 @@
+{
+  "item.chewstuff.silicone": "Silicone",
+  "item.chewstuff.soft_chew": "Soft Chew Necklace",
+  "item.chewstuff.medium_chew": "Medium Chew Necklace",
+  "item.chewstuff.hard_chew": "Hard Chew Necklace"
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/chewstuff/models/item/hard_chew.json b/src/main/resources/assets/chewstuff/models/item/hard_chew.json
new file mode 100644
index 0000000..0191e60
--- /dev/null
+++ b/src/main/resources/assets/chewstuff/models/item/hard_chew.json
@@ -0,0 +1,6 @@
+{
+  "parent": "item/generated",
+  "textures": {
+    "layer0": "chewstuff:item/hard_chew"
+  }
+}
diff --git a/src/main/resources/assets/chewstuff/models/item/medium_chew.json b/src/main/resources/assets/chewstuff/models/item/medium_chew.json
new file mode 100644
index 0000000..596291a
--- /dev/null
+++ b/src/main/resources/assets/chewstuff/models/item/medium_chew.json
@@ -0,0 +1,6 @@
+{
+  "parent": "item/generated",
+  "textures": {
+    "layer0": "chewstuff:item/medium_chew"
+  }
+}
diff --git a/src/main/resources/assets/chewstuff/models/item/silicone.json b/src/main/resources/assets/chewstuff/models/item/silicone.json
new file mode 100644
index 0000000..36fcd35
--- /dev/null
+++ b/src/main/resources/assets/chewstuff/models/item/silicone.json
@@ -0,0 +1,6 @@
+{
+  "parent": "item/generated",
+  "textures": {
+    "layer0": "chewstuff:item/silicone"
+  }
+}
diff --git a/src/main/resources/assets/chewstuff/models/item/soft_chew.json b/src/main/resources/assets/chewstuff/models/item/soft_chew.json
new file mode 100644
index 0000000..3923e9e
--- /dev/null
+++ b/src/main/resources/assets/chewstuff/models/item/soft_chew.json
@@ -0,0 +1,6 @@
+{
+  "parent": "item/generated",
+  "textures": {
+    "layer0": "chewstuff:item/soft_chew"
+  }
+}
diff --git a/src/main/resources/assets/chewstuff/textures/item/hard_chew.png b/src/main/resources/assets/chewstuff/textures/item/hard_chew.png
new file mode 100644
index 0000000..be5712c
--- /dev/null
+++ b/src/main/resources/assets/chewstuff/textures/item/hard_chew.png
Binary files differdiff --git a/src/main/resources/assets/chewstuff/textures/item/medium_chew.png b/src/main/resources/assets/chewstuff/textures/item/medium_chew.png
new file mode 100644
index 0000000..871ad3c
--- /dev/null
+++ b/src/main/resources/assets/chewstuff/textures/item/medium_chew.png
Binary files differdiff --git a/src/main/resources/assets/chewstuff/textures/item/silicone.png b/src/main/resources/assets/chewstuff/textures/item/silicone.png
new file mode 100644
index 0000000..aad04bb
--- /dev/null
+++ b/src/main/resources/assets/chewstuff/textures/item/silicone.png
Binary files differdiff --git a/src/main/resources/assets/chewstuff/textures/item/soft_chew.png b/src/main/resources/assets/chewstuff/textures/item/soft_chew.png
new file mode 100644
index 0000000..2cc2f29
--- /dev/null
+++ b/src/main/resources/assets/chewstuff/textures/item/soft_chew.png
Binary files differdiff --git a/src/main/resources/assets/modid/icon.png b/src/main/resources/assets/modid/icon.png
deleted file mode 100644
index 047b91f..0000000
--- a/src/main/resources/assets/modid/icon.png
+++ /dev/null
Binary files differdiff --git a/src/main/resources/modid.mixins.json b/src/main/resources/chewstuff.mixins.json
index 7c42cb4..ef0b3fa 100644
--- a/src/main/resources/modid.mixins.json
+++ b/src/main/resources/chewstuff.mixins.json
@@ -1,12 +1,13 @@
 {
   "required": true,
   "minVersion": "0.8",
-  "package": "net.fabricmc.example.mixin",
+  "package": "ganarchy.chewstuff.mixin",
   "compatibilityLevel": "JAVA_17",
   "mixins": [
+    "PotionMix",
+    "DesyncFix"
   ],
   "client": [
-    "ExampleMixin"
   ],
   "injectors": {
     "defaultRequire": 1
diff --git a/src/main/resources/data/chewstuff/recipes/hard_chew.json b/src/main/resources/data/chewstuff/recipes/hard_chew.json
new file mode 100644
index 0000000..8cea939
--- /dev/null
+++ b/src/main/resources/data/chewstuff/recipes/hard_chew.json
@@ -0,0 +1,20 @@
+{
+    "type": "minecraft:crafting_shapeless",
+    "ingredients": [
+        {
+            "item": "chewstuff:silicone"
+        },
+        {
+            "item": "chewstuff:silicone"
+        },
+        {
+            "item": "chewstuff:silicone"
+        },
+        {
+            "item": "minecraft:string"
+        }
+    ],
+    "result": {
+        "item": "chewstuff:hard_chew"
+    }
+}
diff --git a/src/main/resources/data/chewstuff/recipes/medium_chew.json b/src/main/resources/data/chewstuff/recipes/medium_chew.json
new file mode 100644
index 0000000..a4b9639
--- /dev/null
+++ b/src/main/resources/data/chewstuff/recipes/medium_chew.json
@@ -0,0 +1,17 @@
+{
+    "type": "minecraft:crafting_shapeless",
+    "ingredients": [
+        {
+            "item": "chewstuff:silicone"
+        },
+        {
+            "item": "chewstuff:silicone"
+        },
+        {
+            "item": "minecraft:string"
+        }
+    ],
+    "result": {
+        "item": "chewstuff:medium_chew"
+    }
+}
diff --git a/src/main/resources/data/chewstuff/recipes/silicone.json b/src/main/resources/data/chewstuff/recipes/silicone.json
new file mode 100644
index 0000000..137e5fd
--- /dev/null
+++ b/src/main/resources/data/chewstuff/recipes/silicone.json
@@ -0,0 +1,14 @@
+{
+    "type": "minecraft:crafting_shapeless",
+    "ingredients": [
+        {
+            "tag": "minecraft:coals"
+        },
+        {
+            "tag": "minecraft:sand"
+        }
+    ],
+    "result": {
+        "item": "chewstuff:silicone"
+    }
+}
diff --git a/src/main/resources/data/chewstuff/recipes/soft_chew.json b/src/main/resources/data/chewstuff/recipes/soft_chew.json
new file mode 100644
index 0000000..1e15630
--- /dev/null
+++ b/src/main/resources/data/chewstuff/recipes/soft_chew.json
@@ -0,0 +1,14 @@
+{
+    "type": "minecraft:crafting_shapeless",
+    "ingredients": [
+        {
+            "item": "chewstuff:silicone"
+        },
+        {
+            "item": "minecraft:string"
+        }
+    ],
+    "result": {
+        "item": "chewstuff:soft_chew"
+    }
+}
diff --git a/src/main/resources/data/trinkets/entities/chewstuff.json b/src/main/resources/data/trinkets/entities/chewstuff.json
new file mode 100644
index 0000000..fd5f0d3
--- /dev/null
+++ b/src/main/resources/data/trinkets/entities/chewstuff.json
@@ -0,0 +1,8 @@
+{
+    "entities": [
+        "player"
+    ],
+    "slots": [
+        "chest/necklace"
+    ]
+}
diff --git a/src/main/resources/data/trinkets/tags/items/chest/necklace.json b/src/main/resources/data/trinkets/tags/items/chest/necklace.json
new file mode 100644
index 0000000..4d31011
--- /dev/null
+++ b/src/main/resources/data/trinkets/tags/items/chest/necklace.json
@@ -0,0 +1,8 @@
+{
+    "replace": false,
+    "values": [
+        "chewstuff:soft_chew",
+        "chewstuff:medium_chew",
+        "chewstuff:hard_chew"
+    ]
+}
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 730d843..9f07af9 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -1,38 +1,47 @@
 {
   "schemaVersion": 1,
-  "id": "modid",
+  "id": "chewstuff",
   "version": "${version}",
 
-  "name": "Example Mod",
-  "description": "This is an example description! Tell everyone what your mod is about!",
+  "name": "Chew Necklaces",
+  "description": "There once was an Autism Acceptance Mod but it hasn't been updated in forever. This is more or less a complete rewrite, from scratch, of that.",
   "authors": [
-    "Me!"
+    "SoniEx2"
   ],
+  "contributors": {
+    "torako"
+  },
   "contact": {
-    "homepage": "https://fabricmc.net/",
-    "sources": "https://github.com/FabricMC/fabric-example-mod"
+    "sources": "https://soniex2.autistic.space/git-repos/chewstuff.git"
   },
 
-  "license": "CC0-1.0",
-  "icon": "assets/modid/icon.png",
+  "license": "0BSD",
+  "icon": "assets/chewstuff/icon.png",
 
   "environment": "*",
   "entrypoints": {
     "main": [
-      "net.fabricmc.example.ExampleMod"
+      "ganarchy.chewstuff.ChewStuff"
+    ],
+    "cardinal-components": [
+      "ganarchy.chewstuff.ChewComponents"
     ]
   },
   "mixins": [
-    "modid.mixins.json"
+    "chewstuff.mixins.json"
   ],
 
   "depends": {
+    "trinkets": "~3.4",
     "fabricloader": ">=0.14.6",
     "fabric": "*",
     "minecraft": "~1.19",
     "java": ">=17"
   },
-  "suggests": {
-    "another-mod": "*"
+
+  "custom": {
+    "cardinal-components": [
+      "chewstuff:chew"
+    ]
   }
 }