feat: implement ItemGroupService for creative inventory management
This commit is contained in:
14 files changed
+148
-3
No files matched your search
+6
-1
@@ -1 +1,6 @@
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||
#Wed Jul 22 12:25:27 GMT+08:00 2026
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -14,6 +14,7 @@ import io.ib67.sfcraft.module.room.CreativeRoomModule;
|
||||
import io.ib67.sfcraft.module.supervisor.WebModule;
|
||||
import io.ib67.sfcraft.module.supervisor.web.SchematicUploader;
|
||||
import io.ib67.sfcraft.module.game.GameExtensionModule;
|
||||
import io.ib67.sfcraft.module.game.ItemGroupModule;
|
||||
import io.ib67.sfcraft.module.game.beheading.BeheadingModule;
|
||||
import io.ib67.sfcraft.module.game.bomb.BombModule;
|
||||
import io.ib67.sfcraft.module.game.cauldron.CauldronModule;
|
||||
@@ -24,7 +25,9 @@ import io.ib67.sfcraft.module.game.mount.MountModule;
|
||||
import io.ib67.sfcraft.module.game.token.TokenModule;
|
||||
import io.ib67.sfcraft.module.game.totem.TotemModule;
|
||||
import io.ib67.sfcraft.registry.CauldronRecipeRegistry;
|
||||
import io.ib67.sfcraft.registry.ItemGroupService;
|
||||
import io.ib67.sfcraft.registry.RoomRegistry;
|
||||
import io.ib67.sfcraft.registry.itemgroup.PolymerItemGroupService;
|
||||
import io.ib67.sfcraft.registry.cauldron.SimpleCauldronRecipeRegistry;
|
||||
import io.ib67.sfcraft.registry.chat.SimpleMessageDecorator;
|
||||
import io.ib67.sfcraft.registry.event.SFRandomEventRegistry;
|
||||
@@ -61,6 +64,7 @@ public class SFCraftInitializer extends GuiceModInitializer {
|
||||
binder().bind(RoomTeleporter.class).in(Singleton.class);
|
||||
binder().bind(RoomRegistry.class).to(SimpleRoomRegistry.class).in(Singleton.class);
|
||||
binder().bind(CauldronRecipeRegistry.class).to(SimpleCauldronRecipeRegistry.class).in(Singleton.class);
|
||||
binder().bind(ItemGroupService.class).to(PolymerItemGroupService.class).in(Singleton.class);
|
||||
}
|
||||
|
||||
private void registerFeatures() {
|
||||
@@ -99,6 +103,8 @@ public class SFCraftInitializer extends GuiceModInitializer {
|
||||
registerFeature(CommanderModule.class);
|
||||
registerFeature(MountModule.class);
|
||||
registerFeature(TotemModule.class);
|
||||
// ItemGroupModule 必须最后注册:它冻结前面各模块被动贡献到 ItemGroupService 的物品,构建创意物品栏
|
||||
registerFeature(ItemGroupModule.class);
|
||||
}
|
||||
|
||||
private void registerWebModules() {
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.spongepowered.asm.mixin.injection.*;
|
||||
|
||||
@Mixin(ServerLevel.class)
|
||||
public abstract class ServerWorldMixin {
|
||||
|
||||
@ModifyArg(method = "tickTime", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/ServerLevelData;setGameTime(J)V"))
|
||||
public long setTimeOfDay(long timeOfDay) {
|
||||
var _this = ((ServerLevel) (Object) this);
|
||||
@@ -18,7 +17,7 @@ public abstract class ServerWorldMixin {
|
||||
var dt = _this.dimensionTypeRegistration();
|
||||
var clock = dt.value().defaultClock();
|
||||
if(clock.isEmpty()) return timeOfDay;
|
||||
_this.clockManager().addTicks(clock.get(), 10);
|
||||
_this.clockManager().addTicks(clock.get(), 20);
|
||||
MixinHelper.spedUp = true;
|
||||
return timeOfDay + 10;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ib67.sfcraft.module.game;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.module.game.cauldron.CauldronModule;
|
||||
import io.ib67.sfcraft.registry.ItemGroupService;
|
||||
|
||||
/**
|
||||
* Finalizes the mod's creative inventory tab. Must be the LAST feature registered in
|
||||
* {@code registerGameFeatures()} so that, by the time its {@link #onInitialize()} runs, every
|
||||
* other feature module has already contributed its items to {@link ItemGroupService}. This module
|
||||
* only freezes the passively-collected set — it never registers items itself.
|
||||
* <p>
|
||||
* The tab icon is the amethyst cauldron, pulled from {@link CauldronModule} (registered earlier,
|
||||
* so its item is available here) to keep the icon type-safe rather than looked up by string id.
|
||||
*/
|
||||
public class ItemGroupModule extends ServerModule {
|
||||
@Inject
|
||||
private ItemGroupService itemGroups;
|
||||
@Inject
|
||||
private CauldronModule cauldron;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
itemGroups.freeze(cauldron.getCauldronItem());
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.google.inject.Inject;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.config.GameConfigService;
|
||||
import io.ib67.sfcraft.module.game.RegistryHelper;
|
||||
import io.ib67.sfcraft.registry.ItemGroupService;
|
||||
import lombok.Getter;
|
||||
import net.fabricmc.fabric.api.loot.v3.LootTableEvents;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
@@ -28,6 +29,8 @@ import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator;
|
||||
public class BombModule extends ServerModule {
|
||||
@Inject
|
||||
private GameConfigService config;
|
||||
@Inject
|
||||
private ItemGroupService itemGroups;
|
||||
|
||||
private EntityType<BombEntity> bombEntityType;
|
||||
private Item bomb;
|
||||
@@ -48,6 +51,9 @@ public class BombModule extends ServerModule {
|
||||
bomb = registerBomb("bomb", BombEntity.BombType.NORMAL);
|
||||
obsidianBomb = registerBomb("obsidian_bomb", BombEntity.BombType.OBSIDIAN);
|
||||
blazeBomb = registerBomb("blaze_bomb", BombEntity.BombType.BLAZE);
|
||||
itemGroups.add(bomb);
|
||||
itemGroups.add(obsidianBomb);
|
||||
itemGroups.add(blazeBomb);
|
||||
registerLootModifiers();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.ib67.sfcraft.config.GameConfigService;
|
||||
import io.ib67.sfcraft.module.game.RegistryHelper;
|
||||
import io.ib67.sfcraft.module.game.item.SimplePolymerItem;
|
||||
import io.ib67.sfcraft.registry.CauldronRecipeRegistry;
|
||||
import io.ib67.sfcraft.registry.ItemGroupService;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -27,6 +28,8 @@ public class CauldronModule extends ServerModule {
|
||||
private GameConfigService config;
|
||||
@Inject
|
||||
private CauldronRecipeRegistry recipes;
|
||||
@Inject
|
||||
private ItemGroupService itemGroups;
|
||||
|
||||
private AmethystCauldronBlock cauldronBlock;
|
||||
private BlockEntityType<AmethystCauldronBlockEntity> cauldronBlockEntity;
|
||||
@@ -67,5 +70,7 @@ public class CauldronModule extends ServerModule {
|
||||
.component(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true)
|
||||
.component(DataComponents.ITEM_NAME, Component.translatable("item.sfcraft.amethyst_cauldron"))
|
||||
);
|
||||
itemGroups.add(cauldronItem);
|
||||
itemGroups.add(cauldronBlankItem);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import io.ib67.sfcraft.callback.SFConfigReload;
|
||||
import io.ib67.sfcraft.config.GameConfigService;
|
||||
import io.ib67.sfcraft.module.game.RegistryHelper;
|
||||
import io.ib67.sfcraft.registry.CauldronRecipeRegistry;
|
||||
import io.ib67.sfcraft.registry.ItemGroupService;
|
||||
import io.ib67.sfcraft.registry.cauldron.CauldronRecipe;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
@@ -31,6 +32,8 @@ public class GravityCrystalModule extends ServerModule {
|
||||
private GameConfigService config;
|
||||
@Inject
|
||||
private CauldronRecipeRegistry cauldronRecipes;
|
||||
@Inject
|
||||
private ItemGroupService itemGroups;
|
||||
|
||||
private GravityCrystalBlock crystalBlock;
|
||||
private BlockEntityType<GravityCrystalBlockEntity> crystalBlockEntity;
|
||||
@@ -63,6 +66,7 @@ public class GravityCrystalModule extends ServerModule {
|
||||
.component(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true)
|
||||
.component(DataComponents.ITEM_NAME, Component.translatable("item.sfcraft.gravity_crystal"))
|
||||
);
|
||||
itemGroups.add(crystalItem);
|
||||
|
||||
registerRecipes();
|
||||
// /sfcraft reload 后重建配方,应用最新的 reactionTicks
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package io.ib67.sfcraft.module.game.lunchbox;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.module.game.RegistryHelper;
|
||||
import io.ib67.sfcraft.registry.ItemGroupService;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -15,6 +17,9 @@ import net.minecraft.world.item.component.Consumables;
|
||||
*/
|
||||
@Getter
|
||||
public class LunchBoxModule extends ServerModule {
|
||||
@Inject
|
||||
private ItemGroupService itemGroups;
|
||||
|
||||
private Item lunchBox;
|
||||
|
||||
@Override
|
||||
@@ -30,5 +35,6 @@ public class LunchBoxModule extends ServerModule {
|
||||
.component(DataComponents.FOOD, new FoodProperties(0, 0.0F, true))
|
||||
.component(DataComponents.CONSUMABLE, Consumables.DEFAULT_FOOD)
|
||||
);
|
||||
itemGroups.add(lunchBox);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import io.ib67.sfcraft.callback.SFConfigReload;
|
||||
import io.ib67.sfcraft.config.GameConfigService;
|
||||
import io.ib67.sfcraft.module.game.RegistryHelper;
|
||||
import io.ib67.sfcraft.registry.CauldronRecipeRegistry;
|
||||
import io.ib67.sfcraft.registry.ItemGroupService;
|
||||
import io.ib67.sfcraft.registry.cauldron.CauldronRecipe;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
@@ -30,6 +31,8 @@ public class TokenModule extends ServerModule {
|
||||
private GameConfigService config;
|
||||
@Inject
|
||||
private CauldronRecipeRegistry cauldronRecipes;
|
||||
@Inject
|
||||
private ItemGroupService itemGroups;
|
||||
|
||||
private Item pearlToken;
|
||||
private ReversePearlTokenItem reversePearlToken;
|
||||
@@ -55,6 +58,8 @@ public class TokenModule extends ServerModule {
|
||||
.durability(config.get().reverseToken.maxUses)
|
||||
.component(DataComponents.ITEM_NAME, Component.translatable("item.sfcraft.reverse_pearl_token"))
|
||||
);
|
||||
itemGroups.add(pearlToken);
|
||||
itemGroups.add(reversePearlToken);
|
||||
registerRecipes();
|
||||
SFConfigReload.EVENT.register(() -> {
|
||||
registeredRecipes.forEach(cauldronRecipes::unregister);
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.ib67.sfcraft.config.GameConfigService;
|
||||
import io.ib67.sfcraft.module.game.RegistryHelper;
|
||||
import io.ib67.sfcraft.module.game.item.SimplePolymerItem;
|
||||
import io.ib67.sfcraft.registry.CauldronRecipeRegistry;
|
||||
import io.ib67.sfcraft.registry.ItemGroupService;
|
||||
import io.ib67.sfcraft.registry.cauldron.CauldronRecipe;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.advancements.triggers.CriteriaTriggers;
|
||||
@@ -40,6 +41,8 @@ public class TotemModule extends ServerModule {
|
||||
private GameConfigService config;
|
||||
@Inject
|
||||
private CauldronRecipeRegistry cauldronRecipes;
|
||||
@Inject
|
||||
private ItemGroupService itemGroups;
|
||||
|
||||
private Item standingFirmTotem;
|
||||
|
||||
@@ -68,6 +71,7 @@ public class TotemModule extends ServerModule {
|
||||
// 效果改由 mixin 完全接管,复用 DeathProtection.TOTEM_OF_UNDYING 常量(见 tryProtect)。
|
||||
.component(DataComponents.ITEM_NAME, Component.translatable("item.sfcraft.standing_firm_totem"))
|
||||
);
|
||||
itemGroups.add(standingFirmTotem);
|
||||
registerRecipes();
|
||||
// /sfcraft reload 后重建配方,应用最新的 reactionTicks
|
||||
SFConfigReload.EVENT.register(() -> {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ib67.sfcraft.registry;
|
||||
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
|
||||
/**
|
||||
* Collects the mod's items into a single server-side creative inventory tab (later synced to
|
||||
* Polymer-compatible clients). Feature modules contribute their items via {@link #add(ItemLike)}
|
||||
* in {@code onInitialize()}; a single finalizer module calls {@link #freeze(ItemLike)} last —
|
||||
* after every other module has registered — to build and register the tab from what was collected.
|
||||
* <p>
|
||||
* Collection is passive: the service only records what modules push to it and does not itself
|
||||
* know about any concrete feature. Adding after {@link #freeze(ItemLike)} is an error.
|
||||
*/
|
||||
public interface ItemGroupService {
|
||||
/** Contribute an item to the creative tab. Must be called before {@link #freeze(ItemLike)}. */
|
||||
void add(ItemLike item);
|
||||
|
||||
/**
|
||||
* Build and register the creative tab from everything collected so far, using {@code icon}
|
||||
* as the tab icon. Must be called exactly once, after all {@link #add(ItemLike)} calls.
|
||||
*/
|
||||
void freeze(ItemLike icon);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package io.ib67.sfcraft.registry.itemgroup;
|
||||
|
||||
import eu.pb4.polymer.core.api.item.PolymerCreativeModeTabUtils;
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import io.ib67.sfcraft.registry.ItemGroupService;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Default {@link ItemGroupService} backed by Polymer's server-side creative tabs
|
||||
* ({@link PolymerCreativeModeTabUtils}). Items are kept in insertion order and de-duplicated;
|
||||
* {@link #freeze(ItemLike)} builds the tab and registers it under {@code sfcraft:general},
|
||||
* titled by the {@code itemGroup.sfcraft.general} translation key (resolved client-side via the
|
||||
* server resource pack).
|
||||
*/
|
||||
public class PolymerItemGroupService implements ItemGroupService {
|
||||
private static final Identifier ID = Identifier.fromNamespaceAndPath(SFCraft.MOD_ID, "general");
|
||||
|
||||
// LinkedHashSet: stable tab order (module registration order) with de-duplication.
|
||||
private final Set<ItemLike> items = new LinkedHashSet<>();
|
||||
private boolean frozen;
|
||||
|
||||
@Override
|
||||
public void add(ItemLike item) {
|
||||
if (frozen) {
|
||||
throw new IllegalStateException("Item group is already frozen; cannot add " + item);
|
||||
}
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void freeze(ItemLike icon) {
|
||||
if (frozen) {
|
||||
throw new IllegalStateException("Item group is already frozen");
|
||||
}
|
||||
frozen = true;
|
||||
var tab = PolymerCreativeModeTabUtils.builder()
|
||||
.title(Component.translatable("itemGroup.sfcraft.general"))
|
||||
.icon(() -> new ItemStack(icon))
|
||||
.displayItems((parameters, output) -> {
|
||||
for (ItemLike item : items) {
|
||||
output.accept(item);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
PolymerCreativeModeTabUtils.registerPolymerCreativeModeTab(ID, tab);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"itemGroup.sfcraft.general": "SFCraft",
|
||||
"block.sfcraft.amethyst_cauldron": "Amethyst Cauldron",
|
||||
"block.sfcraft.gravity_crystal": "Gravity Crystal",
|
||||
"item.sfcraft.amethyst_cauldron_blank": "Amethyst Cauldron Shell",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"itemGroup.sfcraft.general": "SFCraft",
|
||||
"block.sfcraft.amethyst_cauldron": "紫水晶炼药锅",
|
||||
"block.sfcraft.gravity_crystal": "重力水晶",
|
||||
"item.sfcraft.amethyst_cauldron_blank": "紫水晶炼药锅胚体",
|
||||
|
||||
Reference in new issue
Block a user