Initial project setup for SilkSpawners mod

Add SilkSpawners mod source files, configuration, mixins, Gradle build scripts, and metadata for Fabric. Implements spawner drop logic, config loading/saving, and required interfaces/mixins for custom spawner behavior.
This commit is contained in:
2026-01-05 04:33:13 +01:00
commit 1b2c1c862b
18 changed files with 716 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
package com.kasetoatz.silkspawners;
import net.fabricmc.api.ModInitializer;
import static com.kasetoatz.silkspawners.config.Config.load;
public class SilkSpawners implements ModInitializer
{
@Override
public void onInitialize()
{
load();
}
}

View File

@@ -0,0 +1,11 @@
package com.kasetoatz.silkspawners.accessors;
import net.minecraft.entity.EntityType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.World;
public interface IMobSpawnerLogic
{
EntityType<?> silkspawners$getEntityType(World world, Random random, BlockPos pos);
}

View File

@@ -0,0 +1,8 @@
package com.kasetoatz.silkspawners.accessors;
import net.minecraft.block.spawner.TrialSpawnerLogic;
public interface ITrialSpawnerLogic
{
TrialSpawnerLogic.FullConfig silkspawners$getFullConfig();
}

View File

@@ -0,0 +1,75 @@
package com.kasetoatz.silkspawners.config;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.crash.CrashException;
import net.minecraft.util.crash.CrashReport;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Config
{
public static boolean ENABLE_SPAWNER = true;
public static boolean ENABLE_TRIAL_SPAWNER = true;
public static boolean REQUIRE_PICKAXE = true;
public static boolean REQUIRE_SILK_TOUCH = true;
private static final File config = new File(MinecraftClient.getInstance().runDirectory, "config/silkspawners.json");
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public static void load()
{
if (!config.exists())
{
save();
return;
}
try (FileReader reader = new FileReader(config))
{
JsonObject json = gson.fromJson(reader, JsonObject.class);
if (json.has("enable-spawner"))
{
ENABLE_SPAWNER = json.get("enable-spawner").getAsBoolean();
}
if (json.has("enable-trial-spawner"))
{
ENABLE_TRIAL_SPAWNER = json.get("enable-trial-spawner").getAsBoolean();
}
if (json.has("require-pickaxe"))
{
REQUIRE_PICKAXE = json.get("require-pickaxe").getAsBoolean();
}
if (json.has("require-silk-touch"))
{
REQUIRE_SILK_TOUCH = json.get("require-silk-touch").getAsBoolean();
}
save();
}
catch (IOException exc)
{
throw new CrashException(CrashReport.create(exc, "Loading config file."));
}
}
public static void save()
{
JsonObject json = new JsonObject();
json.addProperty("enable-spawner", ENABLE_SPAWNER);
json.addProperty("enable-trial-spawner", ENABLE_TRIAL_SPAWNER);
json.addProperty("require-pickaxe", REQUIRE_PICKAXE);
json.addProperty("require-silk-touch", REQUIRE_SILK_TOUCH);
try (FileWriter writer = new FileWriter(config))
{
gson.toJson(json, writer);
}
catch (IOException exc)
{
throw new CrashException(CrashReport.create(exc, "Saving config file."));
}
}
}

View File

