+ * Call the `init` method in your mod's initialization point.
+ *
+ *
On Fabric: `ModInitializer#onInitialize`
+ *
On Forge: `Constructor` of your mod's main class
+ *
On Architectury: In the `init` method of your common main class
+ *
+ *
+ *
+ * @param beforeRegistration A {@link Runnable} that is executed before the registration of the content. Place all your static initialization code here.
+ */
+ public void init(final Runnable beforeRegistration) {
+ // Initialize content
+ beforeRegistration.run();
+ if (entityTypes != null) entityTypes.register();
+ if (blocks != null) blocks.register();
+ if (items != null) items.register();
+ if (blockEntities != null) blockEntities.register();
+ if (attributes != null) attributes.register();
+ if (skills != null) skills.register();
+ }
+
+ /**
+ * Creates a new {@link ItemBuilder} for the given name.
+ */
+ public ItemBuilder item(final String name) {
+ return item(name, Item::new);
+ }
+
+ /**
+ * Creates a new {@link ItemBuilder} for the given name.
+ */
+ public ItemBuilder item(final String name, Function itemFactory) {
+ if (this.items == null) this.items = DeferredRegister.create(this.modId, Registries.ITEM);
+ return new ItemBuilder<>(self(), name, itemFactory);
+ }
+
+ protected BlockItemBuilder blockItem(final String name) {
+ if (this.items == null) this.items = DeferredRegister.create(this.modId, Registries.ITEM);
+ return new BlockItemBuilder<>(self(), name, (blockRegistrySupplier, properties) -> new BlockItem(blockRegistrySupplier.get(), properties));
+ }
+
+ /**
+ * Creates a new {@link BlockBuilder} for the given name.
+ */
+ public BlockBuilder block(final String name) {
+ return block(name, Block::new);
+ }
+
+ /**
+ * Creates a new {@link BlockBuilder} for the given name.
+ */
+ public BlockBuilder block(final String name, Function blockFactory) {
+ if (this.blocks == null) this.blocks = DeferredRegister.create(this.modId, Registries.BLOCK);
+ return new BlockBuilder<>(self(), name, blockFactory);
+ }
+
+ /**
+ * Creates a new {@link EntityTypeBuilder} for the given name.
+ *
+ * Remember to register an Entity Renderer on client side.
+ *
+ * @see dev.architectury.registry.client.level.entity.EntityRendererRegistry
+ */
+ public EntityTypeBuilder entity(final String name, final EntityFactory entityFactory) {
+ if (this.entityTypes == null) this.entityTypes = DeferredRegister.create(this.modId, Registries.ENTITY_TYPE);
+ return new EntityTypeBuilder<>(self(), name, entityFactory);
+ }
+
+ /**
+ * Creates a new {@link EntityTypeBuilder} for the given name.
+ *
+ * Remember to register an Entity Renderer on client side.
+ *
+ * @see dev.architectury.registry.client.level.entity.EntityRendererRegistry
+ */
+ public AttributeBuilder attribute(final String name) {
+ if (this.attributes == null) this.attributes = DeferredRegister.create(this.modId, Registries.ATTRIBUTE);
+ return new AttributeBuilder<>(self(), name);
+ }
+
+ /**
+ * Creates a new {@link BlockEntityBuilder} for the given name.
+ */
+ public BlockEntityBuilder blockEntity(final String name, final BlockEntitySupplier factory) {
+ if (this.blockEntities == null) this.blockEntities = DeferredRegister.create(this.modId, Registries.BLOCK_ENTITY_TYPE);
+ return new BlockEntityBuilder<>(self(), name, factory);
+ }
+
+
+ /**
+ * Creates a new {@link SkillBuilder} for the given name.
+ */
+ public SkillBuilder skill(final String name, final Supplier skillFactory) {
+ if (this.skills == null) this.skills = DeferredRegister.create(this.modId, SkillAPI.getSkillRegistryKey());
+ return new SkillBuilder<>(self(), name, skillFactory);
+ }
+
+
+ /**
+ * Builder class for {@link Item}s.
+ */
+ public static class ItemBuilder, T extends Item> extends ContentBuilder {
+ protected Item.Properties properties;
+ protected final Function itemFactory;
+
+ private ItemBuilder(final R register, final String name, Function itemFactory) {
+ super(register, name);
+ this.properties = new Item.Properties();
+ this.itemFactory = itemFactory;
+ }
+
+ public ItemBuilder withProperties(final Item.Properties properties) {
+ this.properties = properties;
+ return this;
+ }
+
+ public ItemBuilder withProperties(Consumer properties) {
+ properties.accept(this.properties);
+ return this;
+ }
+
+ public ItemBuilder withProperties(Function properties) {
+ this.properties = properties.apply(this.properties);
+ return this;
+ }
+
+ public ItemBuilder withStackSize(final int stackSize) {
+ this.properties.stacksTo(stackSize);
+ return this;
+ }
+
+ @Override
+ public RegistrySupplier end() {
+ return this.register.items.register(this.id, () -> this.itemFactory.apply(this.properties));
+ }
+
+ @Override
+ public Holder endAsHolder() {
+ return this.end().getRegistrar().getHolder(this.id);
+ }
+ }
+
+ /**
+ * Builder class for {@link BlockItem}s.
+ * Internally used by {@link BlockBuilder}.
+ */
+ public static class BlockItemBuilder, T extends BlockItem> extends ContentBuilder {
+ protected Item.Properties properties;
+ protected final BiFunction, Item.Properties, T> itemFactory;
+ @Nullable
+ protected RegistrySupplier extends Block> parentBlockRegistryEntry = null;
+
+ private BlockItemBuilder(R register, String name, BiFunction, Item.Properties, T> itemFactory) {
+ super(register, name);
+ this.itemFactory = itemFactory;
+ this.properties = new Item.Properties();
+ }
+
+ public BlockItemBuilder withProperties(final Item.Properties properties) {
+ this.properties = properties;
+ return this;
+ }
+
+ public BlockItemBuilder withProperties(Consumer properties) {
+ properties.accept(this.properties);
+ return this;
+ }
+
+ public BlockItemBuilder withProperties(Function properties) {
+ this.properties = properties.apply(this.properties);
+ return this;
+ }
+
+ public BlockItemBuilder withStackSize(final int stackSize) {
+ this.properties.stacksTo(stackSize);
+ return this;
+ }
+
+ protected void setParentBlockRegistryEntry(@NonNull RegistrySupplier extends Block> parentBlockRegistryEntry) {
+ this.parentBlockRegistryEntry = parentBlockRegistryEntry;
+ }
+
+ @Override
+ public RegistrySupplier end() {
+ if (this.parentBlockRegistryEntry == null) throw new IllegalStateException("Parent block registry entry must not be null!");
+ return this.register.items.register(this.id, () -> this.itemFactory.apply(this.parentBlockRegistryEntry, this.properties));
+ }
+
+ @Override
+ public Holder endAsHolder() {
+ return this.end().getRegistrar().getHolder(this.id);
+ }
+ }
+
+ /**
+ * Builder class for {@link Block}s.
+ */
+ public static class BlockBuilder, T extends Block> extends ContentBuilder {
+ private Function blockFactory;
+ private Block.Properties properties;
+ private AbstractRegister.BlockItemBuilder blockItemBuilder;
+
+ private BlockBuilder(R register, String name, Function blockFactory) {
+ super(register, name);
+ this.blockFactory = blockFactory;
+ this.properties = BlockBehaviour.Properties.ofFullCopy(Blocks.STONE);
+ this.blockItemBuilder = register.blockItem(name);
+ }
+
+ public BlockBuilder withBlockItem(BlockItemFactory blockItemFactory) {
+ this.blockItemBuilder = blockItemFactory.modify(this.blockItemBuilder);
+ return this;
+ }
+
+ public BlockBuilder withProperties(final Block.Properties properties) {
+ this.properties = properties;
+ return this;
+ }
+
+ public BlockBuilder withProperties(Consumer properties) {
+ properties.accept(this.properties);
+ return this;
+ }
+
+ public BlockBuilder withProperties(Function properties) {
+ this.properties = properties.apply(this.properties);
+ return this;
+ }
+
+ @Override
+ public RegistrySupplier end() {
+ RegistrySupplier blockSupplier = this.register.blocks.register(this.id, () -> this.blockFactory.apply(this.properties));
+ this.blockItemBuilder.setParentBlockRegistryEntry(blockSupplier);
+ this.blockItemBuilder.end();
+ return blockSupplier;
+ }
+
+ @Override
+ public Holder endAsHolder() {
+ return this.end().getRegistrar().getHolder(this.id);
+ }
+
+ @FunctionalInterface
+ public interface BlockItemFactory, T extends BlockItem> {
+ BlockItemBuilder modify(BlockItemBuilder builder);
+ }
+ }
+
+ public static class EntityTypeBuilder, T extends LivingEntity> extends ContentBuilder, R> {
+ protected final EntityFactory entityFactory;
+ protected MobCategory category;
+ protected int trackingRange;
+ protected EntityDimensions dimensions;
+
+ protected boolean summonable;
+ protected boolean saveable;
+ protected boolean fireImmune;
+ protected Supplier immuneTo;
+ protected boolean canSpawnFarFromPlayer;
+ protected int updateInterval;
+ private Supplier attributeBuilder;
+
+ private EntityTypeBuilder(R register, String name, final EntityFactory entityFactory) {
+ super(register, name);
+ this.entityFactory = entityFactory;
+ this.category = MobCategory.MISC;
+ this.trackingRange = 5;
+ this.dimensions = EntityDimensions.scalable(0.6F, 1.8F);
+ this.summonable = true;
+ this.saveable = true;
+ this.fireImmune = false;
+ this.immuneTo = null;
+ this.canSpawnFarFromPlayer = true;
+ this.updateInterval = 3;
+ this.attributeBuilder = Mob::createMobAttributes;
+ }
+
+ public EntityTypeBuilder withCategory(final MobCategory category) {
+ this.category = category;
+ this.canSpawnFarFromPlayer = category == MobCategory.CREATURE | category == MobCategory.MISC;
+ return this;
+ }
+
+ public EntityTypeBuilder withTrackingRange(final int trackingRange) {
+ this.trackingRange = trackingRange;
+ return this;
+ }
+
+ public EntityTypeBuilder withSize(final float width, final float height) {
+ this.dimensions = EntityDimensions.scalable(width, height);
+ return this;
+ }
+
+ public EntityTypeBuilder notSummonable() {
+ this.summonable = false;
+ return this;
+ }
+
+ public EntityTypeBuilder notSaveable() {
+ this.saveable = false;
+ return this;
+ }
+
+ public EntityTypeBuilder fireImmune() {
+ this.fireImmune = true;
+ return this;
+ }
+
+ public EntityTypeBuilder immuneTo(Supplier immuneTo) {
+ this.immuneTo = immuneTo;
+ return this;
+ }
+
+ public EntityTypeBuilder canSpawnFarFromPlayer() {
+ this.canSpawnFarFromPlayer = true;
+ return this;
+ }
+
+ public EntityTypeBuilder withUpdateInterval(int updateInterval) {
+ this.updateInterval = updateInterval;
+ return this;
+ }
+
+ public EntityTypeBuilder withAttributeBuilder(Supplier attributeBuilder) {
+ this.attributeBuilder = attributeBuilder;
+ return this;
+ }
+
+ @Override
+ public RegistrySupplier> end() {
+ RegistrySupplier> supplier = this.register.entityTypes.register(this.id, () -> {
+ EntityType.Builder builder = EntityType.Builder.of(this.entityFactory, this.category)
+ .clientTrackingRange(this.trackingRange)
+ .sized(this.dimensions.width(), this.dimensions.height())
+ .updateInterval(this.updateInterval);
+
+ if (!this.summonable) builder.noSummon();
+ if (!this.saveable) builder.noSave();
+ if (this.fireImmune) builder.fireImmune();
+ if (this.immuneTo != null) builder.immuneTo(this.immuneTo.get());
+ if (this.canSpawnFarFromPlayer) builder.canSpawnFarFromPlayer();
+
+ return builder.build(this.id.toString());
+ });
+
+ supplier.listen(type -> ManasAttributeRegistry.registerNew(() -> type, this.attributeBuilder));
+ return supplier;
+ }
+
+ @Override
+ public Holder> endAsHolder() {
+ return this.end().getRegistrar().getHolder(this.id);
+ }
+ }
+
+ /**
+ * Builder class for {@link Attribute}s.
+ */
+ public static class AttributeBuilder> extends ContentBuilder {
+ protected double defaultValue;
+ protected double minimumValue;
+ protected double maximumValue;
+
+ protected boolean syncable;
+ protected Map>, Double> applicableEntityTypes;
+ protected boolean applyToAll = false;
+
+ private AttributeBuilder(R register, String name) {
+ super(register, name);
+ this.defaultValue = 1;
+ this.minimumValue = 0;
+ this.maximumValue = 100_000_000_000D;
+ this.syncable = false;
+ this.applicableEntityTypes = new HashMap<>();
+ }
+
+ /**
+ * Sets the default value of the attribute.
+ */
+ public AttributeBuilder withDefaultValue(double defaultValue) {
+ this.defaultValue = defaultValue;
+ return this;
+ }
+
+ /**
+ * Sets the minimum value of the attribute.
+ */
+ public AttributeBuilder withMinimumValue(double minimumValue) {
+ this.minimumValue = minimumValue;
+ return this;
+ }
+
+ /**
+ * Sets the maximum value of the attribute.
+ */
+ public AttributeBuilder withMaximumValue(double maximumValue) {
+ this.maximumValue = maximumValue;
+ return this;
+ }
+
+ /**
+ * Makes the attribute syncable.
+ */
+ public AttributeBuilder syncable() {
+ this.syncable = true;
+ return this;
+ }
+
+ /**
+ * Applies the attribute to all given entities with the default value.
+ */
+ public AttributeBuilder applyTo(double defaultValue, Supplier> entityType) {
+ this.applicableEntityTypes.put(entityType, defaultValue);
+ return this;
+ }
+
+ /**
+ * Applies the attribute to all given entities with the default value.
+ */
+ public AttributeBuilder applyTo(Supplier> entityType) {
+ return applyTo(this.defaultValue, entityType);
+ }
+
+ /**
+ * Applies the attribute to all given entities with the default value.
+ */
+ @SafeVarargs
+ public final AttributeBuilder applyTo(double defaultValue, Supplier>... entityType) {
+ for (Supplier> typeSupplier : entityType) {
+ this.applicableEntityTypes.put(typeSupplier, defaultValue);
+ }
+ return this;
+ }
+
+ /**
+ * Applies the attribute to all given entities with the default value.
+ */
+ @SafeVarargs
+ public final AttributeBuilder applyTo(Supplier>... entityType) {
+ return applyTo(this.defaultValue, entityType);
+ }
+
+ /**
+ * Applies the attribute to all given entities with the default value.
+ */
+ public AttributeBuilder applyTo(double defaultValue, List>> entityTypes) {
+ for (Supplier> typeSupplier : entityTypes) {
+ this.applicableEntityTypes.put(typeSupplier, defaultValue);
+ }
+ return this;
+ }
+
+ /**
+ * Applies the attribute to all given entities with the default value.
+ */
+ public AttributeBuilder applyTo(List>> entityTypes) {
+ return applyTo(this.defaultValue, entityTypes);
+ }
+
+ /**
+ * Applies the attribute to all known entities.
+ */
+ public AttributeBuilder applyToAll() {
+ this.applyToAll = true;
+ return this;
+ }
+
+ @Override
+ public RegistrySupplier end() {
+ Attribute attribute = new RangedAttribute(String.format("%s.attribute.%s", this.id.getNamespace(),
+ this.id.getPath().replaceAll("/", ".")),
+ this.defaultValue, this.minimumValue, this.maximumValue).setSyncable(this.syncable);
+
+ Holder holder = Registry.registerForHolder(BuiltInRegistries.ATTRIBUTE, this.id, attribute);
+ RegistrySupplier supplier = this.register.attributes.register(this.id, () -> attribute);
+
+ if (this.applyToAll) { //registerToAll doesn't work
+ //ManasAttributeRegistry.registerToAll(builder -> builder.add(holder, this.defaultValue));
+ BuiltInRegistries.ENTITY_TYPE.stream().forEach(type ->
+ ManasAttributeRegistry.register(() -> type, builder -> builder.add(holder, defaultValue)));
+ } else this.applicableEntityTypes.forEach((typeSupplier, defaultValue) ->
+ ManasAttributeRegistry.register(typeSupplier, builder -> builder.add(holder, defaultValue)));
+ return supplier;
+ }
+
+ public Holder endAsHolder() {
+ return this.end().getRegistrar().getHolder(this.id);
+ }
+ }
+
+
+ public static class BlockEntityBuilder, T extends BlockEntity> extends ContentBuilder, R> {
+ protected final BlockEntitySupplier factory;
+ protected Type> dataFixerType;
+ protected Set> validBlocks;
+
+ private BlockEntityBuilder(R register, String name, BlockEntitySupplier factory) {
+ super(register, name);
+ this.factory = factory;
+ this.dataFixerType = null;
+ this.validBlocks = new HashSet<>();
+ }
+
+ /**
+ * Sets the data fixer type of the block entity.
+ */
+ public BlockEntityBuilder withDataFixerType(Type> dataFixerType) {
+ this.dataFixerType = dataFixerType;
+ return this;
+ }
+
+ /**
+ * Adds a valid block for the block entity.
+ */
+ @SafeVarargs
+ public final BlockEntityBuilder withValidBlocks(Supplier extends BaseEntityBlock> validBlock, Supplier extends BaseEntityBlock>... validBlocks) {
+ this.validBlocks.add(validBlock);
+ for (Supplier extends BaseEntityBlock> block : validBlocks) {
+ this.validBlocks.add(block);
+ }
+ return this;
+ }
+
+ @Override
+ public RegistrySupplier> end() {
+ return this.register.blockEntities.register(this.id, () -> BlockEntityType.Builder.of(this.factory, this.validBlocks.stream().map(Supplier::get).toArray(Block[]::new)).build(this.dataFixerType));
+ }
+
+ @Override
+ public Holder> endAsHolder() {
+ return this.end().getRegistrar().getHolder(this.id);
+ }
+ }
+
+ public static class SkillBuilder, T extends ManasSkill> extends ContentBuilder {
+ protected final Supplier skillFactory;
+
+ private SkillBuilder(R register, String name, Supplier skillFactory) {
+ super(register, name);
+ this.skillFactory = skillFactory;
+ }
+
+ @Override
+ public RegistrySupplier end() {
+ return this.register.skills.register(this.id, this.skillFactory);
+ }
+
+ @Override
+ public Holder endAsHolder() {
+ return this.end().getRegistrar().getHolder(this.id);
+ }
+ }
+
+ /**
+ * Base class for content builders.
+ * Contains the common fields and methods.
+ */
+ protected static abstract class ContentBuilder> {
+ protected final R register;
+ protected final ResourceLocation id;
+
+ private ContentBuilder(final R register, final String name) {
+ this.register = register;
+ this.id = ResourceLocation.fromNamespaceAndPath(register.modId, name);
+ }
+
+ public R build() {
+ end();
+ return this.register;
+ }
+
+ public abstract RegistrySupplier end();
+ public abstract Holder endAsHolder();
+ }
+}
diff --git a/core-common/src/main/java/io/github/manasmods/manascore/api/registry/Register.java b/core-common/src/main/java/io/github/manasmods/manascore/api/registry/Register.java
new file mode 100644
index 00000000..2f4c3937
--- /dev/null
+++ b/core-common/src/main/java/io/github/manasmods/manascore/api/registry/Register.java
@@ -0,0 +1,25 @@
+package com.github.manasmods.manascore.api.registry;
+
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.level.block.Block;
+
+/**
+ *
Register
+ *
A builder class for registering content.
+ *
+ *
Usage
+ *
+ *
Create a new instance of this class in your mod's main class and store it in a static variable.
+ *
Call the {@link AbstractRegister#init()} method in your mod's initialization point.
+ *
Use the builder methods of your {@link Register} instance to create {@link Item}s, {@link Block}s, {@link EntityType}s, ...
+ *
+ *
+ * @see AbstractRegister
+ * @see AbstractRegister#init()
+ */
+public class Register extends AbstractRegister {
+ public Register(String modId) {
+ super(modId);
+ }
+}
diff --git a/core-common/src/main/java/io/github/manasmods/manascore/api/utils/ItemComparator.java b/core-common/src/main/java/io/github/manasmods/manascore/api/utils/ItemComparator.java
new file mode 100644
index 00000000..bf772c46
--- /dev/null
+++ b/core-common/src/main/java/io/github/manasmods/manascore/api/utils/ItemComparator.java
@@ -0,0 +1,85 @@
+package com.github.manasmods.manascore.api.utils;
+
+import net.minecraft.world.item.Item;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public class ItemComparator {
+ private final BiFunction[] tests;
+
+ private ItemComparator(BiFunction[] tests) {
+ this.tests = tests;
+ }
+
+ public int compare(T object1, T object2) {
+ for (BiFunction test : this.tests) {
+ int testResult = test.apply(object1, object2);
+ if (testResult != 0) return testResult;
+ }
+
+ return 0;
+ }
+
+ public static class Builder {
+ private final List> tests = new ArrayList<>();
+
+ private Builder(BiFunction initialTest) {
+ tests.add(initialTest);
+ }
+
+ public static Builder first(BiFunction initialTest) {
+ return new Builder<>(initialTest);
+ }
+
+ public static Builder firstInstancesOf(Class extends Item> type) {
+ return new Builder<>((t, t2) -> {
+ if (type.isInstance(t) && !type.isInstance(t2)) return -1;
+ if (type.isInstance(t2) && !type.isInstance(t)) return 1;
+ return 0;
+ });
+ }
+
+ public static Builder firstInstancesOf(Class extends Item> type, boolean exact) {
+ if (!exact) return firstInstancesOf(type);
+
+ return new Builder<>((t, t2) -> {
+ if (type == t.getClass() && type != t2.getClass()) return -1;
+ if (type == t2.getClass() && type != t.getClass()) return 1;
+ return 0;
+ });
+ }
+
+ public Builder then(BiFunction test) {
+ tests.add(test);
+ return this;
+ }
+
+ public Builder thenInstancesOf(Class extends Item> type) {
+ tests.add((t, t2) -> {
+ if (type.isInstance(t) && !type.isInstance(t2)) return -1;
+ if (type.isInstance(t2) && !type.isInstance(t)) return 1;
+ return 0;
+ });
+
+ return this;
+ }
+
+ public Builder thenInstancesOf(Class extends Item> type, boolean exact) {
+ if (!exact) return thenInstancesOf(type);
+
+ tests.add((t, t2) -> {
+ if (type == t.getClass() && type != t2.getClass()) return -1;
+ if (type == t2.getClass() && type != t.getClass()) return 1;
+ return 0;
+ });
+
+ return this;
+ }
+
+ public ItemComparator build() {
+ return new ItemComparator<>(this.tests.toArray(BiFunction[]::new));
+ }
+ }
+}
\ No newline at end of file
diff --git a/core-common/src/main/java/io/github/manasmods/manascore/api/utils/StreamUtils.java b/core-common/src/main/java/io/github/manasmods/manascore/api/utils/StreamUtils.java
new file mode 100644
index 00000000..c3c529b7
--- /dev/null
+++ b/core-common/src/main/java/io/github/manasmods/manascore/api/utils/StreamUtils.java
@@ -0,0 +1,16 @@
+package com.github.manasmods.manascore.api.utils;
+
+import lombok.experimental.UtilityClass;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+@UtilityClass
+public class StreamUtils {
+ public static Predicate distinctBy(Function super T, ?> keyExtractor) {
+ Set