@@ -0,0 +1,77 @@
package com.kasetoatz.silkspawners.mixin;
import com.kasetoatz.silkspawners.accessors.IMobSpawnerLogic;
import com.kasetoatz.silkspawners.accessors.ITrialSpawnerLogic;
import net.minecraft.block.*;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.MobSpawnerBlockEntity;
import net.minecraft.block.entity.Spawner;
import net.minecraft.block.entity.TrialSpawnerBlockEntity;
import net.minecraft.block.spawner.TrialSpawnerLogic;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.NbtComponent;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.enchantment.Enchantments;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.ItemTags;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import org.jspecify.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.List;
import java.util.Objects;
import static com.kasetoatz.silkspawners.config.Config.*;
@Mixin(Block.class)
public class BlockMixin
{
@Redirect(method="dropStacks(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/entity/BlockEntity;Lnet/minecraft/entity/Entity;Lnet/minecraft/item/ItemStack;)V", at=@At(value="INVOKE", target="Lnet/minecraft/block/Block;getDroppedStacks(Lnet/minecraft/block/BlockState;Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/entity/BlockEntity;Lnet/minecraft/entity/Entity;Lnet/minecraft/item/ItemStack;)Ljava/util/List;"))
private static List<ItemStack> getDroppedStacks(BlockState state, ServerWorld world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack stack)
{
System.out.println(stack);
if (!(blockEntity instanceof Spawner))
{
return getDroppedStacks(state, world, pos, blockEntity, entity, stack);
}
if (!ENABLE_SPAWNER && state.getBlock() instanceof SpawnerBlock)
{
return List.of();
}
if (!ENABLE_TRIAL_SPAWNER && state.getBlock() instanceof TrialSpawnerBlock)
{
return List.of();
}
if (REQUIRE_PICKAXE && !stack.isIn(ItemTags.PICKAXES))
{
return List.of();
}
if (REQUIRE_SILK_TOUCH && EnchantmentHelper.getLevel(world.getRegistryManager().getOrThrow(RegistryKeys.ENCHANTMENT).getOrThrow(Enchantments.SILK_TOUCH), stack) < 1)
{
return List.of();
}
ItemStack item = state.getBlock().asItem().getDefaultStack();
NbtComponent component = Objects.requireNonNullElse(item.get(DataComponentTypes.CUSTOM_DATA), NbtComponent.DEFAULT);
if (blockEntity instanceof MobSpawnerBlockEntity spawner)
{
EntityType<?> type = ((IMobSpawnerLogic)spawner.getLogic()).silkspawners$getEntityType(world, world.getRandom(), pos);
if (type != null)
{
component.apply(nbtCompound -> nbtCompound.put("entityType", EntityType.CODEC, type));
}
}
else if (blockEntity instanceof TrialSpawnerBlockEntity trialSpawner)
{
component.apply(nbtCompound -> nbtCompound.put("fullConfig", TrialSpawnerLogic.FullConfig.CODEC.codec(), ((ITrialSpawnerLogic)(Object)trialSpawner.getSpawner()).silkspawners$getFullConfig()));
}
item.set(DataComponentTypes.CUSTOM_DATA, component);
return List.of(item);
}
}

View File

@@ -0,0 +1,25 @@
package com.kasetoatz.silkspawners.mixin;
import com.kasetoatz.silkspawners.accessors.IMobSpawnerLogic;
import net.minecraft.block.spawner.MobSpawnerEntry;
import net.minecraft.block.spawner.MobSpawnerLogic;
import net.minecraft.entity.EntityType;
import net.minecraft.registry.Registries;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.World;
import org.jspecify.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(MobSpawnerLogic.class)
public abstract class MobSpawnerLogicMixin implements IMobSpawnerLogic
{
@Shadow protected abstract MobSpawnerEntry getSpawnEntry(@Nullable World world, net.minecraft.util.math.random.Random random, BlockPos pos);
public EntityType<?> silkspawners$getEntityType(World world, Random random, BlockPos pos)
{
return getSpawnEntry(world, random, pos).getNbt().getString("id").map(id -> Registries.ENTITY_TYPE.get(Identifier.of(id))).orElse(null);
}
}

View File

@@ -0,0 +1,18 @@
package com.kasetoatz.silkspawners.mixin;
import com.kasetoatz.silkspawners.accessors.ITrialSpawnerLogic;
import net.minecraft.block.spawner.TrialSpawnerLogic;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(TrialSpawnerLogic.class)
public class TrialSpawnerLogicMixin implements ITrialSpawnerLogic
{
@Shadow private TrialSpawnerLogic.FullConfig fullConfig;
@Override
public TrialSpawnerLogic.FullConfig silkspawners$getFullConfig()
{
return fullConfig;
}
}

View File

@@ -0,0 +1,25 @@
{
"schemaVersion": 1,
"id": "silkspawners",
"version": "${version}",
"name": "SilkSpawners",
"description": "",
"authors": [],
"contact": {},
"license": "MIT",
"icon": "assets/silkspawners/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"com.kasetoatz.silkspawners.SilkSpawners"
]
},
"mixins": [
"silkspawners.mixins.json"
],
"depends": {
"fabricloader": ">=${loader_version}",
"fabric-api": "*",
"minecraft": "${minecraft_version}"
}
}

View File

@@ -0,0 +1,19 @@
{
"required": true,
"minVersion": "0.8",
"package": "com.kasetoatz.silkspawners.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
"BlockMixin",
"MobSpawnerLogicMixin",
"TrialSpawnerLogicMixin"
],
"client": [
],
"injectors": {
"defaultRequire": 1
},
"overwrites": {
"requireAnnotations": true
}
}