diff --git a/attribute-common/build.gradle b/attribute-common/build.gradle new file mode 100644 index 00000000..0bbc98fa --- /dev/null +++ b/attribute-common/build.gradle @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +loom { + accessWidenerPath = file('src/main/resources/manascore_attribute.accesswidener') +} + +dependencies { + implementation(project(path: ":network-common", configuration: 'namedElements')) { transitive false } +} \ No newline at end of file diff --git a/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/ManasCoreAttribute.java b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/ManasCoreAttribute.java new file mode 100644 index 00000000..60044404 --- /dev/null +++ b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/ManasCoreAttribute.java @@ -0,0 +1,12 @@ +package io.github.manasmods.manascore.attribute; + +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributes; +import io.github.manasmods.manascore.attribute.impl.network.ManasCoreAttributeNetwork; + +public class ManasCoreAttribute { + public static void init() { + ManasCoreAttributes.init(); + ManasCoreAttributeRegister.init(); + ManasCoreAttributeNetwork.init(); + } +} diff --git a/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/ManasCoreAttributeRegister.java b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/ManasCoreAttributeRegister.java new file mode 100644 index 00000000..9fe577ed --- /dev/null +++ b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/ManasCoreAttributeRegister.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute; + +import dev.architectury.injectables.annotations.ExpectPlatform; +import net.minecraft.core.Holder; +import net.minecraft.world.entity.ai.attributes.Attribute; +import org.jetbrains.annotations.NotNull; + +public class ManasCoreAttributeRegister { + /** + * Registers a player-specific attribute with the given parameters. + * + * @param modID The mod ID associated with this attribute. + * @param id The unique identifier for the attribute. + * @param name The display name of the attribute. + * @param amount The default base value of the attribute. + * @param min The minimum allowed value for the attribute. + * @param max The maximum allowed value for the attribute. + * @param syncable Whether the attribute should be synchronized between client and server. + * @param sentiment The sentiment classification of the attribute (e.g., beneficial or harmful). + * @return A {@link Holder} containing the registered player attribute. + */ + @ExpectPlatform + public static @NotNull Holder registerPlayerAttribute(String modID, String id, String name, double amount, + double min, double max, boolean syncable, Attribute.Sentiment sentiment) { + throw new AssertionError(); + } + + /** + * Registers a generic attribute that applies to multiple entity types. + * + * @param modID The mod ID associated with this attribute. + * @param id The unique identifier for the attribute. + * @param name The display name of the attribute. + * @param amount The default base value of the attribute. + * @param min The minimum allowed value for the attribute. + * @param max The maximum allowed value for the attribute. + * @param syncable Whether the attribute should be synchronized between client and server. + * @param sentiment The sentiment classification of the attribute (e.g., beneficial or harmful). + * @return A {@link Holder} containing the registered generic attribute. + */ + @ExpectPlatform + public static @NotNull Holder registerGenericAttribute(String modID, String id, String name, double amount, + double min, double max, boolean syncable, Attribute.Sentiment sentiment) { + throw new AssertionError(); + } + + @ExpectPlatform + public static void init() { + throw new AssertionError(); + } +} diff --git a/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/AttributeEvents.java b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/AttributeEvents.java new file mode 100644 index 00000000..1526c70c --- /dev/null +++ b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/AttributeEvents.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.api; + +import dev.architectury.event.Event; +import dev.architectury.event.EventFactory; +import dev.architectury.event.EventResult; +import io.github.manasmods.manascore.network.api.util.Changeable; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; + +public interface AttributeEvents { + Event CRITICAL_ATTACK_CHANCE_EVENT = EventFactory.createEventResult(); + Event START_GLIDE_EVENT = EventFactory.createEventResult(); + Event CONTINUE_GLIDE_EVENT = EventFactory.createEventResult(); + + @FunctionalInterface + interface CriticalAttackChanceEvent { + EventResult applyCrit(LivingEntity attacker, Entity target, float originalMultiplier, Changeable multiplier, Changeable chance); + } + + @FunctionalInterface + interface GlideEvent { + EventResult glide(LivingEntity glider, Changeable canGlide); + } +} diff --git a/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributeUtils.java b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributeUtils.java new file mode 100644 index 00000000..1b587f6e --- /dev/null +++ b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributeUtils.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.api; + +import io.github.manasmods.manascore.network.api.util.Changeable; +import net.minecraft.network.protocol.game.ClientboundAnimatePacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import org.jetbrains.annotations.Nullable; + +public class ManasCoreAttributeUtils { + public static float getAttackDamage(Player player) { + float f = (float) player.getAttributeValue(Attributes.ATTACK_DAMAGE); + float h = player.getAttackStrengthScale(0.5F); + f *= 0.2F + h * h * 0.8F; + return f; + } + + public static float getWeaponDamage(LivingEntity attacker, @Nullable Entity target, @Nullable DamageSource source) { + AttributeInstance attack = attacker.getAttribute(Attributes.ATTACK_DAMAGE); + if (attack == null) return 0; + + float damage = 1F; + AttributeModifier modifier = attack.getModifier(Item.BASE_ATTACK_DAMAGE_ID); + if (modifier != null) damage += (float) modifier.amount(); + + if (target != null && source != null && attacker.level() instanceof ServerLevel serverLevel) + damage = EnchantmentHelper.modifyDamage(serverLevel, attacker.getWeaponItem(), target, source, damage); + return damage; + } + + public static void triggerCriticalAttackEffect(Entity target, Entity attacker) { + target.level().playSound(null, target.getX(), target.getY(), target.getZ(), + SoundEvents.PLAYER_ATTACK_CRIT, attacker.getSoundSource(), 1.0F, 1.0F); + if (target.level() instanceof ServerLevel level) + level.getChunkSource().broadcastAndSend(target, new ClientboundAnimatePacket(target, 4)); + } + + public static boolean canElytraGlide(LivingEntity entity, boolean additionalCheck) { + Changeable glide = Changeable.of(additionalCheck && !entity.onGround() && !entity.isPassenger() + && !entity.hasEffect(MobEffects.LEVITATION) && entity.getAttributeValue(ManasCoreAttributes.GLIDE_SPEED_MULTIPLIER) > 0); + if (AttributeEvents.START_GLIDE_EVENT.invoker().glide(entity, glide).isFalse()) return false; + return glide.get(); + } +} diff --git a/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributes.java b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributes.java new file mode 100644 index 00000000..ccab9143 --- /dev/null +++ b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/api/ManasCoreAttributes.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.api; + +import io.github.manasmods.manascore.attribute.ManasCoreAttributeRegister; +import io.github.manasmods.manascore.attribute.ModuleConstants; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.ai.attributes.Attribute; + +public class ManasCoreAttributes { + /** + * Determine how much the output damage is multiplied when the attacker does a critical attack. + */ + public static final Holder CRITICAL_DAMAGE_MULTIPLIER = ManasCoreAttributeRegister.registerGenericAttribute(ModuleConstants.MOD_ID, + "critical_damage_multiplier", "manascore.attribute.critical_damage_multiplier", + 1.5, 0, 1024, true, Attribute.Sentiment.POSITIVE); + + /** + * Determine the percentage chance for the user to do a critical attack without jumping. + */ + public static final Holder CRITICAL_ATTACK_CHANCE = ManasCoreAttributeRegister.registerGenericAttribute(ModuleConstants.MOD_ID, + "critical_attack_chance", "manascore.attribute.critical_attack_chance", + 0, 0, 100, true, Attribute.Sentiment.POSITIVE); + + /** + * Determine how fast the player can elytra glide without wearing an elytra. + * Sets higher than 0 to allow the player to glide. + * This also affects the speed of normal Elytra gliding speed. + */ + public static final Holder GLIDE_SPEED_MULTIPLIER = ManasCoreAttributeRegister.registerPlayerAttribute(ModuleConstants.MOD_ID, + "glide_speed_multiplier", "manascore.attribute.glide_speed_multiplier", + 0, 0, 1024, true, Attribute.Sentiment.POSITIVE); + + /** + * Determine how fast the player can go inside Lava. + */ + public static final Holder LAVA_SPEED_MULTIPLIER = ManasCoreAttributeRegister.registerGenericAttribute(ModuleConstants.MOD_ID, + "lava_speed_multiplier", "manascore.attribute.lava_speed_multiplier", + 1, 0, 1024, true, Attribute.Sentiment.POSITIVE); + + /** + * Determine how fast the player can swim in Water. + * Similar to NeoForge/Forge's Swim speed instead of Vanilla's Water Movement Efficiency. + */ + public static final Holder SWIM_SPEED_MULTIPLIER = ManasCoreAttributeRegister.registerGenericAttribute(ModuleConstants.MOD_ID, + "swim_speed_multiplier", "manascore.attribute.swim_speed_multiplier", + 1, 0, 1024, true, Attribute.Sentiment.POSITIVE); + + public static ResourceKey getResourceKey(String modID, String path) { + return ResourceKey.create(Registries.ATTRIBUTE, ResourceLocation.fromNamespaceAndPath(modID, path)); + } + + public static void init() { + } +} diff --git a/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/impl/network/ManasCoreAttributeNetwork.java b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/impl/network/ManasCoreAttributeNetwork.java new file mode 100644 index 00000000..e21a5f72 --- /dev/null +++ b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/impl/network/ManasCoreAttributeNetwork.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.impl.network; + +import io.github.manasmods.manascore.attribute.impl.network.c2s.RequestGlideStartPacket; +import io.github.manasmods.manascore.network.api.util.NetworkUtils; + +public class ManasCoreAttributeNetwork { + public static void init() { + NetworkUtils.registerC2SPayload(RequestGlideStartPacket.TYPE, RequestGlideStartPacket.STREAM_CODEC, RequestGlideStartPacket::handle); + } +} diff --git a/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/impl/network/c2s/RequestGlideStartPacket.java b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/impl/network/c2s/RequestGlideStartPacket.java new file mode 100644 index 00000000..d4ddbd72 --- /dev/null +++ b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/impl/network/c2s/RequestGlideStartPacket.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.impl.network.c2s; + +import dev.architectury.networking.NetworkManager; +import dev.architectury.utils.Env; +import io.github.manasmods.manascore.attribute.ModuleConstants; +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributeUtils; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; + +public record RequestGlideStartPacket() implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "request_glide_start")); + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(RequestGlideStartPacket::encode, RequestGlideStartPacket::new); + + public RequestGlideStartPacket(FriendlyByteBuf buf) { + this(); + } + + public void encode(FriendlyByteBuf buf) { + } + + public void handle(NetworkManager.PacketContext context) { + if (context.getEnvironment() != Env.SERVER) return; + context.queue(() -> { + Player player = context.getPlayer(); + if (player == null) return; + player.stopFallFlying(); + if (ManasCoreAttributeUtils.canElytraGlide(player, !player.isFallFlying() && !player.isInLiquid())) + player.startFallFlying(); + }); + } + + public @NotNull Type type() { + return TYPE; + } +} diff --git a/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/mixin/MixinLivingEntity.java b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/mixin/MixinLivingEntity.java new file mode 100644 index 00000000..c62f9aef --- /dev/null +++ b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/mixin/MixinLivingEntity.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.mixin; + +import io.github.manasmods.manascore.attribute.api.AttributeEvents; +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributeUtils; +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributes; +import io.github.manasmods.manascore.network.api.util.Changeable; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(LivingEntity.class) +public abstract class MixinLivingEntity extends Entity { + public MixinLivingEntity(EntityType entityType, Level level) { + super(entityType, level); + } + + @ModifyArg(method = "updateFallFlying", at = @At(value = "INVOKE", + target = "net/minecraft/world/entity/LivingEntity.setSharedFlag(IZ)V")) + private boolean updateFallFlying(boolean value) { + LivingEntity glider = (LivingEntity) (Object) this; + Changeable glide = Changeable.of(value || ManasCoreAttributeUtils.canElytraGlide(glider, this.getSharedFlag(7))); + if (AttributeEvents.CONTINUE_GLIDE_EVENT.invoker().glide(glider, glide).isFalse()) return false; + return glide.get(); + } + + @ModifyArg(method = "travel", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/entity/LivingEntity;move(Lnet/minecraft/world/entity/MoverType;Lnet/minecraft/world/phys/Vec3;)V", ordinal = 2), index = 1) + public Vec3 glideSpeed(Vec3 vec3) { + LivingEntity entity = (LivingEntity) (Object) this; + AttributeInstance instance = entity.getAttribute(ManasCoreAttributes.GLIDE_SPEED_MULTIPLIER); + if (instance == null || instance.getValue() <= 0) return vec3; + return vec3.multiply(instance.getValue(), 1, instance.getValue()); + } + + @ModifyArg(method = "travel", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/entity/LivingEntity;moveRelative(FLnet/minecraft/world/phys/Vec3;)V", ordinal = 1)) + public float lavaSpeed(float speed) { + LivingEntity entity = (LivingEntity) (Object) this; + AttributeInstance instance = entity.getAttribute(ManasCoreAttributes.LAVA_SPEED_MULTIPLIER); + if (instance == null) return speed; + return (float) (speed * instance.getValue()); + } + + @ModifyArg(method = "travel", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/entity/LivingEntity;moveRelative(FLnet/minecraft/world/phys/Vec3;)V", ordinal = 0)) + public float swimSpeed(float speed) { + LivingEntity entity = (LivingEntity) (Object) this; + AttributeInstance instance = entity.getAttribute(ManasCoreAttributes.SWIM_SPEED_MULTIPLIER); + if (instance == null) return speed; + return (float) (speed * instance.getValue()); + } +} diff --git a/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/mixin/MixinLocalPlayer.java b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/mixin/MixinLocalPlayer.java new file mode 100644 index 00000000..e08c8b63 --- /dev/null +++ b/attribute-common/src/main/java/io/github/manasmods/manascore/attribute/mixin/MixinLocalPlayer.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import dev.architectury.networking.NetworkManager; +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributeUtils; +import io.github.manasmods.manascore.attribute.impl.network.c2s.RequestGlideStartPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +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({LocalPlayer.class}) +public class MixinLocalPlayer { + @Inject(method = "aiStep", at = @At(value = "INVOKE_ASSIGN", + target = "net/minecraft/client/player/LocalPlayer.getItemBySlot(Lnet/minecraft/world/entity/EquipmentSlot;)Lnet/minecraft/world/item/ItemStack;")) + public void canStartGliding(CallbackInfo cb) { + LocalPlayer player = Minecraft.getInstance().player; + if (player == null) return; + if (ManasCoreAttributeUtils.canElytraGlide(player, !player.isFallFlying() && !player.isInLiquid())) { + player.startFallFlying(); + NetworkManager.sendToServer(new RequestGlideStartPacket()); + } + } + + @WrapOperation( method = "aiStep", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;tryToStartFallFlying()Z")) + public boolean shouldActivateEquippedElytra(LocalPlayer player, Operation original) { + if (ManasCoreAttributeUtils.canElytraGlide(player, !player.isFallFlying() && !player.isInLiquid())) return false; + return original.call(player); + } +} diff --git a/attribute-common/src/main/resources/architectury.common.json b/attribute-common/src/main/resources/architectury.common.json new file mode 100644 index 00000000..1969c8ce --- /dev/null +++ b/attribute-common/src/main/resources/architectury.common.json @@ -0,0 +1,5 @@ +{ + "injected_interfaces": { + }, + "accessWidener": "manascore_attribute.accesswidener" +} diff --git a/attribute-common/src/main/resources/assets/manascore/lang/en_us.json b/attribute-common/src/main/resources/assets/manascore/lang/en_us.json new file mode 100644 index 00000000..eedf1b3f --- /dev/null +++ b/attribute-common/src/main/resources/assets/manascore/lang/en_us.json @@ -0,0 +1,7 @@ +{ + "manascore.attribute.critical_damage_multiplier": "Critical Damage Multiplier", + "manascore.attribute.critical_attack_chance": "Critical Attack Chance", + "manascore.attribute.glide_speed_multiplier": "Glide Speed Multiplier", + "manascore.attribute.lava_speed_multiplier": "Lava Speed Multiplier", + "manascore.attribute.swim_speed_multiplier": "Swim Speed Multiplier" +} \ No newline at end of file diff --git a/attribute-common/src/main/resources/manascore_attribute.accesswidener b/attribute-common/src/main/resources/manascore_attribute.accesswidener new file mode 100644 index 00000000..91c1adb7 --- /dev/null +++ b/attribute-common/src/main/resources/manascore_attribute.accesswidener @@ -0,0 +1,2 @@ +accessWidener v2 named +accessible field net/minecraft/world/entity/ai/attributes/AttributeSupplier instances Ljava/util/Map; \ No newline at end of file diff --git a/attribute-common/src/main/resources/manascore_attribute.mixins.json b/attribute-common/src/main/resources/manascore_attribute.mixins.json new file mode 100644 index 00000000..d658d26d --- /dev/null +++ b/attribute-common/src/main/resources/manascore_attribute.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "package": "io.github.manasmods.manascore.attribute.mixin", + "compatibilityLevel": "JAVA_21", + "minVersion": "0.8", + "client": [ + "MixinLocalPlayer" + ], + "mixins": [ + "MixinLivingEntity" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/attribute-fabric/build.gradle b/attribute-fabric/build.gradle new file mode 100644 index 00000000..1cc4f1c3 --- /dev/null +++ b/attribute-fabric/build.gradle @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +loom { + accessWidenerPath = project(":attribute-common").loom.accessWidenerPath +} + +dependencies { + implementation project(path: ":network-common", configuration: 'transformProductionFabric') +} diff --git a/attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/ManasCoreAttributeFabric.java b/attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/ManasCoreAttributeFabric.java new file mode 100644 index 00000000..5898b43a --- /dev/null +++ b/attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/ManasCoreAttributeFabric.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.fabric; + +import io.github.manasmods.manascore.attribute.ManasCoreAttribute; +import net.fabricmc.api.ModInitializer; + +public class ManasCoreAttributeFabric implements ModInitializer { + @Override + public void onInitialize() { + ManasCoreAttribute.init(); + } +} \ No newline at end of file diff --git a/attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/ManasCoreAttributeRegisterImpl.java b/attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/ManasCoreAttributeRegisterImpl.java new file mode 100644 index 00000000..492fc9f7 --- /dev/null +++ b/attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/ManasCoreAttributeRegisterImpl.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.fabric; + +import dev.architectury.event.events.common.LifecycleEvent; +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributes; +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricDefaultAttributeRegistry; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.*; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +public class ManasCoreAttributeRegisterImpl { + public static final List> GENERIC_REGISTRY = new CopyOnWriteArrayList<>(); + public static final List> PLAYER_REGISTRY = new CopyOnWriteArrayList<>(); + + public static Holder registerToPlayers(Holder holder) { + PLAYER_REGISTRY.add(holder); + return holder; + } + + public static Holder registerToGeneric(Holder holder) { + GENERIC_REGISTRY.add(holder); + return holder; + } + + public static @NotNull Holder registerPlayerAttribute(String modID, String id, String name, double amount, + double min, double max, boolean syncable, Attribute.Sentiment sentiment) { + Attribute attribute = new RangedAttribute(name, amount, min, max).setSyncable(syncable).setSentiment(sentiment); + Holder holder = Registry.registerForHolder(BuiltInRegistries.ATTRIBUTE, ManasCoreAttributes.getResourceKey(modID, id), attribute); + return registerToPlayers(holder); + } + + public static @NotNull Holder registerGenericAttribute(String modID, String id, String name, double amount, + double min, double max, boolean syncable, Attribute.Sentiment sentiment) { + Attribute attribute = new RangedAttribute(name, amount, min, max).setSyncable(syncable).setSentiment(sentiment); + Holder holder = Registry.registerForHolder(BuiltInRegistries.ATTRIBUTE, ManasCoreAttributes.getResourceKey(modID, id), attribute); + return registerToGeneric(holder); + } + + public static AttributeSupplier.Builder addLivingEntityAttributes(AttributeSupplier.Builder builder) { + for (Holder holder : GENERIC_REGISTRY) builder.add(holder); + return builder; + } + + public static void init() { + LifecycleEvent.SETUP.register(() -> { + BuiltInRegistries.ENTITY_TYPE.stream().filter(DefaultAttributes::hasSupplier) + .map(entityType -> (EntityType) entityType) + .forEach(entityType -> { + if (entityType == null) return; + + Map, AttributeInstance> map = DefaultAttributes.getSupplier(entityType).instances; + AttributeSupplier.Builder builder = new AttributeSupplier.Builder(); + map.forEach((attribute, attributeInstance) -> builder.add(attribute, attributeInstance.getBaseValue())); + + GENERIC_REGISTRY.forEach(holder -> { + if (!map.containsKey(holder)) builder.add(holder); + }); + if (entityType.equals(EntityType.PLAYER)) PLAYER_REGISTRY.forEach(holder -> { + if (!map.containsKey(holder)) builder.add(holder); + }); + FabricDefaultAttributeRegistry.register(entityType, builder); + }); + }); + } +} diff --git a/attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinLivingEntity.java b/attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinLivingEntity.java new file mode 100644 index 00000000..ec31b203 --- /dev/null +++ b/attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinLivingEntity.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.fabric.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef; +import io.github.manasmods.manascore.attribute.api.AttributeEvents; +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributeUtils; +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributes; +import io.github.manasmods.manascore.attribute.fabric.ManasCoreAttributeRegisterImpl; +import io.github.manasmods.manascore.network.api.util.Changeable; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.AttributeSupplier; +import net.minecraft.world.entity.player.Player; +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.CallbackInfoReturnable; + +@Mixin(value = LivingEntity.class, priority = 200) +public class MixinLivingEntity { + + @Inject(method = "createLivingAttributes", at = @At("RETURN")) + private static void createLivingAttributes(CallbackInfoReturnable cir) { + ManasCoreAttributeRegisterImpl.addLivingEntityAttributes(cir.getReturnValue()); + } + + @Inject(method = "hurt(Lnet/minecraft/world/damagesource/DamageSource;F)Z", at = @At(value = "HEAD")) + void applyCriticalDamage(DamageSource damageSource, float amount, CallbackInfoReturnable cir, @Local(argsOnly = true) LocalFloatRef newAmount) { + if (damageSource.getDirectEntity() instanceof LivingEntity attacker) { // Direct attack + if (attacker instanceof Player) return; // Players have their own Critical Event + LivingEntity target = (LivingEntity) (Object) this; + + Changeable multiplier = Changeable.of((float) attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER)); + Changeable chance = Changeable.of(attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE) / 100); + if (AttributeEvents.CRITICAL_ATTACK_CHANCE_EVENT.invoker().applyCrit(attacker, target, 1, multiplier, chance).isFalse()) return; + + if (target.getRandom().nextFloat() > chance.get()) return; + ManasCoreAttributeUtils.triggerCriticalAttackEffect(target, attacker); + newAmount.set(amount * multiplier.get()); + } + } +} diff --git a/attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinPlayer.java b/attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinPlayer.java new file mode 100644 index 00000000..b9d30144 --- /dev/null +++ b/attribute-fabric/src/main/java/io/github/manasmods/manascore/attribute/fabric/mixin/MixinPlayer.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.fabric.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef; +import io.github.manasmods.manascore.attribute.api.AttributeEvents; +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributeUtils; +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributes; +import io.github.manasmods.manascore.network.api.util.Changeable; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.*; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Player.class) +public abstract class MixinPlayer { + @Inject(method = "hurt(Lnet/minecraft/world/damagesource/DamageSource;F)Z", at = @At(value = "HEAD")) + void applyCriticalDamage(DamageSource damageSource, float amount, CallbackInfoReturnable cir, @Local(argsOnly = true) LocalFloatRef newAmount) { + if (damageSource.getDirectEntity() instanceof LivingEntity attacker) { // Direct attack + if (attacker instanceof Player) return; // Players have their own Critical Event + LivingEntity target = (LivingEntity) (Object) this; + + Changeable multiplier = Changeable.of((float) attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER)); + Changeable chance = Changeable.of(attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE) / 100); + if (AttributeEvents.CRITICAL_ATTACK_CHANCE_EVENT.invoker().applyCrit(attacker, target, 1, multiplier, chance).isFalse()) return; + + if (target.getRandom().nextFloat() > chance.get()) return; + ManasCoreAttributeUtils.triggerCriticalAttackEffect(target, attacker); + newAmount.set(amount * multiplier.get()); + } + } + + @ModifyArg(method = "attack", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/entity/Entity;hurt(Lnet/minecraft/world/damagesource/DamageSource;F)Z"), index = 1) + private float getCritChanceDamage(float amount, @Local(ordinal = 0, argsOnly = true) Entity target, + @Local(ordinal = 1) float enchantDamage, @Local(ordinal = 2) boolean vanillaCrit) { + Player player = (Player) (Object) this; + if (!vanillaCrit) { + Changeable multiplier = Changeable.of((float) player.getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER)); + Changeable chance = Changeable.of(player.getAttributeValue(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE) / 100); + if (AttributeEvents.CRITICAL_ATTACK_CHANCE_EVENT.invoker().applyCrit(player, target, 1, multiplier, chance).isFalse()) return amount; + + if (player.getRandom().nextFloat() > chance.get()) return amount; + ManasCoreAttributeUtils.triggerCriticalAttackEffect(target, player); + return (amount - enchantDamage) * multiplier.get() + enchantDamage; + } + return amount; + } + + @ModifyConstant(method = "attack(Lnet/minecraft/world/entity/Entity;)V", constant = @Constant(floatValue = 1.5F)) + private float getCritMultiplier(float multiplier) { + return (float) ((Player) (Object) this).getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER); + } +} diff --git a/attribute-fabric/src/main/resources/assets/manascore/icon.png b/attribute-fabric/src/main/resources/assets/manascore/icon.png new file mode 100644 index 00000000..a38ae1d1 Binary files /dev/null and b/attribute-fabric/src/main/resources/assets/manascore/icon.png differ diff --git a/attribute-fabric/src/main/resources/assets/manascore/logo.png b/attribute-fabric/src/main/resources/assets/manascore/logo.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/attribute-fabric/src/main/resources/assets/manascore/logo.png differ diff --git a/attribute-fabric/src/main/resources/fabric.mod.json b/attribute-fabric/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..439e92bc --- /dev/null +++ b/attribute-fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,35 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "Utility and Core Library for Manas Mods", + "authors": [ + "ManasMods" + ], + "contact": { + "homepage": "" + }, + "license": "${license}", + "icon": "assets/manascore/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "io.github.manasmods.manascore.attribute.fabric.ManasCoreAttributeFabric" + ], + "client": [] + }, + "mixins": [ + "${mod_id}.mixins.json", + "${mod_id}-fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=${fabric_loader_version}", + "minecraft": "~${minecraft_version}", + "java": ">=21", + "architectury": ">=${architectury_version}", + "fabric-api": "*" + }, + "suggests": { + } +} diff --git a/attribute-fabric/src/main/resources/manascore_attribute-fabric.mixins.json b/attribute-fabric/src/main/resources/manascore_attribute-fabric.mixins.json new file mode 100644 index 00000000..3dcf4ec7 --- /dev/null +++ b/attribute-fabric/src/main/resources/manascore_attribute-fabric.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "package": "io.github.manasmods.manascore.attribute.fabric.mixin", + "compatibilityLevel": "JAVA_21", + "minVersion": "0.8", + "client": [ + ], + "mixins": [ + "MixinLivingEntity", + "MixinPlayer" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/attribute-neoforge/build.gradle b/attribute-neoforge/build.gradle new file mode 100644 index 00000000..1dff32af --- /dev/null +++ b/attribute-neoforge/build.gradle @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +loom { + accessWidenerPath = project(":attribute-common").loom.accessWidenerPath +} + +dependencies { + implementation project(path: ":network-common", configuration: 'transformProductionNeoForge') +} + +remapJar { + atAccessWideners.add("${project.mod_id}.accesswidener") +} diff --git a/attribute-neoforge/gradle.properties b/attribute-neoforge/gradle.properties new file mode 100644 index 00000000..7da18ea6 --- /dev/null +++ b/attribute-neoforge/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge diff --git a/attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/ManasCoreAttributeNeoForge.java b/attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/ManasCoreAttributeNeoForge.java new file mode 100644 index 00000000..155e1fbc --- /dev/null +++ b/attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/ManasCoreAttributeNeoForge.java @@ -0,0 +1,18 @@ +package io.github.manasmods.manascore.attribute.neoforge; + +import io.github.manasmods.manascore.attribute.ManasCoreAttribute; +import io.github.manasmods.manascore.attribute.ModuleConstants; +import net.minecraft.core.registries.Registries; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.registries.DeferredRegister; + +@Mod(ModuleConstants.MOD_ID) +public final class ManasCoreAttributeNeoForge { + public static final DeferredRegister ATTRIBUTES = DeferredRegister.create(Registries.ATTRIBUTE, ModuleConstants.MOD_ID); + public ManasCoreAttributeNeoForge(IEventBus bus) { + ManasCoreAttribute.init(); + ATTRIBUTES.register(bus); + } +} diff --git a/attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/ManasCoreAttributeRegisterImpl.java b/attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/ManasCoreAttributeRegisterImpl.java new file mode 100644 index 00000000..33e8fa6b --- /dev/null +++ b/attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/ManasCoreAttributeRegisterImpl.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.neoforge; + +import net.minecraft.core.Holder; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.RangedAttribute; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModLoadingContext; +import net.neoforged.neoforge.event.entity.EntityAttributeModificationEvent; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import static io.github.manasmods.manascore.attribute.neoforge.ManasCoreAttributeNeoForge.ATTRIBUTES; + +public class ManasCoreAttributeRegisterImpl { + private static final List> GENERIC_REGISTRY = new CopyOnWriteArrayList<>(); + private static final List> PLAYER_REGISTRY = new CopyOnWriteArrayList<>(); + + public static Holder registerToPlayers(Holder holder) { + PLAYER_REGISTRY.add(holder); + return holder; + } + + public static Holder registerToGeneric(Holder holder) { + GENERIC_REGISTRY.add(holder); + return holder; + } + + public static @NotNull Holder registerPlayerAttribute(String modID, String id, String name, double amount, + double min, double max, boolean syncable, Attribute.Sentiment sentiment) { + Attribute attribute = new RangedAttribute(name, amount, min, max).setSyncable(syncable).setSentiment(sentiment); + return registerToPlayers(ATTRIBUTES.register(id, () -> attribute)); + } + + public static @NotNull Holder registerGenericAttribute(String modID, String id, String name, double amount, + double min, double max, boolean syncable, Attribute.Sentiment sentiment) { + Attribute attribute = new RangedAttribute(name, amount, min, max).setSyncable(syncable).setSentiment(sentiment); + return registerToGeneric(ATTRIBUTES.register(id, () -> attribute)); + } + + static void registerAttributes(final EntityAttributeModificationEvent e) { + e.getTypes().forEach(type -> { + if (type.equals(EntityType.PLAYER)) PLAYER_REGISTRY.forEach(holder -> { + if (!e.has(type, holder)) e.add(type, holder); + }); + GENERIC_REGISTRY.forEach(holder -> { + if (!e.has(type, holder)) e.add(type, holder); + }); + }); + + // Clear the registry + PLAYER_REGISTRY.clear(); + GENERIC_REGISTRY.clear(); + } + + public static void init() { + IEventBus modEventBus = ModLoadingContext.get().getActiveContainer().getEventBus(); + if (modEventBus == null) return; + modEventBus.addListener(ManasCoreAttributeRegisterImpl::registerAttributes); + } +} diff --git a/attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/NeoForgeCommonEventInvoker.java b/attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/NeoForgeCommonEventInvoker.java new file mode 100644 index 00000000..0ccfe7e3 --- /dev/null +++ b/attribute-neoforge/src/main/java/io/github/manasmods/manascore/attribute/neoforge/NeoForgeCommonEventInvoker.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.attribute.neoforge; + +import io.github.manasmods.manascore.attribute.api.AttributeEvents; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributeUtils; +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributes; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.neoforged.bus.api.EventPriority; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent; +import net.neoforged.neoforge.event.entity.player.CriticalHitEvent; + +@EventBusSubscriber +public class NeoForgeCommonEventInvoker { + private NeoForgeCommonEventInvoker() { + } + + @SubscribeEvent(priority = EventPriority.HIGH) + public static void applyEntityCrit(final LivingIncomingDamageEvent e) { + if (!(e.getSource().getDirectEntity() instanceof LivingEntity attacker)) return; // Direct attack + if (attacker instanceof Player) return; // Players have their own Critical Event + LivingEntity target = e.getEntity(); + + Changeable multiplier = Changeable.of((float) attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER)); + Changeable chance = Changeable.of(attacker.getAttributeValue(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE) / 100); + if (AttributeEvents.CRITICAL_ATTACK_CHANCE_EVENT.invoker().applyCrit(attacker, target, 1, multiplier, chance).isFalse()) return; + + if (target.getRandom().nextFloat() > chance.get()) return; + ManasCoreAttributeUtils.triggerCriticalAttackEffect(target, attacker); + e.setAmount(e.getAmount() * multiplier.get()); + } + + @SubscribeEvent(priority = EventPriority.HIGH) + public static void onCriticalHit(final CriticalHitEvent e) { + if (e.isVanillaCritical()) { + float multiplier = (float) e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER); + e.setDamageMultiplier(e.getDamageMultiplier() / e.getVanillaMultiplier() * multiplier); + return; + } + + float critMultiplier = (float) e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER); + Changeable multiplier = Changeable.of(e.getDamageMultiplier() * critMultiplier); + Changeable chance = Changeable.of(e.getEntity().getAttributeValue(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE) / 100); + if (AttributeEvents.CRITICAL_ATTACK_CHANCE_EVENT.invoker().applyCrit(e.getEntity(), e.getTarget(), + e.getDamageMultiplier(), multiplier, chance).isFalse()) return; + + if (e.getEntity().getRandom().nextFloat() > chance.get()) return; + e.setDamageMultiplier(multiplier.get()); + e.setCriticalHit(true); + } +} diff --git a/attribute-neoforge/src/main/resources/META-INF/neoforge.mods.toml b/attribute-neoforge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..dfbcf412 --- /dev/null +++ b/attribute-neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,38 @@ +modLoader = "javafml" +loaderVersion = "[2,)" +issueTrackerURL = "" +license = "${license}" + +[[mods]] +modId = "${mod_id}" +version = "${version}" +displayName = "${mod_name}" +authors = "ManasMods" +description = ''' +Utility and Core Library for Manas Mods +''' +logoFile = "icon.png" + +[[dependencies.${mod_id}]] + modId = "neoforge" + type = "required" + versionRange = "[21,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "minecraft" + type = "required" + versionRange = "[${minecraft_version},)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "architectury" + type = "required" + versionRange = "[${architectury_version},)" + ordering = "AFTER" + side = "BOTH" + +[[mixins]] +config = "${mod_id}.mixins.json" diff --git a/attribute-neoforge/src/main/resources/icon.png b/attribute-neoforge/src/main/resources/icon.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/attribute-neoforge/src/main/resources/icon.png differ diff --git a/build.gradle b/build.gradle index c4f30571..d459e680 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ subprojects { apply plugin: 'architectury-plugin' apply plugin: 'io.freefair.lombok' - if (!project.name.startsWith("testing")) { + if (!project.name.startsWith("testing") && !project.name.startsWith("bundle")) { apply plugin: 'org.jreleaser' apply plugin: 'maven-publish' } @@ -45,7 +45,7 @@ subprojects { } ext { - var nameSeparatorIndex = project.path.lastIndexOf('-'); + var nameSeparatorIndex = project.path.lastIndexOf('-') var nameOffset = project.path.startsWith(':') && nameSeparatorIndex > 0 ? 1 : 0 module_name = project.path.substring(nameOffset, nameSeparatorIndex == -1 ? project.path.length() : nameSeparatorIndex) @@ -154,22 +154,28 @@ subprojects { modImplementation "net.fabricmc.fabric-api:fabric-api:$rootProject.fabric_api_version" modImplementation "dev.architectury:architectury-fabric:$rootProject.architectury_api_version" - common(project(path: ":$project.module_name-common", configuration: 'namedElements')) { transitive false } - shadowBundle project(path: ":$project.module_name-common", configuration: 'transformProductionFabric') + if(!project.name.startsWith("bundle")) { + common(project(path: ":$project.module_name-common", configuration: 'namedElements')) { transitive false } + shadowBundle project(path: ":$project.module_name-common", configuration: 'transformProductionFabric') + } } if (project.name.endsWith("neoforge")) { neoForge "net.neoforged:neoforge:$rootProject.neoforge_version" modImplementation "dev.architectury:architectury-neoforge:$rootProject.architectury_api_version" - common(project(path: ":$project.module_name-common", configuration: 'namedElements')) { transitive false } - shadowBundle project(path: ":$project.module_name-common", configuration: 'transformProductionNeoForge') + if(!project.name.startsWith("bundle")) { + common(project(path: ":$project.module_name-common", configuration: 'namedElements')) { transitive false } + shadowBundle project(path: ":$project.module_name-common", configuration: 'transformProductionNeoForge') + } } } java { - withSourcesJar() - withJavadocJar() + if (!project.name.startsWith("bundle")) { + withSourcesJar() + withJavadocJar() + } sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 @@ -180,7 +186,7 @@ subprojects { it.options.release = 21 } - if (!project.name.startsWith("testing")) { + if (!project.name.startsWith("testing") && !project.name.startsWith("bundle")) { jreleaser { gitRootSearch = true @@ -236,7 +242,12 @@ subprojects { repositories { maven { - url layout.buildDirectory.dir('staging-deploy') + name = "ManasCore" + credentials { + username System.getenv().getOrDefault("MANAS_REPO_USER", nexusUsername) + password System.getenv().getOrDefault("MANAS_REPO_USER_PASSWORD", nexusPassword) + } + url "https://nexus.nighti.dev/repository/manasmods-public/" } } } @@ -249,25 +260,38 @@ subprojects { if (!project.name.endsWith("common")) { processResources { - def placeholders = [ - version : project.version, - mod_id : project.mod_id, - mod_name : "$mod_display_name - ${project.module_name.capitalize()}", - architectury_version : architectury_api_version, - minecraft_version : minecraft_version, - fabric_loader_version: fabric_loader_version, - license : "GPLv3" - ] - - placeholders.forEach { key, value -> - inputs.property key, value - } - if (project.name.endsWith("neoforge")) { + def placeholders = [ + version : project.version, + mod_id : project.mod_id, + mod_name : "$mod_display_name - ${project.module_name.capitalize()}", + architectury_version : architectury_api_version, + minecraft_version : minecraft_version, + license : "GPLv3" + ] + + placeholders.forEach { key, value -> + inputs.property key, value + } + filesMatching('META-INF/neoforge.mods.toml') { expand placeholders } } else if (project.name.endsWith("fabric")) { + def placeholders = [ + version : project.version, + mod_id : project.mod_id, + mod_name : "$mod_display_name - ${project.module_name.capitalize()}", + architectury_version : architectury_api_version, + minecraft_version : minecraft_version, + fabric_loader_version: fabric_loader_version, + license : "GPLv3" + ] + + placeholders.forEach { key, value -> + inputs.property key, value + } + filesMatching('fabric.mod.json') { expand placeholders @@ -307,7 +331,11 @@ subprojects { if (!project.name.endsWith("common")) { task copyJar(type: Copy) { from remapJar - into "${rootProject.projectDir}/build/${module_type}" + if (project.name.startsWith("bundle")) { + into "${rootProject.projectDir}/build" + } else { + into "${rootProject.projectDir}/build/${module_type}" + } } build.dependsOn copyJar } diff --git a/bundle-fabric/build.gradle b/bundle-fabric/build.gradle new file mode 100644 index 00000000..2a04d613 --- /dev/null +++ b/bundle-fabric/build.gradle @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024-2025. ManasMods + * GNU General Public License 3 + */ + +ext { + includedProjects = [ + ":network", + ":storage", + ":inventory", + ":command" + ] +} + +base { + archivesName = "manascore-fabric" +} + +loom { + mods { + includedProjects.forEach { + def moduleProject = project("$it-fabric") + register(moduleProject.name) { + sourceSet("main", moduleProject) + } + } + } +} + +dependencies { + includedProjects.forEach { + common(project(path: "$it-common", configuration: 'namedElements')) + implementation(project(path: "$it-fabric", configuration: 'namedElements')) + + include project(path: "$it-fabric") + } +} diff --git a/bundle-fabric/src/main/java/io/github/manasmods/manascore/ManasCore.java b/bundle-fabric/src/main/java/io/github/manasmods/manascore/ManasCore.java new file mode 100644 index 00000000..6f83e141 --- /dev/null +++ b/bundle-fabric/src/main/java/io/github/manasmods/manascore/ManasCore.java @@ -0,0 +1,10 @@ +package io.github.manasmods.manascore; + +import net.fabricmc.api.ModInitializer; + +public class ManasCore implements ModInitializer { + @Override + public void onInitialize() { + + } +} diff --git a/bundle-fabric/src/main/resources/assets/manascore/icon.png b/bundle-fabric/src/main/resources/assets/manascore/icon.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/bundle-fabric/src/main/resources/assets/manascore/icon.png differ diff --git a/bundle-fabric/src/main/resources/fabric.mod.json b/bundle-fabric/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..eacbb822 --- /dev/null +++ b/bundle-fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,31 @@ +{ + "schemaVersion": 1, + "id": "manascore", + "version": "${version}", + "name": "ManasCore", + "description": "Utility and Core Library for Manas Mods", + "authors": [ + "ManasMods" + ], + "contact": { + "homepage": "" + }, + "license": "${license}", + "icon": "assets/manascore/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "io.github.manasmods.manascore.ManasCore" + ], + "client": [] + }, + "depends": { + "fabricloader": ">=${fabric_loader_version}", + "minecraft": "~${minecraft_version}", + "java": ">=21", + "architectury": ">=${architectury_version}", + "fabric-api": "*" + }, + "suggests": { + } +} diff --git a/bundle-neoforge/build.gradle b/bundle-neoforge/build.gradle new file mode 100644 index 00000000..8dc0e33d --- /dev/null +++ b/bundle-neoforge/build.gradle @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024-2025. ManasMods + * GNU General Public License 3 + */ + +ext { + includedProjects = [ + ":network", + ":storage", + ":inventory", + ":command" + ] +} + +base { + archivesName = "manascore-neoforge" +} + +loom { + mods { + includedProjects.forEach { + def moduleProject = project("$it-neoforge") + register(moduleProject.name) { + sourceSet("main", moduleProject) + } + } + } +} + +dependencies { + includedProjects.forEach { + common(project(path: "$it-common", configuration: 'namedElements')) + implementation(project(path: "$it-neoforge", configuration: 'namedElements')) + + include project(path: "$it-neoforge") + } +} diff --git a/bundle-neoforge/gradle.properties b/bundle-neoforge/gradle.properties new file mode 100644 index 00000000..7da18ea6 --- /dev/null +++ b/bundle-neoforge/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge diff --git a/bundle-neoforge/src/main/java/io/github/manasmods/manascore/ManasCore.java b/bundle-neoforge/src/main/java/io/github/manasmods/manascore/ManasCore.java new file mode 100644 index 00000000..1c1d806f --- /dev/null +++ b/bundle-neoforge/src/main/java/io/github/manasmods/manascore/ManasCore.java @@ -0,0 +1,7 @@ +package io.github.manasmods.manascore; + +import net.neoforged.fml.common.Mod; + +@Mod("manascore") +public class ManasCore { +} diff --git a/bundle-neoforge/src/main/resources/META-INF/neoforge.mods.toml b/bundle-neoforge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..97e2fa32 --- /dev/null +++ b/bundle-neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,35 @@ +modLoader = "javafml" +loaderVersion = "[2,)" +issueTrackerURL = "" +license = "${license}" + +[[mods]] +modId = "manascore" +version = "${version}" +displayName = "ManasCore" +authors = "ManasMods" +description = ''' +Utility and Core Library for Manas Mods +''' +logoFile = "icon.png" + +[[dependencies.manascore]] + modId = "neoforge" + type = "required" + versionRange = "[21,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.manascore]] + modId = "minecraft" + type = "required" + versionRange = "[${minecraft_version},)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.manascore]] + modId = "architectury" + type = "required" + versionRange = "[${architectury_version},)" + ordering = "AFTER" + side = "BOTH" diff --git a/bundle-neoforge/src/main/resources/icon.png b/bundle-neoforge/src/main/resources/icon.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/bundle-neoforge/src/main/resources/icon.png differ diff --git a/command-common/build.gradle b/command-common/build.gradle index c12dbf77..b33163dc 100644 --- a/command-common/build.gradle +++ b/command-common/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/CommandAnnotationHandler.java b/command-common/src/main/java/io/github/manasmods/manascore/command/CommandAnnotationHandler.java index 4a3218b6..94ab9773 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/CommandAnnotationHandler.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/CommandAnnotationHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -12,7 +12,7 @@ import io.github.manasmods.manascore.command.api.Command; import io.github.manasmods.manascore.command.api.Execute; import io.github.manasmods.manascore.command.api.Permission; -import io.github.manasmods.manascore.command.api.parameter.Sender; +import io.github.manasmods.manascore.command.api.parameter.SenderArg; import io.github.manasmods.manascore.command.internal.CommandArgumentRegistry; import lombok.RequiredArgsConstructor; import net.minecraft.commands.CommandSourceStack; @@ -125,7 +125,7 @@ public List> build(CommandArgumentReg continue; } - if (parameterAnnotation instanceof Sender) { + if (parameterAnnotation instanceof SenderArg) { isSenderArgument = true; } diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java b/command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java index 639c04a9..ad57f1b7 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/ManasCoreCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -12,11 +12,36 @@ import dev.architectury.event.events.common.CommandRegistrationEvent; import io.github.manasmods.manascore.command.api.CommandArgumentRegistrationEvent; import io.github.manasmods.manascore.command.api.parameter.*; +import io.github.manasmods.manascore.command.api.parameter.coordinate.BlockPosArg; +import io.github.manasmods.manascore.command.api.parameter.coordinate.RotationArg; +import io.github.manasmods.manascore.command.api.parameter.coordinate.Vec3Arg; +import io.github.manasmods.manascore.command.api.parameter.primitive.*; +import io.github.manasmods.manascore.command.api.parameter.resource.*; import io.github.manasmods.manascore.command.internal.CommandArgumentRegistry; import net.minecraft.commands.CommandSource; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.*; +import net.minecraft.commands.arguments.blocks.BlockInput; +import net.minecraft.commands.arguments.blocks.BlockStateArgument; +import net.minecraft.commands.arguments.coordinates.BlockPosArgument; +import net.minecraft.commands.arguments.coordinates.Coordinates; +import net.minecraft.commands.arguments.coordinates.RotationArgument; +import net.minecraft.commands.arguments.coordinates.Vec3Argument; +import net.minecraft.commands.arguments.item.ItemArgument; +import net.minecraft.commands.arguments.item.ItemInput; +import net.minecraft.commands.arguments.selector.EntitySelector; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.GameType; +import net.minecraft.world.phys.Vec3; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,9 +52,9 @@ public class ManasCoreCommand { public static final Logger LOG = LoggerFactory.getLogger("ManasCore - Command"); public static void init() { - CommandArgumentRegistrationEvent.EVENT.register((registry) -> { + CommandArgumentRegistrationEvent.EVENT.register((registry, dispatcher, buildContext) -> { // Register Literal Argument - registry.register(String.class, Literal.class, (annotation, handler) -> { + registry.register(String.class, LiteralArg.class, (annotation, handler) -> { var literals = annotation.value(); for (var literal : literals) { @@ -39,15 +64,15 @@ public static void init() { }); // Register Sender Argument - registry.register(CommandSourceStack.class, Sender.class, (annotation, handler) -> handler.addValueExtractor(CommandContext::getSource)); - registry.register(ServerPlayer.class, Sender.class, (annotation, handler) -> { + registry.register(CommandSourceStack.class, SenderArg.class, (annotation, handler) -> handler.addValueExtractor(CommandContext::getSource)); + registry.register(ServerPlayer.class, SenderArg.class, (annotation, handler) -> { handler.addValueExtractor(context -> context.getSource().getPlayerOrException()); handler.preventConsoleUsage(); }); - registry.register(CommandSource.class, Sender.class, (annotation, handler) -> handler.addValueExtractor(commandContext -> commandContext.getSource().source)); + registry.register(CommandSource.class, SenderArg.class, (annotation, handler) -> handler.addValueExtractor(commandContext -> commandContext.getSource().source)); // Register UUID Argument - registry.register(UUID.class, Uuid.class, (annotation, handler) -> { + registry.register(UUID.class, UuidArg.class, (annotation, handler) -> { var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); handler.addNode(Commands.argument(argumentName, StringArgumentType.word())); handler.addValueExtractorToLastNode(commandContext -> { @@ -61,19 +86,19 @@ public static void init() { }); // Register Boolean Argument - registry.register(Boolean.class, Bool.class, (annotation, handler) -> { + registry.register(Boolean.class, BooleanArg.class, (annotation, handler) -> { var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); handler.addNode(Commands.argument(argumentName, BoolArgumentType.bool())); handler.addValueExtractor(commandContext -> BoolArgumentType.getBool(commandContext, argumentName)); }); - registry.register(boolean.class, Bool.class, (annotation, handler) -> { + registry.register(boolean.class, BooleanArg.class, (annotation, handler) -> { var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); handler.addNode(Commands.argument(argumentName, BoolArgumentType.bool())); handler.addValueExtractor(commandContext -> BoolArgumentType.getBool(commandContext, argumentName)); }); // Register Text Argument - registry.register(String.class, Text.class, (annotation, handler) -> { + registry.register(String.class, TextArg.class, (annotation, handler) -> { var argumentName = annotation.name().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.name(); switch (annotation.value()) { @@ -92,63 +117,189 @@ public static void init() { }); // Register Double Argument - registry.register(Double.class, DoubleNumber.class, (annotation, handler) -> { + registry.register(Double.class, DoubleArg.class, (annotation, handler) -> { var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); handler.addNode(Commands.argument(argumentName, DoubleArgumentType.doubleArg(annotation.min(), annotation.max()))); handler.addValueExtractor(commandContext -> DoubleArgumentType.getDouble(commandContext, argumentName)); }); - registry.register(double.class, DoubleNumber.class, (annotation, handler) -> { + registry.register(double.class, DoubleArg.class, (annotation, handler) -> { var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); handler.addNode(Commands.argument(argumentName, DoubleArgumentType.doubleArg(annotation.min(), annotation.max()))); handler.addValueExtractor(commandContext -> DoubleArgumentType.getDouble(commandContext, argumentName)); }); // Register Float Argument - registry.register(Float.class, FloatNumber.class, (annotation, handler) -> { + registry.register(Float.class, FloatArg.class, (annotation, handler) -> { var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); handler.addNode(Commands.argument(argumentName, FloatArgumentType.floatArg(annotation.min(), annotation.max()))); handler.addValueExtractor(commandContext -> FloatArgumentType.getFloat(commandContext, argumentName)); }); - registry.register(float.class, FloatNumber.class, (annotation, handler) -> { + registry.register(float.class, FloatArg.class, (annotation, handler) -> { var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); handler.addNode(Commands.argument(argumentName, FloatArgumentType.floatArg(annotation.min(), annotation.max()))); handler.addValueExtractor(commandContext -> FloatArgumentType.getFloat(commandContext, argumentName)); }); // Register Integer Argument - registry.register(Integer.class, IntNumber.class, (annotation, handler) -> { + registry.register(Integer.class, IntegerArg.class, (annotation, handler) -> { var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); handler.addNode(Commands.argument(argumentName, IntegerArgumentType.integer(annotation.min(), annotation.max()))); handler.addValueExtractor(commandContext -> IntegerArgumentType.getInteger(commandContext, argumentName)); }); - registry.register(int.class, IntNumber.class, (annotation, handler) -> { + registry.register(int.class, IntegerArg.class, (annotation, handler) -> { var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); handler.addNode(Commands.argument(argumentName, IntegerArgumentType.integer(annotation.min(), annotation.max()))); handler.addValueExtractor(commandContext -> IntegerArgumentType.getInteger(commandContext, argumentName)); }); // Register Long Argument - registry.register(Long.class, LongNumber.class, (annotation, handler) -> { + registry.register(Long.class, LongArg.class, (annotation, handler) -> { var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); handler.addNode(Commands.argument(argumentName, LongArgumentType.longArg(annotation.min(), annotation.max()))); handler.addValueExtractor(commandContext -> LongArgumentType.getLong(commandContext, argumentName)); }); - registry.register(long.class, LongNumber.class, (annotation, handler) -> { + registry.register(long.class, LongArg.class, (annotation, handler) -> { var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); handler.addNode(Commands.argument(argumentName, LongArgumentType.longArg(annotation.min(), annotation.max()))); handler.addValueExtractor(commandContext -> LongArgumentType.getLong(commandContext, argumentName)); }); + + // Register Entity Argument + registry.register(EntitySelector.class, EntityArg.class, (annotation, handler) -> { + var argumentName = annotation.name().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.name(); + switch (annotation.value()) { + case ENTITY -> handler.addNode(Commands.argument(argumentName, EntityArgument.entity())); + case ENTITIES -> handler.addNode(Commands.argument(argumentName, EntityArgument.entities())); + case PLAYER -> handler.addNode(Commands.argument(argumentName, EntityArgument.player())); + case PLAYERS -> handler.addNode(Commands.argument(argumentName, EntityArgument.players())); + } + handler.addValueExtractor(commandContext -> commandContext.getArgument(argumentName, EntitySelector.class)); + }); + + // Register Entity Type Argument + registry.register(Holder.class, EntityTypeArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, ResourceArgument.resource(buildContext, Registries.ENTITY_TYPE))); + handler.addValueExtractor(commandContext -> ResourceArgument.getResource(commandContext, argumentName, Registries.ENTITY_TYPE)); + }); + + // Register Game Mode Argument + registry.register(GameType.class, GameModeArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, GameModeArgument.gameMode())); + handler.addValueExtractor(commandContext -> GameModeArgument.getGameMode(commandContext, argumentName)); + }); + + // Register BlockPos Argument + registry.register(BlockPos.class, BlockPosArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, BlockPosArgument.blockPos())); + handler.addValueExtractor(commandContext -> BlockPosArgument.getBlockPos(commandContext, argumentName)); + }); + + // Register Vec3 Argument + registry.register(Vec3.class, Vec3Arg.class, (annotation, handler) -> { + var argumentName = annotation.name().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.name(); + handler.addNode(Commands.argument(argumentName, Vec3Argument.vec3(annotation.value() == Vec3Arg.Type.CENTER))); + handler.addValueExtractor(commandContext -> Vec3Argument.getVec3(commandContext, argumentName)); + }); + + // Register Rotation Argument + registry.register(Coordinates.class, RotationArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, RotationArgument.rotation())); + handler.addValueExtractor(commandContext -> RotationArgument.getRotation(commandContext, argumentName)); + }); + + // Register Dimension Argument + registry.register(ServerLevel.class, DimensionArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, DimensionArgument.dimension())); + handler.addValueExtractor(commandContext -> DimensionArgument.getDimension(commandContext, argumentName)); + }); + + // Register Tag Argument + registry.register(CompoundTag.class, CompoundTagArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, CompoundTagArgument.compoundTag())); + handler.addValueExtractor(commandContext -> CompoundTagArgument.getCompoundTag(commandContext, argumentName)); + }); + registry.register(Tag.class, NbtTagArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, NbtTagArgument.nbtTag())); + handler.addValueExtractor(commandContext -> NbtTagArgument.getNbtTag(commandContext, argumentName)); + }); + + // Register ResourceLocation Argument + registry.register(ResourceLocation.class, ResourceLocationArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, ResourceLocationArgument.id())); + handler.addValueExtractor(commandContext -> ResourceLocationArgument.getId(commandContext, argumentName)); + }); + + // Register Holder.Reference Argument + registry.register(Holder.Reference.class, AttributeArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, ResourceArgument.resource(buildContext, Registries.ATTRIBUTE))); + handler.addValueExtractor(commandContext -> ResourceArgument.getAttribute(commandContext, argumentName)); + }); + registry.register(Holder.Reference.class, DamageArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, ResourceArgument.resource(buildContext, Registries.DAMAGE_TYPE))); + handler.addValueExtractor(commandContext -> ResourceArgument.getResource(commandContext, argumentName, Registries.DAMAGE_TYPE)); + }); + registry.register(Holder.Reference.class, EnchantmentArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, ResourceArgument.resource(buildContext, Registries.ENCHANTMENT))); + handler.addValueExtractor(commandContext -> ResourceArgument.getEnchantment(commandContext, argumentName)); + }); + registry.register(Holder.Reference.class, MobEffectArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, ResourceArgument.resource(buildContext, Registries.MOB_EFFECT))); + handler.addValueExtractor(commandContext -> ResourceArgument.getMobEffect(commandContext, argumentName)); + }); + registry.register(Holder.Reference.class, StructureArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, ResourceArgument.resource(buildContext, Registries.STRUCTURE))); + handler.addValueExtractor(commandContext -> ResourceArgument.getStructure(commandContext, argumentName)); + }); + registry.register(Holder.Reference.class, FeatureArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, ResourceArgument.resource(buildContext, Registries.CONFIGURED_FEATURE))); + handler.addValueExtractor(commandContext -> ResourceArgument.getConfiguredFeature(commandContext, argumentName)); + }); + + // Register Item Argument + registry.register(ItemInput.class, ItemArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, ItemArgument.item(buildContext))); + handler.addValueExtractor(commandContext -> ItemArgument.getItem(commandContext, argumentName)); + }); + + // Register Block Argument + registry.register(BlockInput.class, BlockArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, BlockStateArgument.block(buildContext))); + handler.addValueExtractor(commandContext -> BlockStateArgument.getBlock(commandContext, argumentName)); + }); + + // Register Particle Argument + registry.register(ParticleOptions.class, ParticleArg.class, (annotation, handler) -> { + var argumentName = annotation.value().isBlank() ? handler.getAutoGeneratedArgumentName() : annotation.value(); + handler.addNode(Commands.argument(argumentName, ParticleArgument.particle(buildContext))); + handler.addValueExtractor(commandContext -> ParticleArgument.getParticle(commandContext, argumentName)); + }); }); - CommandRegistrationEvent.EVENT.register((commandDispatcher, commandBuildContext, commandSelection) -> { + CommandRegistrationEvent.EVENT.register((dispatcher, buildContext, commandSelection) -> { var registry = new CommandArgumentRegistry(); // Register Argument Annotations - CommandArgumentRegistrationEvent.EVENT.invoker().register(registry); + CommandArgumentRegistrationEvent.EVENT.invoker().register(registry, dispatcher, buildContext); // Register Commands CommandAnnotationHandler.COMMANDS.forEach(commandNode -> { try { var command = commandNode.build(registry); - command.forEach(commandDispatcher::register); + command.forEach(dispatcher::register); // Register Permissions commandNode.getPermissions().forEach(PlatformCommandUtils::registerPermission); diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/PlatformCommandUtils.java b/command-common/src/main/java/io/github/manasmods/manascore/command/PlatformCommandUtils.java index 94dd2841..5d7d2d9b 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/PlatformCommandUtils.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/PlatformCommandUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/Command.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/Command.java index 167999bd..f775db48 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/Command.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/Command.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/CommandArgumentRegistrationEvent.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/CommandArgumentRegistrationEvent.java index 89a840c7..5f5334d1 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/CommandArgumentRegistrationEvent.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/CommandArgumentRegistrationEvent.java @@ -1,16 +1,19 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ package io.github.manasmods.manascore.command.api; +import com.mojang.brigadier.CommandDispatcher; import dev.architectury.event.Event; import dev.architectury.event.EventFactory; import io.github.manasmods.manascore.command.internal.CommandArgumentRegistry; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; public interface CommandArgumentRegistrationEvent { Event EVENT = EventFactory.createLoop(); - void register(CommandArgumentRegistry registry); + void register(CommandArgumentRegistry registry, CommandDispatcher commandDispatcher, CommandBuildContext commandBuildContext); } diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/CommandRegistry.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/CommandRegistry.java index 0417374a..ed7b9a8c 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/CommandRegistry.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/CommandRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/Execute.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/Execute.java index e0b6ff18..cda5c6b2 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/Execute.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/Execute.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/Permission.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/Permission.java index b5a529f3..4ba2443b 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/Permission.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/Permission.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/BlockArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/BlockArg.java new file mode 100644 index 00000000..4879984c --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/BlockArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface BlockArg { + /** + * Argument Name in the Command + */ + String value() default "block"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/CompoundTagArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/CompoundTagArg.java new file mode 100644 index 00000000..2af59d1e --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/CompoundTagArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface CompoundTagArg { + /** + * Argument Name in the Command + */ + String value() default ""; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/DimensionArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/DimensionArg.java new file mode 100644 index 00000000..b74e0958 --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/DimensionArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface DimensionArg { + /** + * Argument Name in the Command + */ + String value() default "dimension"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/EntityArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/EntityArg.java new file mode 100644 index 00000000..59a5d01c --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/EntityArg.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface EntityArg { + /** + * Argument Name in the Command + */ + String name() default "entity"; + + /** + * Type of the Argument + */ + Type value() default Type.ENTITY; + + enum Type { + /** + * Single Entity + */ + ENTITY, + /** + * Collection of Entities + */ + ENTITIES, + /** + * Single Player + */ + PLAYER, + /** + * Collection of Players + */ + PLAYERS + } +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/EntityTypeArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/EntityTypeArg.java new file mode 100644 index 00000000..3edb0d5c --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/EntityTypeArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface EntityTypeArg { + /** + * Argument Name in the Command + */ + String value() default "entity_type"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/GameModeArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/GameModeArg.java new file mode 100644 index 00000000..c857a168 --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/GameModeArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface GameModeArg { + /** + * Argument Name in the Command + */ + String value() default "mode"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Literal.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/ItemArg.java similarity index 69% rename from command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Literal.java rename to command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/ItemArg.java index 806a19d1..16244581 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Literal.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/ItemArg.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -12,6 +12,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) -public @interface Literal { - String[] value(); -} +public @interface ItemArg { + /** + * Argument Name in the Command + */ + String value() default "item"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Uuid.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/NbtTagArg.java similarity index 86% rename from command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Uuid.java rename to command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/NbtTagArg.java index 02fceb43..48936f11 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Uuid.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/NbtTagArg.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -12,7 +12,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) -public @interface Uuid { +public @interface NbtTagArg { /** * Argument Name in the Command */ diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/ParticleArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/ParticleArg.java new file mode 100644 index 00000000..10340073 --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/ParticleArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface ParticleArg { + /** + * Argument Name in the Command + */ + String value() default "particle"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/ResourceLocationArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/ResourceLocationArg.java new file mode 100644 index 00000000..5f24803d --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/ResourceLocationArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface ResourceLocationArg { + /** + * Argument Name in the Command + */ + String value() default ""; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Sender.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/SenderArg.java similarity index 91% rename from command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Sender.java rename to command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/SenderArg.java index 6dce5511..9e620995 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Sender.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/SenderArg.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -23,6 +23,6 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) -public @interface Sender { +public @interface SenderArg { } diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Bool.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/UuidArg.java similarity index 87% rename from command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Bool.java rename to command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/UuidArg.java index 9bb8fbe9..601cc481 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Bool.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/UuidArg.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -12,7 +12,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) -public @interface Bool { +public @interface UuidArg { /** * Argument Name in the Command */ diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/coordinate/BlockPosArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/coordinate/BlockPosArg.java new file mode 100644 index 00000000..847c5f05 --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/coordinate/BlockPosArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter.coordinate; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface BlockPosArg { + /** + * Argument Name in the Command + */ + String value() default "pos"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/coordinate/RotationArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/coordinate/RotationArg.java new file mode 100644 index 00000000..eff2c77c --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/coordinate/RotationArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter.coordinate; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface RotationArg { + /** + * Argument Name in the Command + */ + String value() default "rotation"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/coordinate/Vec3Arg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/coordinate/Vec3Arg.java new file mode 100644 index 00000000..fb9bfea2 --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/coordinate/Vec3Arg.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter.coordinate; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface Vec3Arg { + /** + * Argument Name in the Command + */ + String name() default ""; + + /** + * Argument Name in the Command + */ + Type value() default Type.NORMAL; + + enum Type { + NORMAL, + CENTER + } +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/BooleanArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/BooleanArg.java new file mode 100644 index 00000000..adfff605 --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/BooleanArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter.primitive; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface BooleanArg { + /** + * Argument Name in the Command + */ + String value() default ""; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/DoubleNumber.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/DoubleArg.java similarity index 80% rename from command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/DoubleNumber.java rename to command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/DoubleArg.java index 995d7dc7..1746e84a 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/DoubleNumber.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/DoubleArg.java @@ -1,9 +1,9 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ -package io.github.manasmods.manascore.command.api.parameter; +package io.github.manasmods.manascore.command.api.parameter.primitive; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -12,7 +12,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) -public @interface DoubleNumber { +public @interface DoubleArg { /** * Argument Name in the Command */ diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Enum.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/EnumArg.java similarity index 70% rename from command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Enum.java rename to command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/EnumArg.java index 248e5e3d..a2808656 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Enum.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/EnumArg.java @@ -1,9 +1,9 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ -package io.github.manasmods.manascore.command.api.parameter; +package io.github.manasmods.manascore.command.api.parameter.primitive; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -12,6 +12,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) -public @interface Enum { +public @interface EnumArg { Class value(); } diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/FloatNumber.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/FloatArg.java similarity index 80% rename from command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/FloatNumber.java rename to command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/FloatArg.java index f86e627f..c52f4eb9 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/FloatNumber.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/FloatArg.java @@ -1,9 +1,9 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ -package io.github.manasmods.manascore.command.api.parameter; +package io.github.manasmods.manascore.command.api.parameter.primitive; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -12,7 +12,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) -public @interface FloatNumber { +public @interface FloatArg { /** * Argument Name in the Command */ diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/IntNumber.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/IntegerArg.java similarity index 80% rename from command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/IntNumber.java rename to command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/IntegerArg.java index b630bdeb..7d02ea2c 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/IntNumber.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/IntegerArg.java @@ -1,9 +1,9 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ -package io.github.manasmods.manascore.command.api.parameter; +package io.github.manasmods.manascore.command.api.parameter.primitive; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -12,7 +12,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) -public @interface IntNumber { +public @interface IntegerArg { /** * Argument Name in the Command */ diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/LiteralArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/LiteralArg.java new file mode 100644 index 00000000..3279658c --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/LiteralArg.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter.primitive; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface LiteralArg { + String[] value(); +} diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/LongNumber.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/LongArg.java similarity index 80% rename from command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/LongNumber.java rename to command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/LongArg.java index 25c21c5f..8fb45b44 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/LongNumber.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/LongArg.java @@ -1,9 +1,9 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ -package io.github.manasmods.manascore.command.api.parameter; +package io.github.manasmods.manascore.command.api.parameter.primitive; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -12,7 +12,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) -public @interface LongNumber { +public @interface LongArg { /** * Argument Name in the Command */ diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Text.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/TextArg.java similarity index 81% rename from command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Text.java rename to command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/TextArg.java index 69eee56d..a894e397 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/Text.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/primitive/TextArg.java @@ -1,9 +1,9 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ -package io.github.manasmods.manascore.command.api.parameter; +package io.github.manasmods.manascore.command.api.parameter.primitive; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -12,7 +12,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) -public @interface Text { +public @interface TextArg { enum Type { /** * Single Word Argument @@ -25,7 +25,7 @@ enum Type { /** * Greedy String Argument */ - GREEDY_STRING; + GREEDY_STRING } /** diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/AttributeArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/AttributeArg.java new file mode 100644 index 00000000..a81fd160 --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/AttributeArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter.resource; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface AttributeArg { + /** + * Argument Name in the Command + */ + String value() default "attribute"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/DamageArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/DamageArg.java new file mode 100644 index 00000000..82c00b0f --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/DamageArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter.resource; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface DamageArg { + /** + * Argument Name in the Command + */ + String value() default "damage_type"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/EnchantmentArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/EnchantmentArg.java new file mode 100644 index 00000000..02d79902 --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/EnchantmentArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter.resource; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface EnchantmentArg { + /** + * Argument Name in the Command + */ + String value() default "enchantment"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/FeatureArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/FeatureArg.java new file mode 100644 index 00000000..40aa06f1 --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/FeatureArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter.resource; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface FeatureArg { + /** + * Argument Name in the Command + */ + String value() default "feature"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/MobEffectArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/MobEffectArg.java new file mode 100644 index 00000000..1b25234f --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/MobEffectArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter.resource; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface MobEffectArg { + /** + * Argument Name in the Command + */ + String value() default "effect"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/StructureArg.java b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/StructureArg.java new file mode 100644 index 00000000..fcd4abc7 --- /dev/null +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/api/parameter/resource/StructureArg.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.command.api.parameter.resource; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface StructureArg { + /** + * Argument Name in the Command + */ + String value() default "structure"; +} \ No newline at end of file diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandArgumentRegistry.java b/command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandArgumentRegistry.java index 6c10b64f..a8df5156 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandArgumentRegistry.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandArgumentRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -10,7 +10,7 @@ import com.google.common.collect.Tables; import com.mojang.brigadier.builder.ArgumentBuilder; import io.github.manasmods.manascore.command.CommandAnnotationHandler; -import io.github.manasmods.manascore.command.api.parameter.Enum; +import io.github.manasmods.manascore.command.api.parameter.primitive.EnumArg; import lombok.RequiredArgsConstructor; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; @@ -40,7 +40,7 @@ public void register(Class

argumentType, Class a * @param enumClass The enum class to register. */ public void registerEnum(Class enumClass) { - register(enumClass, Enum.class, (annotation, handler) -> { + register(enumClass, EnumArg.class, (annotation, handler) -> { for (var constant : enumClass.getEnumConstants()) { handler.addNode(Commands.literal(constant.name())); handler.addValueExtractorToLastNode(context -> constant); diff --git a/command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandCreationContext.java b/command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandCreationContext.java index a60ffa73..1545a6f0 100644 --- a/command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandCreationContext.java +++ b/command-common/src/main/java/io/github/manasmods/manascore/command/internal/CommandCreationContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/command-fabric/build.gradle b/command-fabric/build.gradle index 2e78f4be..a2f83759 100644 --- a/command-fabric/build.gradle +++ b/command-fabric/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/ManasCoreCommandFabric.java b/command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/ManasCoreCommandFabric.java index dbb252af..756f4cff 100644 --- a/command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/ManasCoreCommandFabric.java +++ b/command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/ManasCoreCommandFabric.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/PlatformCommandUtilsImpl.java b/command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/PlatformCommandUtilsImpl.java index 2ecfb1ac..83fc165d 100644 --- a/command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/PlatformCommandUtilsImpl.java +++ b/command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/PlatformCommandUtilsImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/integrations/FabricPermissionsApiIntegration.java b/command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/integrations/FabricPermissionsApiIntegration.java index 0587551a..272ac321 100644 --- a/command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/integrations/FabricPermissionsApiIntegration.java +++ b/command-fabric/src/main/java/io/github/manasmods/manascore/command/fabric/integrations/FabricPermissionsApiIntegration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -12,5 +12,5 @@ import java.util.function.BiFunction; public class FabricPermissionsApiIntegration { - public static BiFunction PERMISSION_CHECK = (commandSourceStack, permission) -> Permissions.check(commandSourceStack, permission.value()); + public static BiFunction PERMISSION_CHECK = (commandSourceStack, permission) -> Permissions.check(commandSourceStack, permission.value(), permission.permissionLevel().getLevel()); } diff --git a/command-fabric/src/main/resources/assets/manascore/icon.png b/command-fabric/src/main/resources/assets/manascore/icon.png index c9a7b039..a38ae1d1 100644 Binary files a/command-fabric/src/main/resources/assets/manascore/icon.png and b/command-fabric/src/main/resources/assets/manascore/icon.png differ diff --git a/command-fabric/src/main/resources/assets/manascore/logo.png b/command-fabric/src/main/resources/assets/manascore/logo.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/command-fabric/src/main/resources/assets/manascore/logo.png differ diff --git a/command-neoforge/build.gradle b/command-neoforge/build.gradle index c47e20da..53f70f48 100644 --- a/command-neoforge/build.gradle +++ b/command-neoforge/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/command-neoforge/gradle.properties b/command-neoforge/gradle.properties index 9d43d620..a1b12e49 100644 --- a/command-neoforge/gradle.properties +++ b/command-neoforge/gradle.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2024. ManasMods +# Copyright (c) 2025. ManasMods # GNU General Public License 3 # diff --git a/command-neoforge/src/main/java/io/github/manasmods/manascore/command/neoforge/ManasCoreCommandNeoForge.java b/command-neoforge/src/main/java/io/github/manasmods/manascore/command/neoforge/ManasCoreCommandNeoForge.java index 1cab9665..122ab614 100644 --- a/command-neoforge/src/main/java/io/github/manasmods/manascore/command/neoforge/ManasCoreCommandNeoForge.java +++ b/command-neoforge/src/main/java/io/github/manasmods/manascore/command/neoforge/ManasCoreCommandNeoForge.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/command-neoforge/src/main/java/io/github/manasmods/manascore/command/neoforge/PlatformCommandUtilsImpl.java b/command-neoforge/src/main/java/io/github/manasmods/manascore/command/neoforge/PlatformCommandUtilsImpl.java index 2cedb7a7..5dc54035 100644 --- a/command-neoforge/src/main/java/io/github/manasmods/manascore/command/neoforge/PlatformCommandUtilsImpl.java +++ b/command-neoforge/src/main/java/io/github/manasmods/manascore/command/neoforge/PlatformCommandUtilsImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -20,6 +20,7 @@ public class PlatformCommandUtilsImpl { public static final Map> PERMISSIONS = new HashMap<>(); public static boolean hasPermission(CommandSourceStack commandSourceStack, Permission permission) { + if (commandSourceStack.hasPermission(permission.permissionLevel().getLevel())) return true; if (!commandSourceStack.isPlayer()) return true; return PermissionAPI.getPermission(Objects.requireNonNull(commandSourceStack.getPlayer()), PERMISSIONS.get(permission.value())); } diff --git a/config-common/build.gradle b/config-common/build.gradle new file mode 100644 index 00000000..5b0dd08a --- /dev/null +++ b/config-common/build.gradle @@ -0,0 +1,9 @@ +loom { + accessWidenerPath = file('src/main/resources/manascore_config.accesswidener') +} + +dependencies { + implementation "com.electronwill.night-config:core:3.8.1" + implementation "com.electronwill.night-config:toml:3.8.1" + implementation(project(path: ":network-common", configuration: 'namedElements')) { transitive false } +} \ No newline at end of file diff --git a/config-common/src/main/java/io/github/manasmods/manascore/config/ConfigRegistry.java b/config-common/src/main/java/io/github/manasmods/manascore/config/ConfigRegistry.java new file mode 100644 index 00000000..7dae3c76 --- /dev/null +++ b/config-common/src/main/java/io/github/manasmods/manascore/config/ConfigRegistry.java @@ -0,0 +1,100 @@ +package io.github.manasmods.manascore.config; + +import com.electronwill.nightconfig.toml.TomlWriter; +import io.github.manasmods.manascore.config.api.ManasConfig; +import io.github.manasmods.manascore.config.api.SyncToClient; + +import java.util.HashMap; +import java.util.Map; + +/** + * Handles the registration and management of all {@link ManasConfig} instances. + */ +public class ConfigRegistry { + private static final Map, ManasConfig> CONFIGS = new HashMap<>(); + + /** + * Retrieves a registered config instance by class type. + *

+ * @param configClass The config class. + * @return The instance of the requested config, or null if not registered. + */ + public static T getConfig(Class configClass) { + return configClass.cast(CONFIGS.get(configClass)); + } + + /** + * Registers a new configuration and loads it. + *

+ * @param configInstance The config instance to register. + */ + public static void registerConfig(ManasConfig configInstance) { + CONFIGS.put(configInstance.getClass(), configInstance); + configInstance.load(); + } + + /** + * Reloads all registered configurations from disk. + */ + public static void loadConfigSyncData() { + for (ManasConfig config : CONFIGS.values()) { + config.load(); + } + } + + /** + * Saves all registered configurations to disk. + */ + public static void saveAllConfigs() { + for (ManasConfig config : CONFIGS.values()) { + config.save(); + } + } + + /** + * Loads config data from a synced client-server map. + * Only applies to configs annotated with {@link SyncToClient}. + *

+ * @param map The config data received from the server. + */ + public static void loadConfigSyncData(Map map) { + if (map == null || map.isEmpty()) return; + CONFIGS.forEach((clazz, config) -> { + if (!map.containsKey(clazz.getSimpleName())) return; + if (!clazz.isAnnotationPresent(SyncToClient.class)) return; + config.loadFromString(map.get(clazz.getSimpleName())); + }); + } + + /** + * Serializes all syncable configs into a map for server-to-client transmission. + *

+ * @return A map containing serialized TOML configs. + */ + public static Map getConfigSyncData() { + Map configData = new HashMap<>(); + CONFIGS.forEach((clazz, config) -> { + if (!clazz.isAnnotationPresent(SyncToClient.class)) return; + config.load(); + configData.put(clazz.getSimpleName(), (new TomlWriter()).writeToString(config.getConfig())); + }); + return configData; + } + + /** + * Serializes a specific config for syncing. + *

+ * @param configClass The class of the config to sync. + * @return A map containing the serialized TOML data for the requested config. + */ + public static Map getConfigSyncData(Class configClass) { + Map configData = new HashMap<>(); + if (!CONFIGS.containsKey(configClass)) return configData; + if (!configClass.isAnnotationPresent(SyncToClient.class)) return configData; + + ManasConfig config = CONFIGS.get(configClass); + config.load(); + configData.put(configClass.getSimpleName(), (new TomlWriter()).writeToString(config.getConfig())); + return configData; + } +} diff --git a/config-common/src/main/java/io/github/manasmods/manascore/config/ManasCoreConfig.java b/config-common/src/main/java/io/github/manasmods/manascore/config/ManasCoreConfig.java new file mode 100644 index 00000000..be2c37b8 --- /dev/null +++ b/config-common/src/main/java/io/github/manasmods/manascore/config/ManasCoreConfig.java @@ -0,0 +1,16 @@ +package io.github.manasmods.manascore.config; + +import com.electronwill.nightconfig.core.Config; +import dev.architectury.event.events.common.PlayerEvent; +import io.github.manasmods.manascore.config.imp.network.ManasConfigNetwork; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ManasCoreConfig { + public static final Logger LOG = LoggerFactory.getLogger("ManasCore - Config"); + public static void init() { + Config.setInsertionOrderPreserved(true); + ManasConfigNetwork.init(); + PlayerEvent.PLAYER_JOIN.register(ManasConfigNetwork::syncToClient); + } +} diff --git a/config-common/src/main/java/io/github/manasmods/manascore/config/api/Comment.java b/config-common/src/main/java/io/github/manasmods/manascore/config/api/Comment.java new file mode 100644 index 00000000..2e525710 --- /dev/null +++ b/config-common/src/main/java/io/github/manasmods/manascore/config/api/Comment.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.config.api; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation to add comments to fields inside a {@link ManasConfig}. + * These comments will be included in the generated TOML config files. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Comment { + String value(); // The comment text to be added to the config file. +} \ No newline at end of file diff --git a/config-common/src/main/java/io/github/manasmods/manascore/config/api/ManasConfig.java b/config-common/src/main/java/io/github/manasmods/manascore/config/api/ManasConfig.java new file mode 100644 index 00000000..a793b524 --- /dev/null +++ b/config-common/src/main/java/io/github/manasmods/manascore/config/api/ManasConfig.java @@ -0,0 +1,138 @@ +package io.github.manasmods.manascore.config.api; + +import com.electronwill.nightconfig.core.CommentedConfig; +import com.electronwill.nightconfig.core.Config; +import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import com.electronwill.nightconfig.toml.TomlParser; +import io.github.manasmods.manascore.config.ManasCoreConfig; +import lombok.Getter; +import net.minecraft.resources.ResourceLocation; + +import java.io.File; +import java.io.StringReader; +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Base class for defining a config file in TOML format. + * Extend this class to create custom mod configs. + *

+ * Supports automatic loading, saving, and syncing of fields. + * Reads {@link Comment} annotations to add comments in TOML files. + */ +public abstract class ManasConfig { + @Getter + private CommentedFileConfig config; + + /** + * Define the file name for the config (without extension). + */ + public abstract String getFileName(); + + /** + * Returns the path where the config file is stored. + */ + public Path getConfigPath() { + return Paths.get("config", this.getFileName() + ".toml"); + } + + /** + * Loads the config file from disk or creates a new one if it doesn't exist. + */ + public void load() { + Path path = getConfigPath(); + File file = path.toFile(); + if (!file.exists()) { + try { + file.getParentFile().mkdirs(); + file.createNewFile(); + } catch (Exception e) { + ManasCoreConfig.LOG.error("Error creating new config file at " + path + ": " + e.getMessage(), e); + } + } + + config = CommentedFileConfig.builder(path).sync().build(); + config.load(); + applyToFields(); + save(); + } + + /** + * Loads config values from a TOML-formatted string. + */ + public void loadFromString(String tomlData) { + if (tomlData == null || tomlData.isEmpty()) return; + TomlParser parser = new TomlParser(); + try { + Config parsedConfig = parser.parse(new StringReader(tomlData)); + config.putAll(parsedConfig); + applyToFields(); + } catch (Exception e) { + ManasCoreConfig.LOG.error("Error parsing TOML data: " + e.getMessage(), e); + } + } + + /** + * Saves the config values from the class fields into the file. + */ + public void save() { + saveFromFields(); + config.save(); + } + + /** + * Reads values from the TOML config and applies them to fields. + */ + private void applyToFields() { + for (Field field : this.getClass().getDeclaredFields()) { + try { + field.setAccessible(true); + Object value = config.getOrElse(field.getName(), field.get(this)); + if (value instanceof Config configSub && field.get(this) instanceof ManasSubConfig sub) { + sub.applySubConfigFields(configSub); + field.set(this, sub); + } else if (value != null) field.set(this, ManasConfig.getFieldValueConverted(field, value)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to apply configuration for field: " + field.getName(), e); + } + } + } + + /** + * Saves field values into the config file. + */ + private void saveFromFields() { + for (Field field : this.getClass().getDeclaredFields()) { + try { + field.setAccessible(true); + Object value = field.get(this); + if (value instanceof ManasSubConfig sub) { + CommentedConfig subConfig = config.get(field.getName()); + if (subConfig == null) subConfig = config.createSubConfig(); + sub.saveSubConfigFields(sub, subConfig); + config.set(field.getName(), subConfig); + } else { + if (field.getType() == ResourceLocation.class && value instanceof ResourceLocation rl) value = rl.toString(); + if (value != null) config.set(field.getName(), value); + } + Comment comment = field.getAnnotation(Comment.class); + if (comment != null) config.setComment(field.getName(), comment.value()); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to save configuration for field: " + field.getName(), e); + } + } + } + + /** + * Converts field values to the correct type when reading from the config. + */ + public static Object getFieldValueConverted(Field field, Object value) { + if (field.getType() == float.class && value instanceof Double d) return d.floatValue(); + if (field.getType() == ResourceLocation.class && value instanceof String s) return ResourceLocation.tryParse(s); + if (field.getType().isEnum() && value instanceof String s) return Enum.valueOf((Class) field.getType(), s); + return value; + } +} + + diff --git a/config-common/src/main/java/io/github/manasmods/manascore/config/api/ManasSubConfig.java b/config-common/src/main/java/io/github/manasmods/manascore/config/api/ManasSubConfig.java new file mode 100644 index 00000000..4f186e38 --- /dev/null +++ b/config-common/src/main/java/io/github/manasmods/manascore/config/api/ManasSubConfig.java @@ -0,0 +1,58 @@ +package io.github.manasmods.manascore.config.api; + +import com.electronwill.nightconfig.core.CommentedConfig; +import com.electronwill.nightconfig.core.Config; +import net.minecraft.resources.ResourceLocation; + +import java.lang.reflect.Field; + +/** + * Base class for sub-config sections inside a {@link ManasConfig}. + * Supports nested configurations. + */ +public abstract class ManasSubConfig { + + /** + * Reads config values and applies them to the subconfig fields. + */ + public void applySubConfigFields(Config sourceConfig) { + for (Field field : this.getClass().getDeclaredFields()) { + try { + field.setAccessible(true); + Object value = sourceConfig.get(field.getName()); + if (value instanceof Config configSub && field.get(this) instanceof ManasSubConfig sub) { + sub.applySubConfigFields(configSub); + field.set(this, sub); + } else if (value != null) field.set(this, ManasConfig.getFieldValueConverted(field, value)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to apply configuration for field: " + field.getName(), e); + } + } + } + + /** + * Saves field values into the subconfig. + */ + public void saveSubConfigFields(ManasSubConfig subConfigInstance, CommentedConfig config) { + for (Field field : this.getClass().getDeclaredFields()) { + try { + field.setAccessible(true); + Object value = field.get(subConfigInstance); + if (value instanceof ManasSubConfig sub) { + CommentedConfig subConfig = config.get(field.getName()); + if (subConfig == null) subConfig = config.createSubConfig(); + sub.saveSubConfigFields(sub, subConfig); + config.set(field.getName(), subConfig); + } else { + if (field.getType() == ResourceLocation.class && value instanceof ResourceLocation rl) value = rl.toString(); + if (value != null) config.set(field.getName(), value); + } + Comment comment = field.getAnnotation(Comment.class); + if (comment != null) config.setComment(field.getName(), comment.value()); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to save configuration for field: " + field.getName(), e); + } + } + } +} + diff --git a/config-common/src/main/java/io/github/manasmods/manascore/config/api/SyncToClient.java b/config-common/src/main/java/io/github/manasmods/manascore/config/api/SyncToClient.java new file mode 100644 index 00000000..c6f2acd3 --- /dev/null +++ b/config-common/src/main/java/io/github/manasmods/manascore/config/api/SyncToClient.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.config.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a {@link ManasConfig} class as syncable from server to client. + * When a player joins a multiplayer server, configs with this annotation will be automatically synced with the server's config. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface SyncToClient { +} \ No newline at end of file diff --git a/config-common/src/main/java/io/github/manasmods/manascore/config/imp/network/ManasConfigNetwork.java b/config-common/src/main/java/io/github/manasmods/manascore/config/imp/network/ManasConfigNetwork.java new file mode 100644 index 00000000..6f7b5404 --- /dev/null +++ b/config-common/src/main/java/io/github/manasmods/manascore/config/imp/network/ManasConfigNetwork.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.config.imp.network; + +import dev.architectury.networking.NetworkManager; +import dev.architectury.utils.GameInstance; +import io.github.manasmods.manascore.config.ConfigRegistry; +import io.github.manasmods.manascore.config.api.ManasConfig; +import io.github.manasmods.manascore.config.imp.network.s2c.SyncConfigToClientPayload; +import io.github.manasmods.manascore.network.api.util.NetworkUtils; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; + +public class ManasConfigNetwork { + public static void init() { + NetworkUtils.registerS2CPayload(SyncConfigToClientPayload.TYPE, + SyncConfigToClientPayload.STREAM_CODEC, SyncConfigToClientPayload::handle); + } + + /** + * Syncs all registered server-side configurations to a specific player. + *

+ * @param player The player to send the config data to. + */ + public static void syncToClient(ServerPlayer player) { + NetworkManager.sendToPlayer(player, new SyncConfigToClientPayload(ConfigRegistry.getConfigSyncData())); + } + + /** + * Syncs a specific server-side configuration to a specific player. + *

+ * @param player The player to send the config data to. + * @param config The specific configuration class to sync. + */ + public static void syncToClient(ServerPlayer player, Class config) { + NetworkManager.sendToPlayer(player, new SyncConfigToClientPayload(ConfigRegistry.getConfigSyncData(config))); + } + + /** + * Syncs all registered server-side configurations to all connected clients. + *

+ * If called when no players are online, this method will do nothing. + */ + public static void syncToClients() { + MinecraftServer server = GameInstance.getServer(); + if (server == null) throw new RuntimeException("Failed to find the Server."); + NetworkManager.sendToPlayers(server.getPlayerList().getPlayers(), new SyncConfigToClientPayload(ConfigRegistry.getConfigSyncData())); + } + + /** + * Syncs a specific server-side configuration to all connected clients. + *

+ * @param config The specific configuration class to sync. + */ + public static void syncToClients(Class config) { + MinecraftServer server = GameInstance.getServer(); + if (server == null) throw new RuntimeException("Failed to find the Server."); + NetworkManager.sendToPlayers(server.getPlayerList().getPlayers(), new SyncConfigToClientPayload(ConfigRegistry.getConfigSyncData(config))); + } +} diff --git a/config-common/src/main/java/io/github/manasmods/manascore/config/imp/network/s2c/SyncConfigToClientPayload.java b/config-common/src/main/java/io/github/manasmods/manascore/config/imp/network/s2c/SyncConfigToClientPayload.java new file mode 100644 index 00000000..4b1c94f6 --- /dev/null +++ b/config-common/src/main/java/io/github/manasmods/manascore/config/imp/network/s2c/SyncConfigToClientPayload.java @@ -0,0 +1,43 @@ +package io.github.manasmods.manascore.config.imp.network.s2c; + + +import dev.architectury.networking.NetworkManager; +import dev.architectury.utils.Env; +import io.github.manasmods.manascore.config.ConfigRegistry; +import io.github.manasmods.manascore.config.ModuleConstants; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +/** + * Handles the syncing of configuration data from the server to the client. + * This is triggered when a player joins a multiplayer server. + */ +public record SyncConfigToClientPayload( + Map configData +) implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "sync_config_to_client")); + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(SyncConfigToClientPayload::encode, SyncConfigToClientPayload::new); + + public SyncConfigToClientPayload(FriendlyByteBuf buf) { + this(buf.readMap(FriendlyByteBuf::readUtf, FriendlyByteBuf::readUtf)); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeMap(this.configData, FriendlyByteBuf::writeUtf, FriendlyByteBuf::writeUtf); + } + + public void handle(NetworkManager.PacketContext context) { + if (context.getEnvironment() != Env.CLIENT) return; + context.queue(() -> ConfigRegistry.loadConfigSyncData(this.configData)); + } + + @NotNull + public CustomPacketPayload.Type type() { + return TYPE; + } +} diff --git a/config-common/src/main/resources/architectury.common.json b/config-common/src/main/resources/architectury.common.json new file mode 100644 index 00000000..9c43f078 --- /dev/null +++ b/config-common/src/main/resources/architectury.common.json @@ -0,0 +1,3 @@ +{ + "accessWidener": "manascore_config.accesswidener" +} \ No newline at end of file diff --git a/config-common/src/main/resources/manascore_config.accesswidener b/config-common/src/main/resources/manascore_config.accesswidener new file mode 100644 index 00000000..9ab21739 --- /dev/null +++ b/config-common/src/main/resources/manascore_config.accesswidener @@ -0,0 +1 @@ +accessWidener v2 named diff --git a/config-common/src/main/resources/manascore_config.mixins.json b/config-common/src/main/resources/manascore_config.mixins.json new file mode 100644 index 00000000..b88e49d0 --- /dev/null +++ b/config-common/src/main/resources/manascore_config.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "io.github.manasmods.manascore.config.mixin", + "compatibilityLevel": "JAVA_21", + "minVersion": "0.8", + "client": [ + ], + "mixins": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/config-fabric/build.gradle b/config-fabric/build.gradle new file mode 100644 index 00000000..d42c4829 --- /dev/null +++ b/config-fabric/build.gradle @@ -0,0 +1,9 @@ +loom { + accessWidenerPath = project(":config-common").loom.accessWidenerPath +} + +dependencies { + include(implementation("com.electronwill.night-config:core:3.8.1")) + include(implementation("com.electronwill.night-config:toml:3.8.1")) + implementation project(path: ":network-common", configuration: 'transformProductionFabric') +} \ No newline at end of file diff --git a/config-fabric/src/main/java/io/github/manasmods/manascore/config/fabric/ManasCoreConfigFabric.java b/config-fabric/src/main/java/io/github/manasmods/manascore/config/fabric/ManasCoreConfigFabric.java new file mode 100644 index 00000000..d051e9f0 --- /dev/null +++ b/config-fabric/src/main/java/io/github/manasmods/manascore/config/fabric/ManasCoreConfigFabric.java @@ -0,0 +1,11 @@ +package io.github.manasmods.manascore.config.fabric; + +import io.github.manasmods.manascore.config.ManasCoreConfig; +import net.fabricmc.api.ModInitializer; + +public class ManasCoreConfigFabric implements ModInitializer { + @Override + public void onInitialize() { + ManasCoreConfig.init(); + } +} \ No newline at end of file diff --git a/config-fabric/src/main/resources/assets/manascore/icon.png b/config-fabric/src/main/resources/assets/manascore/icon.png new file mode 100644 index 00000000..a38ae1d1 Binary files /dev/null and b/config-fabric/src/main/resources/assets/manascore/icon.png differ diff --git a/config-fabric/src/main/resources/assets/manascore/logo.png b/config-fabric/src/main/resources/assets/manascore/logo.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/config-fabric/src/main/resources/assets/manascore/logo.png differ diff --git a/config-fabric/src/main/resources/fabric.mod.json b/config-fabric/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..fd33c5f1 --- /dev/null +++ b/config-fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,34 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "Utility and Core Library for Manas Mods", + "authors": [ + "ManasMods" + ], + "contact": { + "homepage": "" + }, + "license": "${license}", + "icon": "assets/manascore/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "io.github.manasmods.manascore.config.fabric.ManasCoreConfigFabric" + ], + "client": [] + }, + "mixins": [ + "${mod_id}.mixins.json" + ], + "depends": { + "fabricloader": ">=${fabric_loader_version}", + "minecraft": "~${minecraft_version}", + "java": ">=21", + "architectury": ">=${architectury_version}", + "fabric-api": "*" + }, + "suggests": { + } +} diff --git a/config-neoforge/build.gradle b/config-neoforge/build.gradle new file mode 100644 index 00000000..26030a15 --- /dev/null +++ b/config-neoforge/build.gradle @@ -0,0 +1,11 @@ +loom { + accessWidenerPath = project(":config-common").loom.accessWidenerPath +} + +dependencies { + implementation project(path: ":network-common", configuration: 'transformProductionNeoForge') +} + +remapJar { + atAccessWideners.add("${project.mod_id}.accesswidener") +} \ No newline at end of file diff --git a/config-neoforge/gradle.properties b/config-neoforge/gradle.properties new file mode 100644 index 00000000..2914393d --- /dev/null +++ b/config-neoforge/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge \ No newline at end of file diff --git a/config-neoforge/src/main/java/io/github/manasmods/manascore/config/neoforge/ManasCoreConfigNeoForge.java b/config-neoforge/src/main/java/io/github/manasmods/manascore/config/neoforge/ManasCoreConfigNeoForge.java new file mode 100644 index 00000000..69c31c5d --- /dev/null +++ b/config-neoforge/src/main/java/io/github/manasmods/manascore/config/neoforge/ManasCoreConfigNeoForge.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.config.neoforge; + +import io.github.manasmods.manascore.config.ManasCoreConfig; +import io.github.manasmods.manascore.config.ModuleConstants; +import net.neoforged.fml.common.Mod; + +@Mod(ModuleConstants.MOD_ID) +public class ManasCoreConfigNeoForge { + public ManasCoreConfigNeoForge() { + ManasCoreConfig.init(); + } +} diff --git a/config-neoforge/src/main/resources/META-INF/neoforge.mods.toml b/config-neoforge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..dfbcf412 --- /dev/null +++ b/config-neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,38 @@ +modLoader = "javafml" +loaderVersion = "[2,)" +issueTrackerURL = "" +license = "${license}" + +[[mods]] +modId = "${mod_id}" +version = "${version}" +displayName = "${mod_name}" +authors = "ManasMods" +description = ''' +Utility and Core Library for Manas Mods +''' +logoFile = "icon.png" + +[[dependencies.${mod_id}]] + modId = "neoforge" + type = "required" + versionRange = "[21,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "minecraft" + type = "required" + versionRange = "[${minecraft_version},)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "architectury" + type = "required" + versionRange = "[${architectury_version},)" + ordering = "AFTER" + side = "BOTH" + +[[mixins]] +config = "${mod_id}.mixins.json" diff --git a/config-neoforge/src/main/resources/icon.png b/config-neoforge/src/main/resources/icon.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/config-neoforge/src/main/resources/icon.png differ diff --git a/gradle.properties b/gradle.properties index aff23f1a..8cc44a89 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ org.gradle.jvmargs=-Xmx6G org.gradle.parallel=true # Mod properties -mod_version=4.0.0.0 +mod_version=3.0.3.1 maven_group=io.github.manasmods archives_name=manascore mod_display_name=ManasCore @@ -18,7 +18,7 @@ minecraft_version=1.21.1 architectury_api_version=13.0.8 fabric_loader_version=0.16.3 fabric_api_version=0.103.0+1.21.1 -neoforge_version=21.1.28 +neoforge_version=21.1.216 fabric_permissions_api_version=0.3.1 fabric_luckperms_version=v5.4.140-fabric \ No newline at end of file diff --git a/inventory-common/build.gradle b/inventory-common/build.gradle index 6feb0168..a183be83 100644 --- a/inventory-common/build.gradle +++ b/inventory-common/build.gradle @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + loom { accessWidenerPath = file('src/main/resources/manascore_inventory.accesswidener') } \ No newline at end of file diff --git a/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/VanillaInventoryTab.java b/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/VanillaInventoryTab.java index ceb6cc4d..53793188 100644 --- a/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/VanillaInventoryTab.java +++ b/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/VanillaInventoryTab.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/api/AbstractInventoryTab.java b/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/api/AbstractInventoryTab.java index 657fb9cd..96810c9c 100644 --- a/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/api/AbstractInventoryTab.java +++ b/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/api/AbstractInventoryTab.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/client/ManasCoreInventoryClient.java b/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/client/ManasCoreInventoryClient.java index c840bac9..bf5f0c2e 100644 --- a/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/client/ManasCoreInventoryClient.java +++ b/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/client/ManasCoreInventoryClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/client/widget/InventoryTabSwitcherWidget.java b/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/client/widget/InventoryTabSwitcherWidget.java index e86958bd..48cb5a85 100644 --- a/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/client/widget/InventoryTabSwitcherWidget.java +++ b/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/client/widget/InventoryTabSwitcherWidget.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/mixin/MixinAbstractContainerScreen.java b/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/mixin/MixinAbstractContainerScreen.java index aff0caa1..43bf02c8 100644 --- a/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/mixin/MixinAbstractContainerScreen.java +++ b/inventory-common/src/main/java/io/github/manasmods/manascore/inventory/mixin/MixinAbstractContainerScreen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/inventory-fabric/build.gradle b/inventory-fabric/build.gradle index 1589225d..27375aaa 100644 --- a/inventory-fabric/build.gradle +++ b/inventory-fabric/build.gradle @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + loom { accessWidenerPath = project(":inventory-common").loom.accessWidenerPath } diff --git a/inventory-fabric/src/main/java/io/github/manasmods/manascore/inventory/fabric/ManasCoreInventoryFabric.java b/inventory-fabric/src/main/java/io/github/manasmods/manascore/inventory/fabric/ManasCoreInventoryFabric.java index a795e1f3..1e292edd 100644 --- a/inventory-fabric/src/main/java/io/github/manasmods/manascore/inventory/fabric/ManasCoreInventoryFabric.java +++ b/inventory-fabric/src/main/java/io/github/manasmods/manascore/inventory/fabric/ManasCoreInventoryFabric.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + package io.github.manasmods.manascore.inventory.fabric; import io.github.manasmods.manascore.inventory.ManasCoreInventory; diff --git a/inventory-fabric/src/main/resources/assets/manascore/icon.png b/inventory-fabric/src/main/resources/assets/manascore/icon.png index c9a7b039..a38ae1d1 100644 Binary files a/inventory-fabric/src/main/resources/assets/manascore/icon.png and b/inventory-fabric/src/main/resources/assets/manascore/icon.png differ diff --git a/inventory-fabric/src/main/resources/assets/manascore/logo.png b/inventory-fabric/src/main/resources/assets/manascore/logo.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/inventory-fabric/src/main/resources/assets/manascore/logo.png differ diff --git a/inventory-neoforge/build.gradle b/inventory-neoforge/build.gradle index 003383f2..1a907914 100644 --- a/inventory-neoforge/build.gradle +++ b/inventory-neoforge/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/keybind-common/build.gradle b/keybind-common/build.gradle new file mode 100644 index 00000000..22349cfc --- /dev/null +++ b/keybind-common/build.gradle @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +loom { + accessWidenerPath = file('src/main/resources/manascore_keybind.accesswidener') +} \ No newline at end of file diff --git a/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/ManasCoreKeybind.java b/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/ManasCoreKeybind.java new file mode 100644 index 00000000..d340df20 --- /dev/null +++ b/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/ManasCoreKeybind.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.keybind; + +import dev.architectury.platform.Platform; +import dev.architectury.utils.Env; + +public class ManasCoreKeybind { + public static void init() { + if (Platform.getEnvironment() == Env.CLIENT) { + ManasCoreKeybindClient.init(); + } + } +} diff --git a/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/ManasCoreKeybindClient.java b/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/ManasCoreKeybindClient.java new file mode 100644 index 00000000..d6f03576 --- /dev/null +++ b/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/ManasCoreKeybindClient.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.keybind; + +import dev.architectury.event.events.client.ClientLifecycleEvent; +import io.github.manasmods.manascore.keybind.api.KeybindingManager; + +public class ManasCoreKeybindClient { + public static void init() { + ClientLifecycleEvent.CLIENT_SETUP.register(instance -> KeybindingManager.init()); + } +} diff --git a/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/KeybindingCategory.java b/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/KeybindingCategory.java new file mode 100644 index 00000000..297a8ab4 --- /dev/null +++ b/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/KeybindingCategory.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.keybind.api; + +import io.github.manasmods.manascore.keybind.ModuleConstants; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(staticName = "of") +public class KeybindingCategory { + private final String name; + + public String getCategoryString() { + return String.format("%s.category.%s", ModuleConstants.MOD_ID, this.name); + } +} \ No newline at end of file diff --git a/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/KeybindingManager.java b/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/KeybindingManager.java new file mode 100644 index 00000000..26f455d8 --- /dev/null +++ b/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/KeybindingManager.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.keybind.api; + +import dev.architectury.event.events.client.ClientTickEvent; +import dev.architectury.registry.client.keymappings.KeyMappingRegistry; + +import java.util.ArrayList; + +public class KeybindingManager { + private static final ArrayList keybindings = new ArrayList<>(); + + public static void register(T... keybindings) { + for (final ManasKeybinding keybinding : keybindings) { + register(keybinding); + } + } + + public static T register(T keybinding) { + keybindings.add(keybinding); + KeyMappingRegistry.register(keybinding); + return keybinding; + } + + public static void init() { + // Execute Actions on press + ClientTickEvent.CLIENT_POST.register(instance -> { + for (final ManasKeybinding keybinding : keybindings) { + if (keybinding.isDown()) { + keybinding.getAction().onPress(); + } else if (keybinding.getRelease() != null) { + keybinding.getRelease().run(); + } + } + }); + } +} diff --git a/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/ManasKeybinding.java b/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/ManasKeybinding.java new file mode 100644 index 00000000..765e82d1 --- /dev/null +++ b/keybind-common/src/main/java/io/github/manasmods/manascore/keybind/api/ManasKeybinding.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.keybind.api; + +import com.mojang.blaze3d.platform.InputConstants; +import lombok.Getter; +import net.minecraft.client.KeyMapping; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; + +public class ManasKeybinding extends KeyMapping { + private static final HashMap PRESSED_KEYBINDINGS = new HashMap<>(); + @Getter + private final KeyBindingAction action; + @Getter + @Nullable + private final Runnable release; + + /** + * Creates a Keybinding which handles the given action automatically. + * + * @param langKey Translation String + * @param defaultKey Default Key + * @param category Category + * @param action Action when pressed + */ + public ManasKeybinding(String langKey, int defaultKey, KeybindingCategory category, KeyBindingAction action) { + this(langKey, InputConstants.Type.KEYSYM.getOrCreate(defaultKey), category, action, null); + } + + /** + * Creates a Keybinding which handles the given action automatically. + * + * @param langKey Translation String + * @param defaultKey Default Key + * @param category Category + * @param action Action when pressed + */ + public ManasKeybinding(String langKey, InputConstants.Key defaultKey, KeybindingCategory category, KeyBindingAction action) { + this(langKey, defaultKey, category, action, null); + } + + /** + * Creates a Keybinding without a default key. + * + * @param langKey Translation String + * @param category Category + * @param action Action when pressed. + */ + public ManasKeybinding(String langKey, KeybindingCategory category, KeyBindingAction action) { + this(langKey, InputConstants.UNKNOWN.getValue(), category, action, null); + } + + /** + * Creates a Keybinding without a default key. + * + * @param langKey Translation String + * @param category Category + * @param action Action when pressed. + * @param release Action when released + */ + public ManasKeybinding(String langKey, KeybindingCategory category, KeyBindingAction action, @Nullable KeyBindingRelease release) { + this(langKey, InputConstants.UNKNOWN.getValue(), category, action, release); + } + + /** + * Creates a Keybinding which handles the given action automatically. + * + * @param langKey Translation String + * @param defaultKey Default Key + * @param category Category + * @param action Action when pressed + * @param release Action when released + */ + public ManasKeybinding(String langKey, int defaultKey, KeybindingCategory category, KeyBindingAction action, @Nullable KeyBindingRelease release) { + this(langKey, InputConstants.Type.KEYSYM.getOrCreate(defaultKey), category, action, release); + } + + /** + * Creates a Keybinding which handles the given action automatically. + * + * @param langKey Translation String + * @param defaultKey Default Key + * @param category Category + * @param action Action when pressed + * @param release Action when released + */ + public ManasKeybinding(String langKey, InputConstants.Key defaultKey, KeybindingCategory category, KeyBindingAction action, @Nullable KeyBindingRelease release) { + super(langKey, defaultKey.getType(), defaultKey.getValue(), category.getCategoryString()); + if (release == null) { + this.action = action; + this.release = null; + } else { + this.action = () -> { + if (!PRESSED_KEYBINDINGS.containsKey(this)) { + PRESSED_KEYBINDINGS.put(this, System.currentTimeMillis()); + action.onPress(); + } + }; + this.release = () -> { + if (PRESSED_KEYBINDINGS.containsKey(this)) { + long start = PRESSED_KEYBINDINGS.remove(this); + long end = System.currentTimeMillis(); + release.onRelease(end - start); + } + }; + } + } + + + @FunctionalInterface + public interface KeyBindingAction { + void onPress(); + } + + @FunctionalInterface + public interface KeyBindingRelease { + /** + * @param duration in milliseconds + */ + void onRelease(long duration); + } +} diff --git a/keybind-common/src/main/resources/architectury.common.json b/keybind-common/src/main/resources/architectury.common.json new file mode 100644 index 00000000..4d3111aa --- /dev/null +++ b/keybind-common/src/main/resources/architectury.common.json @@ -0,0 +1,5 @@ +{ + "injected_interfaces": { + }, + "accessWidener": "manascore_keybind.accesswidener" +} diff --git a/keybind-common/src/main/resources/manascore_keybind.accesswidener b/keybind-common/src/main/resources/manascore_keybind.accesswidener new file mode 100644 index 00000000..2464d6d5 --- /dev/null +++ b/keybind-common/src/main/resources/manascore_keybind.accesswidener @@ -0,0 +1 @@ +accessWidener v2 named \ No newline at end of file diff --git a/keybind-common/src/main/resources/manascore_keybind.mixins.json b/keybind-common/src/main/resources/manascore_keybind.mixins.json new file mode 100644 index 00000000..0509d2da --- /dev/null +++ b/keybind-common/src/main/resources/manascore_keybind.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "package": "io.github.manasmods.manascore.keybind.mixin", + "compatibilityLevel": "JAVA_21", + "minVersion": "0.8", + "client": [ + ], + "mixins": [], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/keybind-fabric/build.gradle b/keybind-fabric/build.gradle new file mode 100644 index 00000000..eb7d0873 --- /dev/null +++ b/keybind-fabric/build.gradle @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +loom { + accessWidenerPath = project(":keybind-common").loom.accessWidenerPath +} diff --git a/keybind-fabric/src/main/java/io/github/manasmods/manascore/keybind/fabric/ManasCoreKeybindFabric.java b/keybind-fabric/src/main/java/io/github/manasmods/manascore/keybind/fabric/ManasCoreKeybindFabric.java new file mode 100644 index 00000000..26e631c6 --- /dev/null +++ b/keybind-fabric/src/main/java/io/github/manasmods/manascore/keybind/fabric/ManasCoreKeybindFabric.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.keybind.fabric; + +import io.github.manasmods.manascore.keybind.ManasCoreKeybind; +import net.fabricmc.api.ModInitializer; +public class ManasCoreKeybindFabric implements ModInitializer { + @Override + public void onInitialize() { + ManasCoreKeybind.init(); + } +} \ No newline at end of file diff --git a/keybind-fabric/src/main/resources/assets/manascore/icon.png b/keybind-fabric/src/main/resources/assets/manascore/icon.png new file mode 100644 index 00000000..a38ae1d1 Binary files /dev/null and b/keybind-fabric/src/main/resources/assets/manascore/icon.png differ diff --git a/keybind-fabric/src/main/resources/assets/manascore/logo.png b/keybind-fabric/src/main/resources/assets/manascore/logo.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/keybind-fabric/src/main/resources/assets/manascore/logo.png differ diff --git a/keybind-fabric/src/main/resources/fabric.mod.json b/keybind-fabric/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..b1dd59ce --- /dev/null +++ b/keybind-fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,34 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "Utility and Core Library for Manas Mods", + "authors": [ + "ManasMods" + ], + "contact": { + "homepage": "" + }, + "license": "${license}", + "icon": "assets/manascore/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "io.github.manasmods.manascore.keybind.fabric.ManasCoreKeybindFabric" + ], + "client": [] + }, + "mixins": [ + "${mod_id}.mixins.json" + ], + "depends": { + "fabricloader": ">=${fabric_loader_version}", + "minecraft": "~${minecraft_version}", + "java": ">=21", + "architectury": ">=${architectury_version}", + "fabric-api": "*" + }, + "suggests": { + } +} diff --git a/keybind-neoforge/build.gradle b/keybind-neoforge/build.gradle new file mode 100644 index 00000000..aea97f02 --- /dev/null +++ b/keybind-neoforge/build.gradle @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +loom { + accessWidenerPath = project(":keybind-common").loom.accessWidenerPath +} + +remapJar { + atAccessWideners.add("${project.mod_id}.accesswidener") +} diff --git a/keybind-neoforge/gradle.properties b/keybind-neoforge/gradle.properties new file mode 100644 index 00000000..7da18ea6 --- /dev/null +++ b/keybind-neoforge/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge diff --git a/keybind-neoforge/src/main/java/io/github/manasmods/manascore/keybind/neoforge/ManasCoreKeybindNeoForge.java b/keybind-neoforge/src/main/java/io/github/manasmods/manascore/keybind/neoforge/ManasCoreKeybindNeoForge.java new file mode 100644 index 00000000..4ea4f336 --- /dev/null +++ b/keybind-neoforge/src/main/java/io/github/manasmods/manascore/keybind/neoforge/ManasCoreKeybindNeoForge.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.keybind.neoforge; + +import io.github.manasmods.manascore.keybind.ManasCoreKeybind; +import io.github.manasmods.manascore.keybind.ModuleConstants; +import net.neoforged.fml.common.Mod; + +@Mod(ModuleConstants.MOD_ID) +public final class ManasCoreKeybindNeoForge { + public ManasCoreKeybindNeoForge() { + ManasCoreKeybind.init(); + } +} diff --git a/keybind-neoforge/src/main/resources/META-INF/neoforge.mods.toml b/keybind-neoforge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..dfbcf412 --- /dev/null +++ b/keybind-neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,38 @@ +modLoader = "javafml" +loaderVersion = "[2,)" +issueTrackerURL = "" +license = "${license}" + +[[mods]] +modId = "${mod_id}" +version = "${version}" +displayName = "${mod_name}" +authors = "ManasMods" +description = ''' +Utility and Core Library for Manas Mods +''' +logoFile = "icon.png" + +[[dependencies.${mod_id}]] + modId = "neoforge" + type = "required" + versionRange = "[21,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "minecraft" + type = "required" + versionRange = "[${minecraft_version},)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "architectury" + type = "required" + versionRange = "[${architectury_version},)" + ordering = "AFTER" + side = "BOTH" + +[[mixins]] +config = "${mod_id}.mixins.json" diff --git a/keybind-neoforge/src/main/resources/icon.png b/keybind-neoforge/src/main/resources/icon.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/keybind-neoforge/src/main/resources/icon.png differ diff --git a/network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java b/network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java new file mode 100644 index 00000000..f08c24bc --- /dev/null +++ b/network-common/src/main/java/io/github/manasmods/manascore/network/api/util/Changeable.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.network.api.util; + +import lombok.Synchronized; +import org.jetbrains.annotations.Nullable; + +public class Changeable { + @Nullable + private final T original; + private T value; + + protected Changeable(@Nullable T value) { + this.original = value; + this.value = value; + } + + public static Changeable of(@Nullable T value) { + return new Changeable<>(value); + } + + @Synchronized + @Nullable + public T get() { + return value; + } + + @Synchronized + public void set(@Nullable T value) { + this.value = value; + } + + public boolean isPresent() { + return value != null; + } + + public boolean isEmpty() { + return value == null; + } + + public boolean hasChanged() { + if (original == null) return value != null; + return !original.equals(value); + } +} diff --git a/network-fabric/src/main/resources/assets/manascore/icon.png b/network-fabric/src/main/resources/assets/manascore/icon.png index c9a7b039..a38ae1d1 100644 Binary files a/network-fabric/src/main/resources/assets/manascore/icon.png and b/network-fabric/src/main/resources/assets/manascore/icon.png differ diff --git a/network-fabric/src/main/resources/assets/manascore/logo.png b/network-fabric/src/main/resources/assets/manascore/logo.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/network-fabric/src/main/resources/assets/manascore/logo.png differ diff --git a/network-neoforge/build.gradle b/network-neoforge/build.gradle index d769e0ba..15d305be 100644 --- a/network-neoforge/build.gradle +++ b/network-neoforge/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/race-common/build.gradle b/race-common/build.gradle new file mode 100644 index 00000000..c4e80712 --- /dev/null +++ b/race-common/build.gradle @@ -0,0 +1,9 @@ +loom { + accessWidenerPath = file('src/main/resources/manascore_race.accesswidener') +} + +dependencies { + implementation(project(path: ":skill-common", configuration: 'namedElements')) { transitive false } + implementation(project(path: ":network-common", configuration: 'namedElements')) { transitive false } + implementation(project(path: ":storage-common", configuration: 'namedElements')) { transitive false } +} \ No newline at end of file diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/ManasCoreRace.java b/race-common/src/main/java/io/github/manasmods/manascore/race/ManasCoreRace.java new file mode 100644 index 00000000..827e3ad6 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/ManasCoreRace.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race; + +import io.github.manasmods.manascore.race.api.RaceEvents; +import io.github.manasmods.manascore.race.impl.RaceRegistry; +import io.github.manasmods.manascore.race.impl.RaceStorage; +import io.github.manasmods.manascore.race.impl.network.ManasRaceNetwork; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class ManasCoreRace { + public static final Logger LOG = LoggerFactory.getLogger("ManasCore - Race"); + + public static void init() { + RaceRegistry.init(); + RaceStorage.init(); + ManasRaceNetwork.init(); + RaceEvents.POST_INIT.invoker().run(); + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRace.java b/race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRace.java new file mode 100644 index 00000000..1b30c9b3 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRace.java @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.api; + +import com.mojang.datafixers.util.Pair; +import dev.architectury.event.Event; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.race.impl.RaceStorage; +import io.github.manasmods.manascore.skill.api.ManasSkill; +import io.github.manasmods.manascore.skill.api.SkillAPI; +import io.github.manasmods.manascore.skill.api.Skills; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.minecraft.ChatFormatting; +import net.minecraft.core.Holder; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.AttributeMap; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * This is the Registry Object for Races. + * Extend from this Class to create your own Races. + *

+ * To add functionality to the {@link ManasRace}, you need to implement a listener interface. + * Those interfaces allow you to invoke a Method when an {@link Event} happens. + * The Method will only be invoked for an {@link Entity} that learned the {@link ManasRace}. + *

+ * Races will be set by calling the {@link RaceStorage#setRace} method. + * You can simply use {@link RaceAPI#getRaceFrom(LivingEntity)} to get the {@link RaceStorage} of an {@link Entity}. + *

+ * You're also allowed to override the {@link ManasRace#createDefaultInstance()} method to create your own implementation + * of a {@link ManasRaceInstance}. This is required if you want to attach additional data to the {@link ManasRace} + * (for example to allow to disable a race or make the race gain exp on usage). + */ +public class ManasRace { + @Getter + private final Difficulty difficulty; + @Getter + protected final Map, AttributeTemplate> attributeModifiers = new Object2ObjectOpenHashMap<>(); + public ManasRace(Difficulty difficulty) { + this.difficulty = difficulty; + } + + /** + * Used to create a {@link ManasRaceInstance} of this Race. + *

+ * Override this Method to use your extended version of {@link ManasRaceInstance} + */ + public ManasRaceInstance createDefaultInstance() { + return new ManasRaceInstance(this); + } + + /** + * Used to get the {@link ResourceLocation} id of this race. + */ + @Nullable + public ResourceLocation getRegistryName() { + return RaceAPI.getRaceRegistry().getId(this); + } + + /** + * Used to get the {@link MutableComponent} name of this race for translation. + */ + @Nullable + public MutableComponent getName() { + final ResourceLocation id = getRegistryName(); + if (id == null) return null; + return Component.translatable(String.format("%s.race.%s", id.getNamespace(), id.getPath().replace('/', '.'))); + } + + /** + * Used to get the {@link ResourceLocation} of this race's icon texture. + */ + @Nullable + public ResourceLocation getRaceIcon() { + ResourceLocation id = this.getRegistryName(); + if (id == null) return null; + return ResourceLocation.fromNamespaceAndPath(id.getNamespace(), "icons/races/" + id.getPath()); + } + + /** + * Used to get the {@link MutableComponent} description of this race for translation. + */ + public MutableComponent getRaceDescription() { + ResourceLocation id = this.getRegistryName(); + if (id == null) return Component.empty(); + return Component.translatable(String.format("%s.race.%s.description", id.getNamespace(), id.getPath().replace('/', '.'))); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ManasRace race = (ManasRace) o; + return Objects.equals(getRegistryName(), race.getRegistryName()); + } + + /** + * Determine if the {@link ManasRaceInstance} of this Race's ability can be used by {@link LivingEntity}. + * + * @param instance Affected {@link ManasRaceInstance} + * @param user Affected {@link LivingEntity} using this Race's ability. + * @return false will stop {@link LivingEntity} from using any ability of the race. + */ + public boolean canActivateAbility(ManasRaceInstance instance, LivingEntity user) { + return true; + } + + /** + * @return the maximum number of ticks that this race's ability can be held down with the activation button. + *

+ */ + public int getMaxHeldTime(ManasRaceInstance instance, LivingEntity entity) { + return 72000; + } + + /** + * Determine if this race's {@link ManasRace#onTick} can be executed. + * + * @param instance Affected {@link ManasRaceInstance} + * @param entity Affected {@link LivingEntity} being this Race. + * @return false if this race cannot tick. + */ + public boolean canTick(ManasRaceInstance instance, LivingEntity entity) { + return false; + } + + /** + * Adds an attribute modifier to this skillId. This method can be called for more than one attribute. + * The attributes are applied to an entity when the race is set. + *

+ */ + public void addAttributeModifier(Holder holder, ResourceLocation resourceLocation, double amount, AttributeModifier.Operation operation) { + this.attributeModifiers.put(holder, new AttributeTemplate(resourceLocation, amount, operation)); + } + + /** + * Applies the attribute modifiers of this race on the {@link LivingEntity} when set. + * + * @param entity Affected {@link LivingEntity} being this Race. + * @param instance Affected {@link ManasRaceInstance} + */ + public void addAttributeModifiers(ManasRaceInstance instance, LivingEntity entity) { + if (this.attributeModifiers.isEmpty()) return; + + AttributeMap attributeMap = entity.getAttributes(); + for (Map.Entry, AttributeTemplate> entry : this.attributeModifiers.entrySet()) { + AttributeInstance attributeInstance = attributeMap.getInstance(entry.getKey()); + + if (attributeInstance == null) continue; + attributeInstance.removeModifier(entry.getValue().id()); + attributeInstance.addPermanentModifier(entry.getValue().create()); + } + } + + /** + * Removes the attribute modifiers of this skillId from the {@link LivingEntity} when changing race. + * + * @param entity Affected {@link LivingEntity} being this Race. + */ + public void removeAttributeModifiers(ManasRaceInstance instance, LivingEntity entity) { + if (this.attributeModifiers.isEmpty()) return; + AttributeMap map = entity.getAttributes(); + List dirtyInstances = new ArrayList<>(); + + for (Map.Entry, AttributeTemplate> entry : this.attributeModifiers.entrySet()) { + AttributeInstance attributeInstance = map.getInstance(entry.getKey()); + if (attributeInstance == null) continue; + attributeInstance.removeModifier(entry.getValue().id()); + dirtyInstances.add(attributeInstance); + } + + if (!dirtyInstances.isEmpty() && entity instanceof ServerPlayer player) { + ClientboundUpdateAttributesPacket packet = new ClientboundUpdateAttributesPacket(player.getId(), dirtyInstances); + player.connection.send(packet); + } + } + + /** + * Called every tick of the {@link LivingEntity} owning this Race. + * + * @param instance Affected {@link ManasRaceInstance} + * @param living Affected {@link LivingEntity} being this Race. + */ + public void onTick(ManasRaceInstance instance, LivingEntity living) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} using this Race's ability. + * + * @param instance Affected {@link ManasRaceInstance} + * @param entity Affected {@link LivingEntity} using this Race's ability. + */ + public void onActivateAbility(ManasRaceInstance instance, LivingEntity entity) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} holds this Race's ability activation button. + * + * @param instance Affected {@link ManasRaceInstance} + * @param entity Affected {@link LivingEntity} using this Race's ability. + * @return true to continue ticking this Skill. + */ + public boolean onHeldAbility(ManasRaceInstance instance, LivingEntity entity, int heldTicks) { + // Override this method to add your own logic + return false; + } + + /** + * Called when the {@link LivingEntity} releases this Race's ability activation button after {@param heldTicks}. + * + * @param instance Affected {@link ManasRaceInstance} + * @param entity Affected {@link LivingEntity} using this Race's ability. + */ + public void onReleaseAbility(ManasRaceInstance instance, LivingEntity entity, int heldTicks) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} sets to this Race. + * + * @param instance Affected {@link ManasRaceInstance} + * @param living Affected {@link LivingEntity} sets to this Race. + */ + public void onRaceSet(ManasRaceInstance instance, LivingEntity living) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} being this Race gains an effect. + * + * @see ManasRaceInstance#onEffectAdded(LivingEntity, Entity, Changeable) + */ + public boolean onEffectAdded(ManasRaceInstance instance, LivingEntity entity, @Nullable Entity source, Changeable effect) { + // Override this method to add your own logic + return true; + } + + /** + * Called when the {@link LivingEntity} being this Race starts to be targeted by a mob. + * + * @see ManasRaceInstance#onBeingTargeted(Changeable, LivingEntity) + */ + public boolean onBeingTargeted(ManasRaceInstance instance, Changeable target, LivingEntity owner) { + // Override this method to add your own logic + return true; + } + + /** + * Called when the {@link LivingEntity} being this Race attack another {@link LivingEntity}, + * + * @see ManasRaceInstance#onAttackEntity(LivingEntity, LivingEntity, DamageSource, Changeable) + */ + public boolean onAttackEntity(ManasRaceInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable amount) { + // Override this method to add your own logic + return true; + } + + /** + * Called when the {@link LivingEntity} being this Race takes damage. + * + * @see ManasRaceInstance#onHurt(LivingEntity, DamageSource, Changeable) + */ + public boolean onHurt(ManasRaceInstance instance, LivingEntity owner, DamageSource source, Changeable amount) { + // Override this method to add your own logic + return true; + } + + /** + * Called when the {@link LivingEntity} being this Race dies. + * + * @see ManasRaceInstance#onDeath(LivingEntity, DamageSource) + */ + public boolean onDeath(ManasRaceInstance instance, LivingEntity owner, DamageSource source) { + // Override this method to add your own logic + return true; + } + + /** + * Called when the {@link ServerPlayer} being this Race respawns. + * + * @see ManasRaceInstance#onRespawn(ServerPlayer, boolean) + */ + public void onRespawn(ManasRaceInstance instance, ServerPlayer owner, boolean conqueredEnd) { + // Override this method to add your own logic + } + + /** + * Returns the dimension that {@link LivingEntity} respawns at as this Race. + * Decides whether if the game should spawn a 3x3 platform of {@link BlockState} when no valid spawn is found. + * + * @see ManasRaceInstance#getRespawnDimension(LivingEntity) + */ + public Pair, BlockState> getRespawnDimension(ManasRaceInstance instance, LivingEntity owner) { + return Pair.of(Level.OVERWORLD, Blocks.AIR.defaultBlockState()); + } + + /** + * Returns a list of all {@link ManasSkill} that {@link LivingEntity} gains on changing to this Race. + * + * @see ManasRaceInstance#getIntrinsicSkills(LivingEntity) + */ + public List getIntrinsicSkills(ManasRaceInstance instance, LivingEntity entity) { + return new ArrayList<>(); + } + + public boolean isIntrinsicSkill(ManasRaceInstance instance, LivingEntity entity, ManasSkill skill) { + return this.getIntrinsicSkills(instance, entity).contains(skill); + } + + /** + * Returns a list of all {@link ManasSkill} that {@link LivingEntity} gains on changing to this Race. + * + * @see ManasRaceInstance#getIntrinsicSkills(LivingEntity) + */ + public void learnIntrinsicSkills(ManasRaceInstance instance, LivingEntity entity) { + Skills storage = SkillAPI.getSkillsFrom(entity); + for (ManasSkill skill : instance.getIntrinsicSkills(entity)) { + if (storage.learnSkill(skill)) instance.addIntrinsicSkill(skill); + } + } + + /** + * Returns a list of all {@link ManasRace} that this Race can evolve into. + * + * @see ManasRaceInstance#getNextEvolutions(LivingEntity) + */ + public List getNextEvolutions(ManasRaceInstance instance, LivingEntity entity) { + return new ArrayList<>(); + } + + /** + * Returns a list of all {@link ManasRace} that evolve into this Race. + * + * @see ManasRaceInstance#getPreviousEvolutions(LivingEntity) + */ + public List getPreviousEvolutions(ManasRaceInstance instance, LivingEntity entity) { + return new ArrayList<>(); + } + + /** + * Returns the default {@link ManasRace} that this Race evolves into. + * + * @see ManasRaceInstance#getDefaultEvolution(LivingEntity) + */ + public @Nullable ManasRace getDefaultEvolution(ManasRaceInstance instance, LivingEntity entity) { + return null; + } + + /** + * Returns the float progress for this {@link ManasRace} to evolve into its evolution. + * Acceptable values: 0 - 1.0 + * + * @see ManasRaceInstance#getEvolutionProgress(LivingEntity, ManasRace) + */ + public float getEvolutionProgress(ManasRaceInstance instance, LivingEntity entity, ManasRace evolution) { + return 0; + } + + /** + * Called when the {@link LivingEntity} evolves this Race. + * + * @see ManasRaceInstance#onRaceEvolution(LivingEntity, ManasRaceInstance) + */ + public void onRaceEvolution(ManasRaceInstance instance, LivingEntity living, ManasRaceInstance evolution) { + // Override this method to add your own logic + } + + /** + * Attribute Template for easier base attribute modifier implementation. + */ + public static record AttributeTemplate(ResourceLocation id, double amount, AttributeModifier.Operation operation) { + public AttributeTemplate(ResourceLocation id, double amount, AttributeModifier.Operation operation) { + this.id = id; + this.amount = amount; + this.operation = operation; + } + + public AttributeModifier create() { + return new AttributeModifier(this.id, this.amount, this.operation); + } + + public ResourceLocation id() { + return this.id; + } + + public double amount() { + return this.amount; + } + + public AttributeModifier.Operation operation() { + return this.operation; + } + } + + @RequiredArgsConstructor + public enum Difficulty { + EASY(Component.translatable("manascore.race.difficulty.easy").withStyle(ChatFormatting.GREEN)), + INTERMEDIATE(Component.translatable("manascore.race.difficulty.intermediate").withStyle(style -> style.withColor(0xFFA500))), + HARD(Component.translatable("manascore.race.difficulty.hard").withStyle(ChatFormatting.RED)), + EXTREME(Component.translatable("manascore.race.difficulty.extreme").withStyle(ChatFormatting.DARK_RED)); + + private final MutableComponent name; + + public MutableComponent asText() { + return name; + } + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java b/race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java new file mode 100644 index 00000000..aa6b5ba0 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/api/ManasRaceInstance.java @@ -0,0 +1,497 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.api; + +import com.mojang.datafixers.util.Pair; +import dev.architectury.registry.registries.RegistrySupplier; +import io.github.manasmods.manascore.skill.api.ManasSkill; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.SkillAPI; +import io.github.manasmods.manascore.storage.api.Storage; +import io.github.manasmods.manascore.storage.impl.StorageManager; +import lombok.Getter; +import net.minecraft.ChatFormatting; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.tags.TagKey; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static io.github.manasmods.manascore.storage.ManasCoreStorage.LOG; + +public class ManasRaceInstance { + @Nullable + private CompoundTag tag = null; + private int cooldown = 0; + @Getter + private final List obtainedIntrinsicSkills = new ArrayList<>(); + @Getter + private boolean dirty = false; + protected final RegistrySupplier raceRegistryObject; + + protected ManasRaceInstance(ManasRace race) { + this.raceRegistryObject = RaceAPI.getRaceRegistry().delegate(RaceAPI.getRaceRegistry().getId(race)); + } + + /** + * Used to get the {@link ManasRace} type of this Instance. + */ + public ManasRace getRace() { + return raceRegistryObject.get(); + } + + public ResourceLocation getRaceId() { + return this.raceRegistryObject.getId(); + } + + /** + * Used to get the difficulty of this {@link ManasRace}. + */ + public ManasRace.Difficulty getDifficulty() { + return this.getRace().getDifficulty(); + } + + /** + * Used to create an exact copy of the current instance. + */ + public ManasRaceInstance copy() { + ManasRaceInstance clone = new ManasRaceInstance(getRace()); + clone.dirty = this.dirty; + if (this.tag != null) clone.tag = this.tag.copy(); + return clone; + } + + /** + * This method is used to ensure that all required information are stored. + *

+ * Override {@link ManasRaceInstance#serialize(CompoundTag)} to store your custom Data. + */ + public final CompoundTag toNBT() { + CompoundTag nbt = new CompoundTag(); + nbt.putString("race", this.getRaceId().toString()); + this.serialize(nbt); + return nbt; + } + + /** + * Can be used to save custom data. + * + * @param nbt Tag with data from {@link ManasRaceInstance#fromNBT(CompoundTag)} + */ + public CompoundTag serialize(CompoundTag nbt) { + if (this.tag != null) nbt.put("tag", this.tag.copy()); + nbt.putInt("cooldown", this.cooldown); + ListTag listTag = new ListTag(); + for (ManasSkill skill : this.obtainedIntrinsicSkills) { + if (skill.getRegistryName() == null) continue; + CompoundTag tag = new CompoundTag(); + tag.putString("skill", skill.getRegistryName().toString()); + listTag.add(tag); + } + nbt.put("intrinsicSkills", listTag); + return nbt; + } + + /** + * Can be used to load custom data. + */ + public void deserialize(CompoundTag tag) { + if (tag.contains("tag", 10)) this.tag = tag.getCompound("tag"); + this.cooldown = tag.getInt("cooldown"); + + this.obtainedIntrinsicSkills.clear(); + ListTag listTag = tag.getList("intrinsicSkills", Tag.TAG_COMPOUND); + listTag.forEach(t -> { + CompoundTag entryTag = (CompoundTag) t; + ManasSkill skill = SkillAPI.getSkillRegistry().get(ResourceLocation.tryParse(entryTag.getString("skill"))); + if (skill == null) return; + this.obtainedIntrinsicSkills.add(skill); + }); + } + + /** + * Can be used to load a {@link ManasRaceInstance} from a {@link CompoundTag}. + *

+ * The {@link CompoundTag} has to be created though {@link ManasRaceInstance#toNBT()} + */ + public static ManasRaceInstance fromNBT(CompoundTag tag) throws NullPointerException { + ResourceLocation location = ResourceLocation.tryParse(tag.getString("race")); + ManasRace race = RaceAPI.getRaceRegistry().get(location); + if (race == null) throw new NullPointerException("No race found for location: " + location); + ManasRaceInstance instance = race.createDefaultInstance(); + instance.deserialize(tag); + return instance; + } + + /** + * Marks the current instance as dirty. + */ + public void markDirty() { + this.dirty = true; + } + + /** + * This Method is invoked to indicate that a {@link ManasRaceInstance} has been synced with the clients. + *

+ * Do NOT use that method on our own! + */ + @ApiStatus.Internal + public void resetDirty() { + this.dirty = false; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ManasRaceInstance instance = (ManasRaceInstance) o; + return this.getRaceId().equals(instance.getRaceId()) && + raceRegistryObject.getRegistryKey().equals(instance.raceRegistryObject.getRegistryKey()); + } + + @Override + public int hashCode() { + return Objects.hash(this.getRaceId(), raceRegistryObject.getRegistryKey()); + } + + public boolean is(TagKey tag) { + return this.raceRegistryObject.is(tag); + } + + public MutableComponent getDisplayName() { + return this.getRace().getName(); + } + + public MutableComponent getChatDisplayName(boolean withDescription) { + Style style = Style.EMPTY.withColor(ChatFormatting.GRAY); + if (withDescription) { + MutableComponent hoverMessage = getDisplayName().append("\n"); + hoverMessage.append(this.getRace().getRaceDescription().withStyle(ChatFormatting.GRAY)); + style = style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverMessage)); + } + + MutableComponent component = Component.literal("[").append(getDisplayName()).append("]"); + return component.withStyle(style); + } + + /** + * Determine if this instance can be used by {@link LivingEntity}. + * + * @param user Affected {@link LivingEntity} + * @return false will stop {@link LivingEntity} from using any ability of the race. + */ + public boolean canActivateAbility(LivingEntity user) { + return this.getRace().canActivateAbility(this, user); + } + + /** + * @return the maximum number of ticks that this race's ability can be held down with the activation button. + *

+ */ + public int getMaxHeldTime(LivingEntity entity) { + return this.getRace().getMaxHeldTime(this, entity); + } + + /** + * Determine if this instance's {@link ManasRaceInstance#onTick} can be executed. + * + * @param entity Affected {@link LivingEntity} being this Race. + * @return false if this race cannot tick. + */ + public boolean canTick(LivingEntity entity) { + return this.getRace().canTick(this, entity); + } + + /** + * @return compound tag of this instance. + */ + @Nullable + public CompoundTag getTag() { + return this.tag; + } + + /** + * Used to add/create additional tags for this instance. + * + * @return compound tag of this instance or create if null. + */ + public CompoundTag getOrCreateTag() { + if (this.tag == null) { + this.setTag(new CompoundTag()); + } + return this.tag; + } + + /** + * Used to add/create additional tags for this instance. + * Set the tag of this instance. + */ + public void setTag(@Nullable CompoundTag tag) { + this.tag = tag; + markDirty(); + } + + /** + * @return if this race's ability is on cooldown. + */ + public boolean isOnCooldown() { + return this.cooldown > 0; + } + + /** + * @return the cooldown of this race's ability. + */ + public int getCooldown() { + return this.cooldown; + } + + /** + * Set the cooldown of this race's ability. + */ + public void setCooldown(int cooldown) { + this.cooldown = cooldown; + markDirty(); + } + + /** + * Set an intrinsic skill as obtained. + */ + public boolean addIntrinsicSkill(ManasSkill skill) { + return this.obtainedIntrinsicSkills.add(skill); + } + + /** + * Set an intrinsic skill as unobtained. + */ + public boolean removeIntrinsicSkill(ManasSkill skill) { + return this.obtainedIntrinsicSkills.remove(skill); + } + + /** + * Set all intrinsic skills as unobtained. + */ + public void clearsIntrinsicSkills() { + this.obtainedIntrinsicSkills.clear(); + } + + /** + * Applies the attribute modifiers of this instance on the {@link LivingEntity} when set. + * + * @param entity Affected {@link LivingEntity} being thisRace. + */ + public void addAttributeModifiers(LivingEntity entity) { + this.getRace().addAttributeModifiers(this, entity); + } + + /** + * Removes the attribute modifiers of this instance from the {@link LivingEntity} when changing race. + * + * @param entity Affected {@link LivingEntity} being this Race. + */ + public void removeAttributeModifiers(LivingEntity entity) { + this.getRace().removeAttributeModifiers(this, entity); + } + + /** + * Called every tick if this instance is set for {@link LivingEntity}. + * + * @param living Affected {@link LivingEntity} being this Race. + */ + public void onTick(LivingEntity living) { + this.getRace().onTick(this, living); + } + + /** + * Called when the {@link LivingEntity} uses this Race's ability. + * + * @param entity Affected {@link LivingEntity} being this Race. + */ + public void onActivateAbility(LivingEntity entity) { + this.getRace().onActivateAbility(this, entity); + } + + /** + * Called when the {@link LivingEntity} holds this Race's ability activation button. + * + * @param entity Affected {@link LivingEntity} being this Race. + * @return true to continue ticking this Skill. + */ + public boolean onHeldAbility(LivingEntity entity, int heldTicks) { + return this.getRace().onHeldAbility(this, entity, heldTicks); + } + + /** + * Called when the {@link LivingEntity} releases this Race's ability activation button after {@param heldTicks}. + * + * @param entity Affected {@link LivingEntity} being this Race. + */ + public void onReleaseAbility(LivingEntity entity, int heldTicks) { + this.getRace().onReleaseAbility(this, entity, heldTicks); + } + + /** + * Called when the {@link LivingEntity} sets to this Race. + * + * @param living Affected {@link LivingEntity} sets to this Race. + */ + public void onRaceSet(LivingEntity living) { + this.getRace().onRaceSet(this, living); + } + + /** + * Called when the {@link LivingEntity} owning this instance gains an effect. + * + * @param entity owning this instance. + */ + public boolean onEffectAdded(LivingEntity entity, @Nullable Entity source, Changeable instance) { + return this.getRace().onEffectAdded(this, entity, source, instance); + } + + /** + * Called when the {@link LivingEntity} owning this instance starts to be targeted by a mob. + * + * @return false will stop the mob from targeting the owner. + */ + public boolean onBeingTargeted(Changeable owner, LivingEntity mob) { + return this.getRace().onBeingTargeted(this, owner, mob); + } + + /** + * Called when the {@link LivingEntity} being this race hurts another {@link LivingEntity}. + *

+ * Gets executed before {@link ManasRaceInstance#onHurt} + * + * @return false will prevent the owner from dealing damage. + */ + public boolean onAttackEntity(LivingEntity owner, LivingEntity target, DamageSource source, Changeable amount) { + return this.getRace().onAttackEntity(this, owner, target, source, amount); + } + + /** + * Called when the {@link LivingEntity} being this Race takes damage. + *

+ * Gets executed after {@link ManasRaceInstance#onAttackEntity} + * + * @return false will prevent the owner from taking damage. + */ + public boolean onHurt(LivingEntity owner, DamageSource source, Changeable amount) { + return this.getRace().onHurt(this, owner, source, amount); + } + + /** + * Called when the {@link LivingEntity} being this Race dies. + * + * @return false will prevent the owner from dying. + */ + public boolean onDeath(LivingEntity owner, DamageSource source) { + return this.getRace().onDeath(this, owner, source); + } + + /** + * Called when the {@link ServerPlayer} being this Race respawns. + */ + public void onRespawn(ServerPlayer owner, boolean conqueredEnd) { + this.getRace().onRespawn(this, owner, conqueredEnd); + } + + /** + * Returns the dimension that {@link LivingEntity} respawns at as this Race. + * Decides whether if the game should spawn a 3x3 platform of {@link BlockState} when no valid spawn is found. + *

+ * @param player Affected {@link LivingEntity} being this race. + */ + public Pair, BlockState> getRespawnDimension(LivingEntity player) { + return this.getRace().getRespawnDimension(this, player); + } + + /** + * Returns a list of all {@link ManasSkill} that {@link LivingEntity} gains on changing to this Instance. + *

+ * @param entity Affected {@link LivingEntity} being this race. + */ + public List getIntrinsicSkills(LivingEntity entity) { + return this.getRace().getIntrinsicSkills(this, entity); + } + + public boolean isIntrinsicSkill(LivingEntity entity, ManasSkill skill) { + return this.getRace().isIntrinsicSkill(this, entity, skill); + } + + /** + * Returns a list of all {@link ManasSkill} that {@link LivingEntity} gains on changing to this Race. + *

+ * @param entity Affected {@link LivingEntity} being this race. + */ + public void learnIntrinsicSkills(LivingEntity entity) { + this.getRace().learnIntrinsicSkills(this, entity); + } + + /** + * Returns a list of all {@link ManasRace} that this Race can evolve into. + *

+ * @param entity Affected {@link LivingEntity} evolving this race. + */ + public List getNextEvolutions(LivingEntity entity) { + return this.getRace().getNextEvolutions(this, entity); + } + + /** + * Returns a list of all {@link ManasRace} that evolve into this Race. + *

+ * @param entity Affected {@link LivingEntity} being this race. + */ + public List getPreviousEvolutions(LivingEntity entity) { + return this.getRace().getPreviousEvolutions(this, entity); + } + + /** + * Returns the default {@link ManasRace} that this Race evolves into. + *

+ * @param entity Affected {@link LivingEntity} evolving this race. + */ + public @Nullable ManasRace getDefaultEvolution(LivingEntity entity) { + return this.getRace().getDefaultEvolution(this, entity); + } + + /** + * Returns the float progress for this {@link ManasRace} to evolve into its evolution. + * Acceptable values: 0 - 1.0 + *

+ * @param entity Affected {@link LivingEntity} evolving this race. + * @param evolution Affected {@link ManasRace} that this Race evolves into. + */ + public float getEvolutionProgress(LivingEntity entity, ManasRace evolution) { + return this.getRace().getEvolutionProgress(this, entity, evolution); + } + + /** + * Called when the {@link LivingEntity} evolves this Race. + *

+ * @param entity Affected {@link LivingEntity} evolving this Race. + * @param evolution Affected {@link ManasRaceInstance} that this Race evolves into. + */ + public void onRaceEvolution(LivingEntity entity, ManasRaceInstance evolution) { + this.getRace().onRaceEvolution(this, entity, evolution); + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java b/race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java new file mode 100644 index 00000000..4a5594c0 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceAPI.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.api; + +import dev.architectury.platform.Platform; +import dev.architectury.registry.registries.Registrar; +import dev.architectury.utils.Env; +import io.github.manasmods.manascore.race.impl.RaceRegistry; +import io.github.manasmods.manascore.race.impl.RaceStorage; +import io.github.manasmods.manascore.race.impl.network.InternalRacePacketActions; +import lombok.NonNull; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.LivingEntity; + +public class RaceAPI { + private RaceAPI() { + } + + /** + * This Method returns the {@link ManasRace} Registry. + * It can be used to load {@link ManasRace}s from the Registry. + */ + public static Registrar getRaceRegistry() { + return RaceRegistry.RACES; + } + + /** + * This Method returns the Registry Key of the {@link RaceRegistry}. + * It can be used to create {@link dev.architectury.registry.registries.DeferredRegister} instances + */ + public static ResourceKey> getRaceRegistryKey() { + return RaceRegistry.KEY; + } + + /** + * Can be used to load the {@link RaceStorage} from an {@link LivingEntity}. + */ + public static Races getRaceFrom(@NonNull LivingEntity entity) { + return entity.manasCore$getStorage(RaceStorage.getKey()); + } + + /** + * Send {@link InternalRacePacketActions#sendRaceAbilityActivationPacket} with a DistExecutor on client side. + * Used when player activates the Race Ability. + * + * @see InternalRacePacketActions#sendRaceAbilityActivationPacket + */ + public static void raceAbilityActivationPacket() { + if (Platform.getEnvironment() == Env.CLIENT) { + InternalRacePacketActions.sendRaceAbilityActivationPacket(); + } + } + + /** + * Send {@link InternalRacePacketActions#sendRaceAbilityReleasePacket} with a DistExecutor on client side. + * Used when player releases the Race Ability. + * + * @see InternalRacePacketActions#sendRaceAbilityReleasePacket + */ + public static void raceAbilityReleasePacket(int heldTick) { + if (Platform.getEnvironment() == Env.CLIENT) { + InternalRacePacketActions.sendRaceAbilityReleasePacket(heldTick); + } + } + + /** + * Send {@link InternalRacePacketActions#sendRaceEvolutionPacket} with a DistExecutor on client side. + * Used when player evolves into a race. + * + * @see InternalRacePacketActions#sendRaceEvolutionPacket + */ + public static void raceEvolutionPacket(ResourceLocation location) { + if (Platform.getEnvironment() == Env.CLIENT) { + InternalRacePacketActions.sendRaceEvolutionPacket(location); + } + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceEvents.java b/race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceEvents.java new file mode 100644 index 00000000..6f748850 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/api/RaceEvents.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.api; + +import dev.architectury.event.Event; +import dev.architectury.event.EventFactory; +import dev.architectury.event.EventResult; +import io.github.manasmods.manascore.network.api.util.Changeable; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.entity.LivingEntity; + +public interface RaceEvents { + Event POST_INIT = EventFactory.createLoop(); + Event SET_RACE = EventFactory.createEventResult(); + Event RACE_PRE_TICK = EventFactory.createEventResult(); + Event RACE_UPDATE_COOLDOWN = EventFactory.createEventResult(); + Event RACE_POST_TICK = EventFactory.createLoop(); + Event ACTIVATE_ABILITY = EventFactory.createEventResult(); + Event RELEASE_ABILITY = EventFactory.createEventResult(); + + @FunctionalInterface + interface SetRaceEvent { + EventResult set(ManasRaceInstance instance, LivingEntity owner, ManasRaceInstance newInstance, boolean evolution, Changeable teleportToSpawn, Changeable raceMessage); + } + + @FunctionalInterface + interface RaceTickEvent { + EventResult tick(ManasRaceInstance instance, LivingEntity owner); + } + + @FunctionalInterface + interface RacePostTickEvent { + void tick(ManasRaceInstance instance, LivingEntity owner); + } + + @FunctionalInterface + interface RaceUpdateCooldownEvent { + EventResult cooldown(ManasRaceInstance instance, LivingEntity owner, int currentCooldown); + } + + @FunctionalInterface + interface RaceAbilityActivationEvent { + EventResult activateAbility(ManasRaceInstance instance, LivingEntity owner); + } + + @FunctionalInterface + interface RaceAbilityReleaseEvent { + EventResult releaseAbility(ManasRaceInstance instance, LivingEntity owner, int heldTicks); + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/api/Races.java b/race-common/src/main/java/io/github/manasmods/manascore/race/api/Races.java new file mode 100644 index 00000000..7c0c6f74 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/api/Races.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.api; + +import lombok.NonNull; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public interface Races { + Optional getRace(); + + default boolean setRace(@NotNull ResourceLocation raceId, boolean teleportToSpawn) { + return setRace(raceId, teleportToSpawn, null); + } + + default boolean setRace(@NotNull ResourceLocation raceId, boolean teleportToSpawn, @Nullable MutableComponent component) { + ManasRace race = RaceAPI.getRaceRegistry().get(raceId); + if (race == null) return false; + return setRace(race.createDefaultInstance(), false, teleportToSpawn, component); + } + + default boolean setRace(@NonNull ManasRace race, boolean teleportToSpawn) { + return setRace(race, teleportToSpawn, null); + } + + default boolean setRace(@NonNull ManasRace race, boolean teleportToSpawn, @Nullable MutableComponent component) { + return setRace(race.createDefaultInstance(), false, teleportToSpawn, component); + } + + default boolean setRace(ManasRaceInstance instance, boolean evolution, boolean teleportToSpawn) { + return setRace(instance, evolution, teleportToSpawn, null); + } + + boolean setRace(ManasRaceInstance instance, boolean evolution, boolean teleportToSpawn, @Nullable MutableComponent component); + + default boolean evolveRace(@NotNull ResourceLocation raceId) { + return evolveRace(raceId, null); + } + + default boolean evolveRace(@NotNull ResourceLocation raceId, @Nullable MutableComponent component) { + ManasRace race = RaceAPI.getRaceRegistry().get(raceId); + if (race == null) return false; + return setRace(race.createDefaultInstance(), true, false); + } + + default boolean evolveRace(@NonNull ManasRace race) { + return evolveRace(race, null); + } + + default boolean evolveRace(@NonNull ManasRace race, @Nullable MutableComponent component) { + return setRace(race.createDefaultInstance(), true, false, component); + } + + default boolean evolveRace(ManasRaceInstance evolution) { + return evolveRace(evolution, null); + } + + default boolean evolveRace(ManasRaceInstance evolution, @Nullable MutableComponent component) { + return setRace(evolution, true, false, component); + } + + void markDirty(); + + default void checkAndMarkDirty(ManasRaceInstance instance) { + if (instance.isDirty()) this.markDirty(); + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java b/race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java new file mode 100644 index 00000000..55143758 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/api/SpawnPointHelper.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.api; + +import com.mojang.datafixers.util.Pair; +import io.github.manasmods.manascore.race.ManasCoreRace; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.TicketType; +import net.minecraft.util.Tuple; +import net.minecraft.util.Unit; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.vehicle.DismountHelper; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * This class contains a modified version of one or multiple open source code snippets. + *

+ * The following sources have been used to create this class: + * - ... + */ +public class SpawnPointHelper { + /** + * Can be used to teleport {@link Entity} to a new location among dimensions. + */ + public static void teleportToAcrossDimensions(Entity entity, ServerLevel dimension, double x, double y, double z, float xRot, float yRot) { + ChunkPos chunkPos = new ChunkPos(SectionPos.blockToSectionCoord(x), SectionPos.blockToSectionCoord(z)); + dimension.getChunkSource().addRegionTicket(TicketType.START, chunkPos, 11, Unit.INSTANCE); + if (entity instanceof ServerPlayer serverPlayer) { + serverPlayer.teleportTo(dimension, x, y, z, xRot, yRot); + } else { + entity.teleportTo(dimension, x, y, z, Set.of(), xRot, yRot); + } + } + + public static void teleportToAcrossDimensions(Entity entity, ResourceKey dimension, double x, double y, double z, float xRot, float yRot) { + MinecraftServer server = entity.getServer(); + if (server == null) return; + ServerLevel level = server.getLevel(dimension); + if (level == null) return; + teleportToAcrossDimensions(entity, level, x, y, z, xRot, yRot); + } + + /** + * Can be used to teleport {@link Entity} to a new valid spawn location among dimensions. + */ + public static void teleportToNewSpawn(Entity entity, ResourceKey dimension, BlockState platformMaterial) { + Tuple spawn = getSpawn(entity.level(), dimension, platformMaterial); + if (spawn == null) return; + Vec3 pos = spawn.getB(); + teleportToAcrossDimensions(entity, spawn.getA(), pos.x, pos.y, pos.z, entity.getXRot(), entity.getYRot()); + } + + public static void teleportToNewSpawn(ServerPlayer player) { + Optional optional = RaceAPI.getRaceFrom(player).getRace(); + if (optional.isEmpty()) return; + Pair, BlockState> pair = optional.get().getRespawnDimension(player); + ResourceKey dimension = pair.getFirst(); + if (player.getRespawnDimension() == dimension) return; + teleportToNewSpawn(player, dimension, pair.getSecond()); + } + + @Nullable + public static Tuple getSpawn(Level level, ResourceKey dimension, BlockState platformMaterial) { + MinecraftServer server = level.getServer(); + if (server == null) return null; + + ServerLevel destination = server.getLevel(dimension); + if (destination == null) { + ManasCoreRace.LOG.warn("Could not find dimension \"{}\".", dimension.toString()); + return null; + } + + BlockPos defaultSpawn = dimension == Level.END ? ServerLevel.END_SPAWN_POINT : Objects.requireNonNull(server.getLevel(Level.OVERWORLD)).getSharedSpawnPos(); + return getSpawn(destination, defaultSpawn, platformMaterial, 200, 100); + } + + @Nullable + public static Tuple getSpawn(ServerLevel destination, BlockPos basePos, BlockState platformMaterial, int searchRange, int secondRange) { + Vec3 validSpawn = getValidSpawn(basePos, searchRange, destination); + if (validSpawn != null) { + ChunkPos chunkPos = new ChunkPos(SectionPos.blockToSectionCoord(validSpawn.x), SectionPos.blockToSectionCoord(validSpawn.z)); + destination.getChunkSource().addRegionTicket(TicketType.START, chunkPos, 11, Unit.INSTANCE); + return new Tuple<>(destination, validSpawn); + } + + if (platformMaterial.isAir()) return null; + createSafePlatform(destination, BlockPos.containing(basePos.getBottomCenter()).below(), platformMaterial, true); + Vec3 secondSpawn = getValidSpawn(basePos, searchRange, destination); + + if (secondSpawn != null) { + ChunkPos chunkPos = new ChunkPos(SectionPos.blockToSectionCoord(secondSpawn.x), SectionPos.blockToSectionCoord(secondSpawn.z)); + destination.getChunkSource().addRegionTicket(TicketType.START, chunkPos, 11, Unit.INSTANCE); + return new Tuple<>(destination, secondSpawn); + } + return null; + } + + @Nullable + public static Vec3 getValidSpawn(BlockPos startPos, int range, ServerLevel world) { + //Force load the chunk in which we are working. + //This method will generate the chunk if it needs to. + world.getChunk(startPos.getX() >> 4, startPos.getZ() >> 4, ChunkStatus.FULL, true); + + // (di, dj) is a vector - direction in which we move right now + // (di, dj) is a vector - direction in which we move right now + int dx = 1; + int dz = 0; + + // length of current segment + int segmentLength = 1; + BlockPos.MutableBlockPos mutable = startPos.mutable(); + + // center of our starting structure, or dimension + int center = startPos.getY(); + // Our valid spawn location + Vec3 tpPos; + + // current position (x, z) and how much of current segment we passed + int x = startPos.getX(); + int z = startPos.getZ(); + + //position to check up, or down + int segmentPassed = 0; + // Increase y check + int i = 0; + // Decrease y check + int d = -1; + + while (i < world.getLogicalHeight() || d > 0) { + for (int coordinateCount = 0; coordinateCount < range; ++coordinateCount) { + // make a step, add 'direction' vector (di, dj) to current position (i, j) + x += dx; + z += dz; + ++segmentPassed; + + mutable.setX(x); + mutable.setZ(z); + mutable.setY(center + i); + + tpPos = DismountHelper.findSafeDismountLocation(EntityType.PLAYER, world, mutable, true); + if (tpPos != null) return (tpPos); + + mutable.setY(center + d); + tpPos = DismountHelper.findSafeDismountLocation(EntityType.PLAYER, world, mutable, true); + if (tpPos != null) return (tpPos); + + if (segmentPassed == segmentLength) { + // done with current segment + segmentPassed = 0; + + // 'rotate' directions + int buffer = dx; + dx = -dz; + dz = buffer; + + // increase segment length if necessary + if (dz == 0) ++segmentLength; + } + } + i++; + d--; + } + return null; + } + + public static void createSafePlatform(ServerLevel level, BlockPos blockPos, BlockState state, boolean replaceBlocks) { + BlockPos.MutableBlockPos mutable = blockPos.mutable(); + for(int i = -2; i <= 2; ++i) { + for(int j = -2; j <= 2; ++j) { + for(int k = -1; k < 3; ++k) { + + BlockPos pos = mutable.set(blockPos).move(j, k, i); + BlockState blockState = k == -1 ? state : Blocks.AIR.defaultBlockState(); + + if (level.getBlockState(pos).is(blockState.getBlock())) continue; + if (replaceBlocks) level.destroyBlock(pos, true, null); + level.setBlock(pos, blockState, 3); + } + } + } + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java new file mode 100644 index 00000000..08da1af1 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceRegistry.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.impl; + +import dev.architectury.event.EventResult; +import dev.architectury.event.events.common.EntityEvent; +import dev.architectury.event.events.common.PlayerEvent; +import dev.architectury.registry.registries.Registrar; +import dev.architectury.registry.registries.RegistrarManager; +import io.github.manasmods.manascore.race.ModuleConstants; +import io.github.manasmods.manascore.race.api.*; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import io.github.manasmods.manascore.skill.api.SkillEvents; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.LivingEntity; + +import java.util.Optional; + +public class RaceRegistry { + private static final RegistrarManager MANAGER = RegistrarManager.get(ModuleConstants.MOD_ID); + private static final ResourceLocation registryId = ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "races"); + public static final Registrar RACES = MANAGER.builder(registryId).syncToClients().build(); + public static final ResourceKey> KEY = createKey(RACES); + + public static void init() { + EntityEvents.LIVING_EFFECT_ADDED.register((entity, source, changeableTarget) -> { + Races storage = RaceAPI.getRaceFrom(entity); + Optional optional = storage.getRace(); + if (optional.isEmpty()) return EventResult.pass(); + + ManasRaceInstance instance = optional.get(); + if (!instance.canActivateAbility(entity)) return EventResult.pass(); + if (!instance.onEffectAdded(entity, source, changeableTarget)) { + storage.checkAndMarkDirty(instance); + return EventResult.interruptFalse(); + } else storage.checkAndMarkDirty(instance); + return EventResult.pass(); + }); + + EntityEvents.LIVING_CHANGE_TARGET_EARLY.register((entity, changeableTarget) -> { + if (!changeableTarget.isPresent()) return EventResult.pass(); + LivingEntity owner = changeableTarget.get(); + if (owner == null) return EventResult.pass(); + + Races storage = RaceAPI.getRaceFrom(owner); + Optional optional = storage.getRace(); + if (optional.isEmpty()) return EventResult.pass(); + + ManasRaceInstance instance = optional.get(); + if (!instance.canActivateAbility(owner)) return EventResult.pass(); + if (!instance.onBeingTargeted(changeableTarget, entity)) { + storage.checkAndMarkDirty(instance); + return EventResult.interruptFalse(); + } else storage.checkAndMarkDirty(instance); + return EventResult.pass(); + }); + + SkillEvents.SKILL_DAMAGE_POST_CALCULATION.register((storage, target, source, amount) -> { + if (!(source.getEntity() instanceof LivingEntity owner)) return EventResult.pass(); + Races ownerStorage = RaceAPI.getRaceFrom(owner); + Optional optional = ownerStorage.getRace(); + if (optional.isEmpty()) return EventResult.pass(); + + ManasRaceInstance instance = optional.get(); + if (!instance.canActivateAbility(owner)) return EventResult.pass(); + if (!instance.onAttackEntity(owner, target, source, amount)) { + ownerStorage.checkAndMarkDirty(instance); + return EventResult.interruptFalse(); + } else ownerStorage.checkAndMarkDirty(instance); + return EventResult.pass(); + }); + + EntityEvents.LIVING_DAMAGE.register((entity, source, amount) -> { + Races storage = RaceAPI.getRaceFrom(entity); + Optional optional = storage.getRace(); + if (optional.isEmpty()) return EventResult.pass(); + + ManasRaceInstance instance = optional.get(); + if (!instance.canActivateAbility(entity)) return EventResult.pass(); + if (!instance.onHurt(entity, source, amount)) { + storage.checkAndMarkDirty(instance); + return EventResult.interruptFalse(); + } else storage.checkAndMarkDirty(instance); + return EventResult.pass(); + }); + + EntityEvent.LIVING_DEATH.register((entity, source) -> { + Races storage = RaceAPI.getRaceFrom(entity); + Optional optional = storage.getRace(); + if (optional.isEmpty()) return EventResult.pass(); + + ManasRaceInstance instance = optional.get(); + if (!instance.canActivateAbility(entity)) return EventResult.pass(); + if (!instance.onDeath(entity, source)) { + storage.checkAndMarkDirty(instance); + return EventResult.interruptFalse(); + } else storage.checkAndMarkDirty(instance); + return EventResult.pass(); + }); + + PlayerEvent.PLAYER_RESPAWN.register((newPlayer, conqueredEnd, removalReason) -> { + Races storage = RaceAPI.getRaceFrom(newPlayer); + Optional optional = storage.getRace(); + if (optional.isEmpty()) return; + + ManasRaceInstance instance = optional.get(); + if (!conqueredEnd) { + instance.addAttributeModifiers(newPlayer); + newPlayer.setHealth(newPlayer.getMaxHealth()); + SpawnPointHelper.teleportToNewSpawn(newPlayer); + } + + if (!instance.canActivateAbility(newPlayer)) return; + instance.onRespawn(newPlayer, conqueredEnd); + storage.checkAndMarkDirty(instance); + }); + } + + private RaceRegistry() { + } + + private static ResourceKey> createKey(final Registrar registrar) { + return ResourceKey.createRegistryKey(registrar.key().location()); + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java new file mode 100644 index 00000000..15bd7b59 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/RaceStorage.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.impl; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import dev.architectury.event.EventResult; +import dev.architectury.event.events.common.PlayerEvent; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.race.ModuleConstants; +import io.github.manasmods.manascore.race.api.*; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import io.github.manasmods.manascore.storage.api.Storage; +import io.github.manasmods.manascore.storage.api.StorageEvents; +import io.github.manasmods.manascore.storage.api.StorageKey; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.log4j.Log4j2; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.UUID; + +@Log4j2 +public class RaceStorage extends Storage implements Races { + @Getter + private static StorageKey key = null; + public static final int INSTANCE_UPDATE = 20; + public static final Multimap tickingRaces = ArrayListMultimap.create(); + private static final String RACE_KEY = "race_key"; + + public static void init() { + StorageEvents.REGISTER_ENTITY_STORAGE.register(registry -> + key = registry.register(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "race_storage"), + RaceStorage.class, LivingEntity.class::isInstance, target -> new RaceStorage((LivingEntity) target))); + + EntityEvents.LIVING_POST_TICK.register(entity -> { + Level level = entity.level(); + if (level.isClientSide()) return; + Races storage = RaceAPI.getRaceFrom(entity); + handleRaceTick(entity, level, storage); + if (entity instanceof Player player) handleRaceHeldTick(player, storage); + }); + + PlayerEvent.PLAYER_QUIT.register(player -> tickingRaces.removeAll(player.getUUID())); + PlayerEvent.CHANGE_DIMENSION.register((player, resourceKey, resourceKey1) -> tickingRaces.removeAll(player.getUUID())); + } + + private static void handleRaceTick(LivingEntity entity, Level level, Races storage) { + MinecraftServer server = level.getServer(); + if (server == null) return; + + boolean shouldTickRace = server.getTickCount() % INSTANCE_UPDATE == 0; + if (!shouldTickRace) return; + tickRace(entity, storage); + } + + private static void handleRaceHeldTick(Player player, Races storage) { + if (!tickingRaces.containsKey(player.getUUID())) return; + tickingRaces.get(player.getUUID()).removeIf(skill -> !skill.tick(storage, player)); + storage.markDirty(); + } + + private static void tickRace(LivingEntity entity, Races storage) { + Optional optional = storage.getRace(); + if (optional.isEmpty()) return; + + ManasRaceInstance instance = optional.get(); + if (instance.isOnCooldown()) { + if (!RaceEvents.RACE_UPDATE_COOLDOWN.invoker().cooldown(instance, entity, instance.getCooldown()).isFalse()) + instance.setCooldown(instance.getCooldown() - 1); + storage.checkAndMarkDirty(instance); + } + + if (!instance.canActivateAbility(entity)) return; + if (!instance.canTick(entity)) return; + if (RaceEvents.RACE_PRE_TICK.invoker().tick(instance, entity).isFalse()) return; + instance.onTick(entity); + RaceEvents.RACE_POST_TICK.invoker().tick(instance, entity); + storage.checkAndMarkDirty(instance); + } + + private ManasRaceInstance raceInstance = null; + + protected RaceStorage(LivingEntity holder) { + super(holder); + } + + public Optional getRace() { + return Optional.ofNullable(this.raceInstance); + } + + public boolean setRace(@NonNull ManasRaceInstance race, boolean evolution, boolean teleportToSpawn, @Nullable MutableComponent component) { + ManasRaceInstance instance = this.raceInstance; + Changeable teleport = Changeable.of(teleportToSpawn); + Changeable raceMessage = Changeable.of(component); + EventResult result = RaceEvents.SET_RACE.invoker().set(instance, getOwner(), race, evolution, teleport, raceMessage); + if (result.isFalse()) return false; + + LivingEntity owner = this.getOwner(); + if (instance != null && instance != race) { + instance.removeAttributeModifiers(owner); + if (evolution) { + race.deserialize(instance.serialize(new CompoundTag())); + instance.onRaceEvolution(owner, race); + } + } + + if (raceMessage.isPresent()) getOwner().sendSystemMessage(raceMessage.get()); + race.markDirty(); + race.addAttributeModifiers(owner); + owner.setHealth(owner.getMaxHealth()); + race.onRaceSet(owner); + + race.learnIntrinsicSkills(owner); + this.raceInstance = race; + + if (teleport.get() && getOwner() instanceof ServerPlayer player) SpawnPointHelper.teleportToNewSpawn(player); + markDirty(); + return true; + } + + @Override + public void save(CompoundTag data) { + if (this.raceInstance == null) return; + data.put(RACE_KEY, this.raceInstance.toNBT()); + } + + @Override + public void load(CompoundTag data) { + if (!data.contains(RACE_KEY)) return; + this.raceInstance = ManasRaceInstance.fromNBT(data.getCompound(RACE_KEY)); + } + + protected LivingEntity getOwner() { + return (LivingEntity) this.holder; + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/impl/TickingRace.java b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/TickingRace.java new file mode 100644 index 00000000..b1bde045 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/TickingRace.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.impl; + +import io.github.manasmods.manascore.race.api.ManasRace; +import io.github.manasmods.manascore.race.api.ManasRaceInstance; +import io.github.manasmods.manascore.race.api.Races; +import net.minecraft.world.entity.LivingEntity; + +import java.util.Optional; + +/** + * This is the Registry Object for Ticking Races when a {@link ManasRace} is held down. + */ +public class TickingRace { + private int duration = 0; + public TickingRace() { + } + + public boolean tick(Races storage, LivingEntity entity) { + if (!entity.isAlive()) return false; + Optional optional = storage.getRace(); + if (optional.isEmpty()) return false; + + ManasRaceInstance instance = optional.get(); + if (reachedMaxDuration(instance, entity)) return false; + + if (!instance.canActivateAbility(entity)) return false; + return instance.onHeldAbility(entity, this.duration++); + } + + public boolean reachedMaxDuration(ManasRaceInstance instance, LivingEntity entity) { + int maxDuration = instance.getMaxHeldTime(entity); + if (maxDuration == -1) return false; + return duration >= maxDuration; + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/impl/data/RaceTagProvider.java b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/data/RaceTagProvider.java new file mode 100644 index 00000000..94ab12f7 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/data/RaceTagProvider.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.impl.data; + +import io.github.manasmods.manascore.race.api.ManasRace; +import io.github.manasmods.manascore.race.impl.RaceRegistry; +import net.minecraft.core.HolderLookup; +import net.minecraft.data.PackOutput; +import net.minecraft.data.tags.IntrinsicHolderTagsProvider; + +import java.util.concurrent.CompletableFuture; + +public abstract class RaceTagProvider extends IntrinsicHolderTagsProvider { + public RaceTagProvider(PackOutput output, CompletableFuture lookupProvider) { + super(output, RaceRegistry.KEY, lookupProvider, manasSkill -> RaceRegistry.RACES.getKey(manasSkill).orElseThrow()); + } + + public RaceTagProvider(PackOutput output, CompletableFuture lookupProvider, CompletableFuture> parentProvider) { + super(output, RaceRegistry.KEY, lookupProvider, parentProvider, manasSkill -> RaceRegistry.RACES.getKey(manasSkill).orElseThrow()); + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/InternalRacePacketActions.java b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/InternalRacePacketActions.java new file mode 100644 index 00000000..7d626bc7 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/InternalRacePacketActions.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.impl.network; + +import dev.architectury.networking.NetworkManager; +import io.github.manasmods.manascore.race.api.ManasRace; +import io.github.manasmods.manascore.race.impl.network.c2s.RequestRaceAbilityActivationPacket; +import io.github.manasmods.manascore.race.impl.network.c2s.RequestRaceAbilityReleasePacket; +import io.github.manasmods.manascore.race.impl.network.c2s.RequestRaceEvolutionPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; + +public class InternalRacePacketActions { + private InternalRacePacketActions() { + } + + /** + * This Method sends packet for the {@link ManasRace} Ability Activation. + * Only executes on client using the dist executor. + */ + public static void sendRaceAbilityActivationPacket() { + var minecraft = Minecraft.getInstance(); + Player player = minecraft.player; + if (player == null) return; + NetworkManager.sendToServer(new RequestRaceAbilityActivationPacket()); + } + + /** + * This Method sends packet for the {@link ManasRace} Ability Release. + * Only executes on client using the dist executor. + */ + public static void sendRaceAbilityReleasePacket(int heldTick) { + var minecraft = Minecraft.getInstance(); + Player player = minecraft.player; + if (player == null) return; + NetworkManager.sendToServer(new RequestRaceAbilityReleasePacket(heldTick)); + } + + /** + * This Method sends packet for the {@link ManasRace} Evolution. + * Only executes on client using the dist executor. + */ + public static void sendRaceEvolutionPacket(ResourceLocation evolution) { + var minecraft = Minecraft.getInstance(); + Player player = minecraft.player; + if (player == null) return; + NetworkManager.sendToServer(new RequestRaceEvolutionPacket(evolution)); + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/ManasRaceNetwork.java b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/ManasRaceNetwork.java new file mode 100644 index 00000000..4e089083 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/ManasRaceNetwork.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.impl.network; + +import io.github.manasmods.manascore.network.api.util.NetworkUtils; +import io.github.manasmods.manascore.race.impl.network.c2s.RequestRaceAbilityActivationPacket; +import io.github.manasmods.manascore.race.impl.network.c2s.RequestRaceAbilityReleasePacket; +import io.github.manasmods.manascore.race.impl.network.c2s.RequestRaceEvolutionPacket; + +public class ManasRaceNetwork { + public static void init() { + NetworkUtils.registerC2SPayload(RequestRaceAbilityActivationPacket.TYPE, + RequestRaceAbilityActivationPacket.STREAM_CODEC, RequestRaceAbilityActivationPacket::handle); + NetworkUtils.registerC2SPayload(RequestRaceAbilityReleasePacket.TYPE, + RequestRaceAbilityReleasePacket.STREAM_CODEC, RequestRaceAbilityReleasePacket::handle); + NetworkUtils.registerC2SPayload(RequestRaceEvolutionPacket.TYPE, + RequestRaceEvolutionPacket.STREAM_CODEC, RequestRaceEvolutionPacket::handle); + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceAbilityActivationPacket.java b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceAbilityActivationPacket.java new file mode 100644 index 00000000..928c9ba2 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceAbilityActivationPacket.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.impl.network.c2s; + +import dev.architectury.networking.NetworkManager; +import dev.architectury.utils.Env; +import io.github.manasmods.manascore.race.ModuleConstants; +import io.github.manasmods.manascore.race.api.ManasRaceInstance; +import io.github.manasmods.manascore.race.api.RaceAPI; +import io.github.manasmods.manascore.race.api.RaceEvents; +import io.github.manasmods.manascore.race.api.Races; +import io.github.manasmods.manascore.race.impl.RaceStorage; +import io.github.manasmods.manascore.race.impl.TickingRace; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public record RequestRaceAbilityActivationPacket() implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "request_race_ability_activation")); + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(RequestRaceAbilityActivationPacket::encode, RequestRaceAbilityActivationPacket::new); + + public RequestRaceAbilityActivationPacket(FriendlyByteBuf buf) { + this(); + } + + public void encode(FriendlyByteBuf buf) { + } + + public void handle(NetworkManager.PacketContext context) { + if (context.getEnvironment() != Env.SERVER) return; + context.queue(() -> { + Player player = context.getPlayer(); + if(player == null) return; + + Races storage = RaceAPI.getRaceFrom(player); + Optional optional = storage.getRace(); + if (optional.isEmpty()) return; + + ManasRaceInstance instance = optional.get(); + if (RaceEvents.ACTIVATE_ABILITY.invoker().activateAbility(instance, player).isFalse()) return; + if (!instance.canActivateAbility(player)) return; + if (instance.isOnCooldown()) return; + + instance.onActivateAbility(player); + RaceStorage.tickingRaces.put(player.getUUID(), new TickingRace()); + storage.checkAndMarkDirty(instance); + }); + } + + public @NotNull Type type() { + return TYPE; + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceAbilityReleasePacket.java b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceAbilityReleasePacket.java new file mode 100644 index 00000000..e4ed2ea0 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceAbilityReleasePacket.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.impl.network.c2s; + +import dev.architectury.networking.NetworkManager; +import dev.architectury.utils.Env; +import io.github.manasmods.manascore.race.ModuleConstants; +import io.github.manasmods.manascore.race.api.ManasRaceInstance; +import io.github.manasmods.manascore.race.api.RaceAPI; +import io.github.manasmods.manascore.race.api.RaceEvents; +import io.github.manasmods.manascore.race.api.Races; +import io.github.manasmods.manascore.race.impl.RaceStorage; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public record RequestRaceAbilityReleasePacket( + int heldTick +) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "request_race_ability_release")); + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(RequestRaceAbilityReleasePacket::encode, RequestRaceAbilityReleasePacket::new); + + public RequestRaceAbilityReleasePacket(FriendlyByteBuf buf) { + this(buf.readInt()); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeInt(this.heldTick); + } + + public void handle(NetworkManager.PacketContext context) { + if (context.getEnvironment() != Env.SERVER) return; + context.queue(() -> { + Player player = context.getPlayer(); + if(player == null) return; + + Races storage = RaceAPI.getRaceFrom(player); + Optional optional = storage.getRace(); + if (optional.isEmpty()) return; + + ManasRaceInstance instance = optional.get(); + if (RaceEvents.RELEASE_ABILITY.invoker().releaseAbility(instance, player, heldTick).isFalse()) return; + if (instance.canActivateAbility(player) && !instance.isOnCooldown()) { + instance.onReleaseAbility(player, heldTick); + storage.checkAndMarkDirty(instance); + } + RaceStorage.tickingRaces.removeAll(player.getUUID()); + }); + } + + public @NotNull Type type() { + return TYPE; + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceEvolutionPacket.java b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceEvolutionPacket.java new file mode 100644 index 00000000..a3e6bc43 --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/impl/network/c2s/RequestRaceEvolutionPacket.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.impl.network.c2s; + +import dev.architectury.networking.NetworkManager; +import dev.architectury.utils.Env; +import io.github.manasmods.manascore.race.ModuleConstants; +import io.github.manasmods.manascore.race.api.*; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public record RequestRaceEvolutionPacket( + ResourceLocation evolution +) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "request_race_evolution")); + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(RequestRaceEvolutionPacket::encode, RequestRaceEvolutionPacket::new); + + public RequestRaceEvolutionPacket(FriendlyByteBuf buf) { + this(buf.readResourceLocation()); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeResourceLocation(this.evolution); + } + + public void handle(NetworkManager.PacketContext context) { + if (context.getEnvironment() != Env.SERVER) return; + context.queue(() -> { + Player player = context.getPlayer(); + if (player == null) return; + + Races storage = RaceAPI.getRaceFrom(player); + Optional optional = storage.getRace(); + if (optional.isEmpty()) return; + + ManasRace race = RaceAPI.getRaceRegistry().get(evolution); + if (race == null) return; + + ManasRaceInstance instance = optional.get(); + if (!instance.getNextEvolutions(player).contains(race)) return; + + double progress = instance.getEvolutionProgress(player, race); + if (progress < 1.0F) return; + storage.evolveRace(race); + }); + } + + public @NotNull Type type() { + return TYPE; + } +} diff --git a/race-common/src/main/java/io/github/manasmods/manascore/race/mixin/MixinServerPlayer.java b/race-common/src/main/java/io/github/manasmods/manascore/race/mixin/MixinServerPlayer.java new file mode 100644 index 00000000..b833d7cc --- /dev/null +++ b/race-common/src/main/java/io/github/manasmods/manascore/race/mixin/MixinServerPlayer.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(PlayerList.class) +public abstract class MixinServerPlayer { + @WrapOperation(method = "respawn", at = @At(value = "INVOKE", + target = "Lnet/minecraft/server/level/ServerPlayer;restoreFrom(Lnet/minecraft/server/level/ServerPlayer;Z)V")) + private void restoreFrom(ServerPlayer instance, ServerPlayer oldPlayer, boolean endConquered, Operation original) { + if (!endConquered) original.call(instance, oldPlayer, endConquered); + else { + original.call(instance, oldPlayer, endConquered); + instance.getAttributes().assignAllValues(oldPlayer.getAttributes()); + instance.setHealth(oldPlayer.getHealth()); + } + } +} diff --git a/race-common/src/main/resources/architectury.common.json b/race-common/src/main/resources/architectury.common.json new file mode 100644 index 00000000..72ba7b31 --- /dev/null +++ b/race-common/src/main/resources/architectury.common.json @@ -0,0 +1,3 @@ +{ + "accessWidener": "manascore_race.accesswidener" +} \ No newline at end of file diff --git a/race-common/src/main/resources/assets/manascore/lang/en_us.json b/race-common/src/main/resources/assets/manascore/lang/en_us.json new file mode 100644 index 00000000..36772fcf --- /dev/null +++ b/race-common/src/main/resources/assets/manascore/lang/en_us.json @@ -0,0 +1,6 @@ +{ + "manascore.race.difficulty.easy": "Easy", + "manascore.race.difficulty.intermediate": "Intermediate", + "manascore.race.difficulty.hard": "Hard", + "manascore.race.difficulty.extreme": "Extreme" +} \ No newline at end of file diff --git a/race-common/src/main/resources/manascore_race.accesswidener b/race-common/src/main/resources/manascore_race.accesswidener new file mode 100644 index 00000000..2464d6d5 --- /dev/null +++ b/race-common/src/main/resources/manascore_race.accesswidener @@ -0,0 +1 @@ +accessWidener v2 named \ No newline at end of file diff --git a/race-common/src/main/resources/manascore_race.mixins.json b/race-common/src/main/resources/manascore_race.mixins.json new file mode 100644 index 00000000..1fec6648 --- /dev/null +++ b/race-common/src/main/resources/manascore_race.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "package": "io.github.manasmods.manascore.race.mixin", + "compatibilityLevel": "JAVA_21", + "minVersion": "0.8", + "client": [ + ], + "mixins": [ + "MixinServerPlayer" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/race-fabric/build.gradle b/race-fabric/build.gradle new file mode 100644 index 00000000..4e6749f4 --- /dev/null +++ b/race-fabric/build.gradle @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +loom { + accessWidenerPath = project(":race-common").loom.accessWidenerPath +} + +dependencies { + implementation project(path: ":skill-common", configuration: 'transformProductionFabric') + implementation project(path: ":network-common", configuration: 'transformProductionFabric') + implementation project(path: ":storage-common", configuration: 'transformProductionFabric') +} diff --git a/race-fabric/src/main/java/io/github/manasmods/manascore/race/fabric/ManasCoreRaceFabric.java b/race-fabric/src/main/java/io/github/manasmods/manascore/race/fabric/ManasCoreRaceFabric.java new file mode 100644 index 00000000..13c0051c --- /dev/null +++ b/race-fabric/src/main/java/io/github/manasmods/manascore/race/fabric/ManasCoreRaceFabric.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.fabric; + +import io.github.manasmods.manascore.race.ManasCoreRace; +import net.fabricmc.api.ModInitializer; + +public class ManasCoreRaceFabric implements ModInitializer { + @Override + public void onInitialize() { + ManasCoreRace.init(); + } +} \ No newline at end of file diff --git a/race-fabric/src/main/resources/assets/manascore/icon.png b/race-fabric/src/main/resources/assets/manascore/icon.png new file mode 100644 index 00000000..a38ae1d1 Binary files /dev/null and b/race-fabric/src/main/resources/assets/manascore/icon.png differ diff --git a/race-fabric/src/main/resources/assets/manascore/logo.png b/race-fabric/src/main/resources/assets/manascore/logo.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/race-fabric/src/main/resources/assets/manascore/logo.png differ diff --git a/race-fabric/src/main/resources/fabric.mod.json b/race-fabric/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..31f510d1 --- /dev/null +++ b/race-fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "Utility and Core Library for Manas Mods", + "authors": [ + "ManasMods" + ], + "contact": { + "homepage": "" + }, + "license": "${license}", + "icon": "assets/manascore/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "io.github.manasmods.manascore.race.fabric.ManasCoreRaceFabric" + ], + "client": [] + }, + "mixins": [ + "${mod_id}.mixins.json", + "${mod_id}-fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=${fabric_loader_version}", + "minecraft": "~${minecraft_version}", + "java": ">=21", + "architectury": ">=${architectury_version}", + "fabric-api": "*", + "manascore_network": ">=${version}" + }, + "suggests": { + } +} diff --git a/race-fabric/src/main/resources/manascore_race-fabric.mixins.json b/race-fabric/src/main/resources/manascore_race-fabric.mixins.json new file mode 100644 index 00000000..774221d9 --- /dev/null +++ b/race-fabric/src/main/resources/manascore_race-fabric.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "package": "io.github.manasmods.manascore.race.fabric.mixin", + "compatibilityLevel": "JAVA_21", + "minVersion": "0.8", + "client": [ + ], + "mixins": [ + + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/race-neoforge/build.gradle b/race-neoforge/build.gradle new file mode 100644 index 00000000..7fa3b977 --- /dev/null +++ b/race-neoforge/build.gradle @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +loom { + accessWidenerPath = project(":race-common").loom.accessWidenerPath +} + +dependencies { + implementation project(path: ":skill-common", configuration: 'transformProductionNeoForge') + implementation project(path: ":network-common", configuration: 'transformProductionNeoForge') + implementation project(path: ":storage-common", configuration: 'transformProductionNeoForge') +} + +remapJar { + atAccessWideners.add("${project.mod_id}.accesswidener") +} diff --git a/race-neoforge/gradle.properties b/race-neoforge/gradle.properties new file mode 100644 index 00000000..7da18ea6 --- /dev/null +++ b/race-neoforge/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge diff --git a/race-neoforge/src/main/java/io/github/manasmods/manascore/race/neoforge/ManasCoreRaceNeoForge.java b/race-neoforge/src/main/java/io/github/manasmods/manascore/race/neoforge/ManasCoreRaceNeoForge.java new file mode 100644 index 00000000..f8131d09 --- /dev/null +++ b/race-neoforge/src/main/java/io/github/manasmods/manascore/race/neoforge/ManasCoreRaceNeoForge.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.race.neoforge; + +import io.github.manasmods.manascore.race.ManasCoreRace; +import io.github.manasmods.manascore.race.ModuleConstants; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.common.Mod; + +@Mod(ModuleConstants.MOD_ID) +public final class ManasCoreRaceNeoForge { + public ManasCoreRaceNeoForge(IEventBus modEventBus) { + ManasCoreRace.init(); + } +} diff --git a/race-neoforge/src/main/resources/META-INF/neoforge.mods.toml b/race-neoforge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..566ae4c4 --- /dev/null +++ b/race-neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,55 @@ +modLoader = "javafml" +loaderVersion = "[2,)" +issueTrackerURL = "" +license = "${license}" + +[[mods]] +modId = "${mod_id}" +version = "${version}" +displayName = "${mod_name}" +authors = "ManasMods" +description = ''' +Utility and Core Library for Manas Mods +''' +logoFile = "icon.png" + +[[dependencies.${mod_id}]] + modId = "neoforge" + type = "required" + versionRange = "[21,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "minecraft" + type = "required" + versionRange = "[${minecraft_version},)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "architectury" + type = "required" + versionRange = "[${architectury_version},)" + ordering = "AFTER" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "manascore_storage" + type = "required" + versionRange = "[${version},)" + ordering = "AFTER" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "manascore_network" + type = "required" + versionRange = "[${version},)" + ordering = "AFTER" + side = "BOTH" + +[[mixins]] +config = "${mod_id}.mixins.json" + +[[mixins]] +config = "manascore_race-neoforge.mixins.json" diff --git a/race-neoforge/src/main/resources/icon.png b/race-neoforge/src/main/resources/icon.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/race-neoforge/src/main/resources/icon.png differ diff --git a/race-neoforge/src/main/resources/manascore_race-neoforge.mixins.json b/race-neoforge/src/main/resources/manascore_race-neoforge.mixins.json new file mode 100644 index 00000000..3cbe17a4 --- /dev/null +++ b/race-neoforge/src/main/resources/manascore_race-neoforge.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "package": "io.github.manasmods.manascore.race.neoforge.mixin", + "compatibilityLevel": "JAVA_21", + "minVersion": "0.8", + "client": [ + ], + "mixins": [ + + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/settings.gradle b/settings.gradle index 2cdd04a9..c19b1689 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -14,8 +14,12 @@ pluginManagement { rootProject.name = 'manascore' -["network", "storage", "testing", "inventory", "command"].forEach { module -> +["attribute", "command", "config", "inventory", "keybind", "network", "race", "storage", "testing", "skill"].forEach { module -> include ":$module-common" include ":$module-fabric" include ":$module-neoforge" } + +include ':bundle-fabric' +include ':bundle-neoforge' + diff --git a/skill-common/build.gradle b/skill-common/build.gradle new file mode 100644 index 00000000..f44d74e1 --- /dev/null +++ b/skill-common/build.gradle @@ -0,0 +1,8 @@ +loom { + accessWidenerPath = file('src/main/resources/manascore_skill.accesswidener') +} + +dependencies { + implementation(project(path: ":network-common", configuration: 'namedElements')) { transitive false } + implementation(project(path: ":storage-common", configuration: 'namedElements')) { transitive false } +} \ No newline at end of file diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkill.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkill.java new file mode 100644 index 00000000..76f797be --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkill.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill; + +import dev.architectury.platform.Platform; +import dev.architectury.utils.Env; +import io.github.manasmods.manascore.skill.api.SkillEvents; +import io.github.manasmods.manascore.skill.impl.SkillRegistry; +import io.github.manasmods.manascore.skill.impl.SkillStorage; +import io.github.manasmods.manascore.skill.impl.network.ManasSkillNetwork; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class ManasCoreSkill { + public static final Logger LOG = LoggerFactory.getLogger("ManasCore - Skill"); + + public static void init() { + SkillRegistry.init(); + SkillStorage.init(); + ManasSkillNetwork.init(); + if (Platform.getEnvironment() == Env.CLIENT) ManasCoreSkillClient.init(); + SkillEvents.POST_INIT.invoker().run(); + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkillClient.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkillClient.java new file mode 100644 index 00000000..19202483 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/ManasCoreSkillClient.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill; + +import dev.architectury.event.EventResult; +import dev.architectury.event.events.client.ClientRawInputEvent; +import dev.architectury.networking.NetworkManager; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.ManasSkillInstance; +import io.github.manasmods.manascore.skill.api.SkillAPI; +import io.github.manasmods.manascore.skill.api.SkillEvents; +import io.github.manasmods.manascore.skill.impl.network.c2s.RequestSkillScrollPacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; + +import java.util.HashMap; +import java.util.Map; + +public class ManasCoreSkillClient { + public static void init() { + ClientRawInputEvent.MOUSE_SCROLLED.register((client, amountX, amountY) -> { + Player player = client.player; + if (player == null) return EventResult.pass(); + + Map packetSkills = new HashMap<>(); + for (ManasSkillInstance skillInstance : SkillAPI.getSkillsFrom(player).getLearnedSkills()) { + Changeable mode = Changeable.of(0); + if (SkillEvents.SKILL_SCROLL_CLIENT.invoker().scroll(skillInstance, player, mode, amountY).isFalse()) continue; + if (!skillInstance.canScroll(player, mode.get())) continue; + packetSkills.put(skillInstance.getSkillId(), mode.get()); + } + + if (!packetSkills.isEmpty()) { + NetworkManager.sendToServer(new RequestSkillScrollPacket(amountY, packetSkills)); + return EventResult.interruptFalse(); + } + return EventResult.pass(); + }); + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java new file mode 100644 index 00000000..b31bc1a8 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/EntityEvents.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.api; + +import dev.architectury.event.Event; +import dev.architectury.event.EventFactory; +import dev.architectury.event.EventResult; +import dev.architectury.event.events.common.EntityEvent; +import io.github.manasmods.manascore.network.api.util.Changeable; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.entity.projectile.ProjectileDeflection; +import net.minecraft.world.phys.HitResult; + +public class EntityEvents { + public static Event LIVING_PRE_TICK = EventFactory.createLoop(); + public static Event LIVING_POST_TICK = EventFactory.createLoop(); + public static Event LIVING_EFFECT_ADDED = EventFactory.createEventResult(); + + public static Event LIVING_CHANGE_TARGET = EventFactory.createEventResult(); + public static Event LIVING_CHANGE_TARGET_EARLY = EventFactory.createEventResult(); + public static Event LIVING_CHANGE_TARGET_LATE = EventFactory.createEventResult(); + + public static Event LIVING_PRE_DAMAGED = EventFactory.createEventResult(); + public static Event LIVING_ON_BEING_DAMAGED = EventFactory.createEventResult(); + public static Event LIVING_HURT = EventFactory.createEventResult(); + public static Event LIVING_DAMAGE = EventFactory.createEventResult(); + public static Event PROJECTILE_HIT = EventFactory.createLoop(); + + public static Event DEATH_EVENT_FIRST = EventFactory.createEventResult(); + public static Event DEATH_EVENT_HIGH = EventFactory.createEventResult(); + public static Event DEATH_EVENT_NORMAL = EventFactory.createEventResult(); + public static Event DEATH_EVENT_LOW = EventFactory.createEventResult(); + public static Event DEATH_EVENT_LAST = EventFactory.createEventResult(); + + @FunctionalInterface + public interface LivingTickEvent { + void tick(LivingEntity entity); + } + + @FunctionalInterface + public interface LivingEffectAddedEvent { + EventResult effectAdd(LivingEntity entity, Entity source, Changeable changeableInstance); + } + + @FunctionalInterface + public interface LivingChangeTargetEvent { + EventResult changeTarget(LivingEntity entity, Changeable changeableTarget); + } + + @FunctionalInterface + public interface LivingHurtEvent { + EventResult hurt(LivingEntity entity, DamageSource source, Changeable amount); + } + + @FunctionalInterface + public interface LivingDamageEvent { + EventResult damage(LivingEntity entity, DamageSource source, Changeable amount); + } + + @FunctionalInterface + public interface ProjectileHitEvent { + void hit(HitResult hitResult, Projectile projectile, Changeable deflection, Changeable result); + } + + public enum ProjectileHitResult { + DEFAULT, // Hit, damage + possibly continue + HIT, // Hit + damage + HIT_NO_DAMAGE, // Hit + PASS // Pass through + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java new file mode 100644 index 00000000..6bbf6cdc --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkill.java @@ -0,0 +1,535 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.api; + +import dev.architectury.event.Event; +import dev.architectury.event.EventResult; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.ModuleConstants; +import io.github.manasmods.manascore.skill.impl.SkillStorage; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.ChatFormatting; +import net.minecraft.core.Holder; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; +import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.AttributeMap; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.entity.projectile.ProjectileDeflection; +import net.minecraft.world.phys.EntityHitResult; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * This is the Registry Object for Skills. + * Extend from this Class to create your own Skills. + *

+ * To add functionality to the {@link ManasSkill}, you need to implement a listener interface. + * Those interfaces allow you to invoke a Method when an {@link Event} happens. + * The Method will only be invoked for an {@link Entity} that learned the {@link ManasSkill}. + *

+ * Skills can be learned by calling the {@link SkillStorage#learnSkill} method. + * You can simply use {@link SkillAPI#getSkillsFrom(LivingEntity)} to get the {@link SkillStorage} of an {@link Entity}. + *

+ * You're also allowed to override the {@link ManasSkill#createDefaultInstance()} method to create your own implementation + * of a {@link ManasSkillInstance}. This is required if you want to attach additional data to the {@link ManasSkill} + * (for example to allow to disable a skill or make the skill gain exp on usage). + */ +public class ManasSkill { + protected final Map, AttributeTemplate> attributeModifiers = new Object2ObjectOpenHashMap<>(); + public ManasSkill() { + + } + + /** + * Used to create a {@link ManasSkillInstance} of this Skill. + *

+ * Override this Method to use your extended version of {@link ManasSkillInstance} + */ + public ManasSkillInstance createDefaultInstance() { + return new ManasSkillInstance(this); + } + + /** + * Used to get the {@link ResourceLocation} id of this skill. + */ + @Nullable + public ResourceLocation getRegistryName() { + return SkillAPI.getSkillRegistry().getId(this); + } + + /** + * Used to get the {@link MutableComponent} name of this skill for translation. + */ + @Nullable + public MutableComponent getName() { + final ResourceLocation id = getRegistryName(); + if (id == null) return null; + return Component.translatable(String.format("%s.skill.%s", id.getNamespace(), id.getPath().replace('/', '.'))); + } + + public MutableComponent getChatDisplayName(boolean withDescription) { + Style style = Style.EMPTY.withColor(ChatFormatting.GRAY); + if (withDescription) { + MutableComponent hoverMessage = this.getName().append("\n"); + hoverMessage.append(this.getSkillDescription().withStyle(ChatFormatting.GRAY)); + hoverMessage.append("\n").append(Component.literal(SkillAPI.getSkillRegistry().getId(this).toString()).withStyle(ChatFormatting.DARK_GRAY)); + style = style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverMessage)); + } + + MutableComponent component = Component.literal("[").append(this.getName()).append("]"); + return component.withStyle(style); + } + + /** + * Used to get the {@link ResourceLocation} of this skill's icon texture. + */ + @Nullable + public ResourceLocation getSkillIcon() { + ResourceLocation id = this.getRegistryName(); + if (id == null) return null; + return ResourceLocation.fromNamespaceAndPath(id.getNamespace(), "icons/skills/" + id.getPath()); + } + + /** + * Used to get the {@link MutableComponent} description of this skill for translation. + */ + public MutableComponent getSkillDescription() { + ResourceLocation id = this.getRegistryName(); + if (id == null) return Component.empty(); + return Component.translatable(String.format("%s.skill.%s.description", id.getNamespace(), id.getPath().replace('/', '.'))); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ManasSkill skill = (ManasSkill) o; + return Objects.equals(this.getRegistryName(), skill.getRegistryName()); + } + + /** + * Determine if the {@link ManasSkillInstance} of this Skill can be used by {@link LivingEntity}. + * + * @param instance Affected {@link ManasSkillInstance} + * @param user Affected {@link LivingEntity} owning this Skill. + * @return false will stop {@link LivingEntity} from using any feature of the skill. + */ + public boolean canInteractSkill(ManasSkillInstance instance, LivingEntity user) { + return true; + } + + /** + * @return the maximum number of ticks that this skill can be held down with the skill activation button. + *

+ */ + public int getMaxHeldTime(ManasSkillInstance instance, LivingEntity entity) { + return 72000; + } + + /** + * Determine if this skill can be toggled. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} owning this Skill. + * @return false if this skill is not toggleable. + */ + public boolean canBeToggled(ManasSkillInstance instance, LivingEntity entity) { + return false; + } + + /** + * Determine if a mode of this skill can still be activated when on cooldown + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} owning this Skill. + * @return false if this skill cannot ignore cooldown. + */ + public boolean canIgnoreCoolDown(ManasSkillInstance instance, LivingEntity entity, int mode) { + return false; + } + + /** + * Determine if this skill's {@link ManasSkill#onTick} can be executed. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} owning this Skill. + * @return false if this skill cannot tick. + */ + public boolean canTick(ManasSkillInstance instance, LivingEntity entity) { + return false; + } + + /** + * Determine if this skill's {@link ManasSkill#onScroll} can be executed. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} owning this Skill. + * @return false if this skill cannot be scrolled. + */ + public boolean canScroll(ManasSkillInstance instance, LivingEntity entity, int mode) { + return false; + } + + /** + * Determine if this skill's {@link ManasSkill#onRelease} should be executed when onHeld is unnaturally interrupted. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} owning this Skill. + * @return true if this skill should execute the method. + */ + public boolean shouldTriggerReleaseOnHeldInterrupt(ManasSkillInstance instance, LivingEntity entity, int keyNumber, int mode) { + return false; + } + + /** + * @return the number of modes that this skill can have. + */ + public int getModes(ManasSkillInstance instance) { + return 1; + } + + /** + * @return the maximum mastery points that this skill can have. + */ + public int getMaxMastery() { + return 100; + } + + /** + * Determine if the {@link ManasSkillInstance} of this Skill is mastered by {@link LivingEntity} owning it. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} owning this Skill. + * @return true to will mark this Skill is mastered, which can be used for increase stats or additional features/modes. + */ + public boolean isMastered(ManasSkillInstance instance, LivingEntity entity) { + return instance.getMastery() >= getMaxMastery(); + } + + /** + * Increase the mastery points for {@link ManasSkillInstance} of this Skill if not mastered. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public void addMasteryPoint(ManasSkillInstance instance, LivingEntity entity) { + this.addMasteryPoint(instance, entity, 1); + } + + public void addMasteryPoint(ManasSkillInstance instance, LivingEntity entity, double point) { + if (this.isMastered(instance, entity)) return; + Changeable newMastery = Changeable.of(Math.min(instance.getMastery() + point, this.getMaxMastery())); + EventResult result = SkillEvents.SKILL_MASTERY.invoker().master(instance, entity, newMastery); + if (result.isFalse()) return; + instance.setMastery(newMastery.get()); + if (this.isMastered(instance, entity)) instance.onSkillMastered(entity); + } + + /** + * Adds an attribute modifier to this skill. This method can be called for more than one attribute. + * The attributes are applied to an entity when the skill is held and removed when it stops being held. + *

+ */ + public void addHeldAttributeModifier(Holder holder, ResourceLocation resourceLocation, double amount, AttributeModifier.Operation operation) { + this.attributeModifiers.put(holder, new AttributeTemplate(resourceLocation, amount, operation)); + } + + public void addHeldAttributeModifier(Holder holder, String id, double amount, AttributeModifier.Operation operation) { + this.attributeModifiers.put(holder, new AttributeTemplate(id, amount, operation)); + } + + /** + * @return the amplifier for each attribute template that this skill applies. + *

+ * @param entity Affected {@link LivingEntity} owning this Skill. + * @param instance Affected {@link ManasSkillInstance} + * @param holder Affected {@link Holder} that this skill provides. + * @param template Affected {@link AttributeTemplate} that this skill provides for an attribute. + */ + public double getAttributeModifierAmplifier(ManasSkillInstance instance, LivingEntity entity, Holder holder, AttributeTemplate template, int mode) { + return 1; + } + + /** + * Applies the attribute modifiers of this skill on the {@link LivingEntity} holding the skill activation button. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + * @param instance Affected {@link ManasSkillInstance} + */ + public void addHeldAttributeModifiers(ManasSkillInstance instance, LivingEntity entity, int mode) { + if (this.attributeModifiers.isEmpty()) return; + + AttributeMap attributeMap = entity.getAttributes(); + for (Map.Entry, AttributeTemplate> entry : this.attributeModifiers.entrySet()) { + AttributeInstance attributeInstance = attributeMap.getInstance(entry.getKey()); + + if (attributeInstance == null) continue; + attributeInstance.removeModifier(entry.getValue().id()); + attributeInstance.addOrUpdateTransientModifier(entry.getValue().create(instance.getAttributeModifierAmplifier(entity, entry.getKey(), entry.getValue(), mode))); + } + } + + /** + * Removes the attribute modifiers of this skill from the {@link LivingEntity} holding the skill activation button. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public void removeAttributeModifiers(ManasSkillInstance instance, LivingEntity entity, int mode) { + if (this.attributeModifiers.isEmpty()) return; + AttributeMap map = entity.getAttributes(); + List dirtyInstances = new ArrayList<>(); + + for (Map.Entry, AttributeTemplate> entry : this.attributeModifiers.entrySet()) { + AttributeInstance attributeInstance = map.getInstance(entry.getKey()); + if (attributeInstance == null) continue; + attributeInstance.removeModifier(entry.getValue().id()); + dirtyInstances.add(attributeInstance); + } + + if (!dirtyInstances.isEmpty() && entity instanceof ServerPlayer player) { + ClientboundUpdateAttributesPacket packet = new ClientboundUpdateAttributesPacket(player.getId(), dirtyInstances); + player.connection.send(packet); + } + } + + /** + * Called when the {@link LivingEntity} owing this Skill toggles it on. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public void onToggleOn(ManasSkillInstance instance, LivingEntity entity) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} owning this Skill toggles it off. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public void onToggleOff(ManasSkillInstance instance, LivingEntity entity) { + // Override this method to add your own logic + } + + /** + * Called every tick of the {@link LivingEntity} owning this Skill. + * + * @param instance Affected {@link ManasSkillInstance} + * @param living Affected {@link LivingEntity} owning this Skill. + */ + public void onTick(ManasSkillInstance instance, LivingEntity living) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} owning this Skill presses the skill activation button. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public void onPressed(ManasSkillInstance instance, LivingEntity entity, int keyNumber, int mode) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} owning this Skill holds the skill activation button. + * + * @param instance Affected {@link ManasSkillInstance} + * @param living Affected {@link LivingEntity} owning this Skill. + * @return true to continue ticking this Skill. + */ + public boolean onHeld(ManasSkillInstance instance, LivingEntity living, int heldTicks, int mode) { + // Override this method to add your own logic + return false; + } + + /** + * Called when the {@link LivingEntity} owning this Skill releases the skill activation button after {@param heldTicks}. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public void onRelease(ManasSkillInstance instance, LivingEntity entity, int heldTicks, int keyNumber, int mode) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} owning this Skill scrolls the mouse when holding the skill activation buttons. + * + * @param instance Affected {@link ManasSkillInstance} + * @param living Affected {@link LivingEntity} owning this Skill. + * @param delta The scroll delta of the mouse scroll. + */ + public void onScroll(ManasSkillInstance instance, LivingEntity living, double delta, int mode) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} learns this Skill. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} learning this Skill. + */ + public void onLearnSkill(ManasSkillInstance instance, LivingEntity entity) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} forgets this Skill. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} learning this Skill. + */ + public void onForgetSkill(ManasSkillInstance instance, LivingEntity entity) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} masters this skill. + * + * @param instance Affected {@link ManasSkillInstance} + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public void onSkillMastered(ManasSkillInstance instance, LivingEntity entity) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} owning this Skill gains an effect. + * + * @see ManasSkillInstance#onEffectAdded(LivingEntity, Entity, Changeable) + */ + public boolean onEffectAdded(ManasSkillInstance instance, LivingEntity entity, @Nullable Entity source, Changeable effect) { + // Override this method to add your own logic + return true; + } + + /** + * Called when the {@link LivingEntity} owning this Skill starts to be targeted by a mob. + * + * @see ManasSkillInstance#onBeingTargeted(Changeable, LivingEntity) + */ + public boolean onBeingTargeted(ManasSkillInstance instance, Changeable target, LivingEntity owner) { + // Override this method to add your own logic + return true; + } + + /** + * Called when the {@link LivingEntity} owning this Skill starts to be attacked. + * + * @see ManasSkillInstance#onBeingDamaged(LivingEntity, DamageSource, float) + */ + public boolean onBeingDamaged(ManasSkillInstance instance, LivingEntity entity, DamageSource source, float amount) { + // Override this method to add your own logic + return true; + } + + /** + * Called when the {@link LivingEntity} owning this Skill damage another {@link LivingEntity}. + * + * @see ManasSkillInstance#onDamageEntity(LivingEntity, LivingEntity, DamageSource, Changeable) + */ + public boolean onDamageEntity(ManasSkillInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable amount) { + // Override this method to add your own logic + return true; + } + + /** + * Called when the {@link LivingEntity} owning this Skill damage another {@link LivingEntity}, + * + * @see ManasSkillInstance#onTouchEntity(LivingEntity, LivingEntity, DamageSource, Changeable) + */ + public boolean onTouchEntity(ManasSkillInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable amount) { + // Override this method to add your own logic + return true; + } + + /** + * Called when the {@link LivingEntity} owning this Skill takes damage. + * + * @see ManasSkillInstance#onTakenDamage(LivingEntity, DamageSource, Changeable) + */ + public boolean onTakenDamage(ManasSkillInstance instance, LivingEntity owner, DamageSource source, Changeable amount) { + // Override this method to add your own logic + return true; + } + + /** + * Called when the {@link LivingEntity} is hit by a projectile. + */ + public void onProjectileHit(ManasSkillInstance instance, LivingEntity living, EntityHitResult hitResult, Projectile projectile, Changeable deflection, Changeable result) { + // Override this method to add your own logic + } + + /** + * Called when the {@link LivingEntity} owning this Skill dies. + * + * @see ManasSkillInstance#onDeath(LivingEntity, DamageSource) + */ + public boolean onDeath(ManasSkillInstance instance, LivingEntity owner, DamageSource source) { + // Override this method to add your own logic + return true; + } + + /** + * Called when the {@link ServerPlayer} owning this Skill respawns. + */ + public void onRespawn(ManasSkillInstance instance, ServerPlayer owner, boolean conqueredEnd) { + // Override this method to add your own logic + } + + /** + * Attribute Template for easier attribute modifier implementation. + */ + public static record AttributeTemplate(ResourceLocation id, double amount, AttributeModifier.Operation operation) { + public AttributeTemplate(ResourceLocation id, double amount, AttributeModifier.Operation operation) { + this.id = id; + this.amount = amount; + this.operation = operation; + } + + public AttributeTemplate(String id, double amount, AttributeModifier.Operation operation) { + this(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, id), amount, operation); + } + + public AttributeModifier create(double i) { + return new AttributeModifier(this.id, this.amount * i, this.operation); + } + + public AttributeModifier create(ResourceLocation location, double i) { + return new AttributeModifier(location, this.amount * i, this.operation); + } + + public ResourceLocation id() { + return this.id; + } + + public double amount() { + return this.amount; + } + + public AttributeModifier.Operation operation() { + return this.operation; + } + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java new file mode 100644 index 00000000..de3cbc32 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/ManasSkillInstance.java @@ -0,0 +1,709 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.api; + +import dev.architectury.registry.registries.RegistrySupplier; +import io.github.manasmods.manascore.network.api.util.Changeable; +import lombok.Getter; +import net.minecraft.core.Holder; +import net.minecraft.core.NonNullList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.tags.TagKey; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.entity.projectile.ProjectileDeflection; +import net.minecraft.world.phys.EntityHitResult; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.stream.Collectors; + +public class ManasSkillInstance { + private int removeTime = -1; + private double masteryPoint = 0; + private boolean toggled = false; + @Getter + private List cooldownList; + private Map subInstances; + @Nullable + private ManasSkill parentSkill = null; + @Nullable + private CompoundTag tag = null; + @Getter + private boolean dirty = false; + protected final RegistrySupplier skillRegistryObject; + + protected ManasSkillInstance(ManasSkill skill) { + this.skillRegistryObject = SkillAPI.getSkillRegistry().delegate(SkillAPI.getSkillRegistry().getId(skill)); + this.subInstances = new HashMap<>(); + this.cooldownList = NonNullList.withSize(this.getModes(), 0); + } + + /** + * Used to get the {@link ManasSkill} type of this Instance. + */ + public ManasSkill getSkill() { + return skillRegistryObject.get(); + } + + public ResourceLocation getSkillId() { + return this.skillRegistryObject.getId(); + } + + /** + * Used to create an exact copy of the current instance. + */ + public ManasSkillInstance copy() { + ManasSkillInstance clone = new ManasSkillInstance(getSkill()); + clone.dirty = this.dirty; + clone.cooldownList = new ArrayList<>(this.cooldownList); + clone.removeTime = this.removeTime; + clone.masteryPoint = this.masteryPoint; + clone.toggled = this.toggled; + if (this.tag != null) clone.tag = this.tag.copy(); + clone.subInstances = this.subInstances; + clone.parentSkill = this.parentSkill; + return clone; + } + + /** + * This method is used to ensure that all required information are stored. + *

+ * Override {@link ManasSkillInstance#serialize(CompoundTag)} to store your custom Data. + */ + public final CompoundTag toNBT() { + CompoundTag nbt = new CompoundTag(); + nbt.putString("skill", this.getSkillId().toString()); + serialize(nbt); + return nbt; + } + + /** + * Can be used to save custom data. + * + * @param nbt Tag with data from {@link ManasSkillInstance#fromNBT(CompoundTag)} + */ + public CompoundTag serialize(CompoundTag nbt) { + nbt.putInt("RemoveTime", this.removeTime); + nbt.putDouble("Mastery", this.masteryPoint); + nbt.putBoolean("Toggled", this.toggled); + nbt.putIntArray("CooldownList", this.cooldownList); + + if (this.tag != null) nbt.put("tag", this.tag.copy()); + if (this.parentSkill != null && this.parentSkill.getRegistryName() != null) + nbt.putString("parentSkill", this.parentSkill.getRegistryName().toString()); + else if (nbt.contains("parentSkill")) nbt.remove("parentSkill"); + + if (!this.subInstances.isEmpty()) { + CompoundTag subInstances = new CompoundTag(); + for (Map.Entry instance : this.subInstances.entrySet()) + subInstances.put(instance.getValue().getSkillId().toString(), instance.getValue().toNBT()); + nbt.put("subInstances", subInstances); + } + return nbt; + } + + /** + * Can be used to load custom data. + */ + public void deserialize(CompoundTag tag) { + this.removeTime = tag.getInt("RemoveTime"); + this.masteryPoint = tag.getDouble("Mastery"); + this.toggled = tag.getBoolean("Toggled"); + this.cooldownList = Arrays.stream(tag.getIntArray("CooldownList")).boxed().collect(Collectors.toList()); + + if (tag.contains("tag", 10)) this.tag = tag.getCompound("tag"); + if (tag.contains("parentSkill")) + this.parentSkill = SkillAPI.getSkillRegistry().get(ResourceLocation.tryParse(tag.getString("parentSkill"))); + + if (tag.contains("subInstances")) { + this.subInstances.clear(); + CompoundTag subInstances = tag.getCompound("subInstances"); + subInstances.getAllKeys().forEach(s -> { + ManasSkillInstance skill = ManasSkillInstance.fromNBT(subInstances.getCompound(s)); + this.subInstances.put(skill.getSkill(), skill); + }); + } + } + + /** + * Can be used to load a {@link ManasSkillInstance} from a {@link CompoundTag}. + *

+ * The {@link CompoundTag} has to be created though {@link ManasSkillInstance#toNBT()} + */ + public static ManasSkillInstance fromNBT(CompoundTag tag) throws NullPointerException { + ResourceLocation skillLocation = ResourceLocation.tryParse(tag.getString("skill")); + ManasSkill skill = SkillAPI.getSkillRegistry().get(skillLocation); + if (skill == null) throw new IllegalArgumentException("Skill not found in registry: " + skillLocation); + ManasSkillInstance instance = skill.createDefaultInstance(); + instance.deserialize(tag); + return instance; + } + + /** + * Marks the current instance as dirty. + */ + public void markDirty() { + this.dirty = true; + } + + /** + * This Method is invoked to indicate that a {@link ManasSkillInstance} has been synced with the clients. + *

+ * Do NOT use that method on our own! + */ + @ApiStatus.Internal + public void resetDirty() { + this.dirty = false; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ManasSkillInstance instance = (ManasSkillInstance) o; + return this.getSkillId().equals(instance.getSkillId()) && + skillRegistryObject.getRegistryKey().equals(instance.skillRegistryObject.getRegistryKey()); + } + + @Override + public int hashCode() { + return Objects.hash(this.getSkillId(), skillRegistryObject.getRegistryKey()); + } + + /** + * Determine if this instance can be used by {@link LivingEntity}. + * + * @param user Affected {@link LivingEntity} + * @return false will stop {@link LivingEntity} from using any feature of the skill. + */ + public boolean canInteractSkill(LivingEntity user) { + return this.getSkill().canInteractSkill(this, user); + } + + /** + * @return the maximum number of ticks that this skill can be held down with the skill activation button. + */ + public int getMaxHeldTime(LivingEntity entity) { + return this.getSkill().getMaxHeldTime(this, entity); + } + + /** + * Determine if the {@link ManasSkill} type of this instance can be toggled. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + * @return false if this skill is not toggleable. + */ + public boolean canBeToggled(LivingEntity entity) { + return this.getSkill().canBeToggled(this, entity); + } + + /** + * Determine if the {@link ManasSkill} type of this instance can still be activated when on cooldown. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + * @return false if this skill cannot ignore cooldown. + */ + public boolean canIgnoreCoolDown(LivingEntity entity, int mode) { + return this.getSkill().canIgnoreCoolDown(this, entity, mode); + } + + /** + * Determine if this instance's {@link ManasSkillInstance#onTick} can be executed. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + * @return false if this skill cannot tick. + */ + public boolean canTick(LivingEntity entity) { + return this.getSkill().canTick(this, entity); + } + + /** + * Determine if this instance's {@link ManasSkillInstance#onScroll} can be executed. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + * @return false if this skill cannot be scrolled. + */ + public boolean canScroll(LivingEntity entity, int mode) { + return this.getSkill().canScroll(this, entity, mode); + } + + /** + * Determine if this instance's {@link ManasSkillInstance#onRelease} should be executed when onHeld is unnaturally interrupted. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + * @return true if this skill should execute the method. + */ + public boolean shouldTriggerReleaseOnHeldInterrupt(LivingEntity entity, int keyNumber, int mode) { + return this.getSkill().shouldTriggerReleaseOnHeldInterrupt(this, entity, keyNumber, mode); + } + + /** + * @return the number of modes that this skill instance has. + */ + public int getModes() { + return this.getSkill().getModes(this); + } + + /** + * @return the maximum mastery points that this skill instance can have. + */ + public int getMaxMastery() { + return this.getSkill().getMaxMastery(); + } + + /** + * Determine if the {@link ManasSkill} type of this instance is mastered by {@link LivingEntity} owning it. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public boolean isMastered(LivingEntity entity) { + return this.getSkill().isMastered(this, entity); + } + + /** + * Increase the mastery point of the {@link ManasSkill} type of this instance. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public void addMasteryPoint(LivingEntity entity) { + this.getSkill().addMasteryPoint(this, entity); + } + + public void addMasteryPoint(LivingEntity entity, double point) { + this.getSkill().addMasteryPoint(this, entity, point); + } + + /** + * @return the mastery point of the {@link ManasSkill} type of this instance. + */ + public double getMastery() { + return this.masteryPoint; + } + + /** + * Set the mastery point of the {@link ManasSkill} type of this instance. + */ + public void setMastery(double point) { + this.masteryPoint = point; + markDirty(); + } + + /** + * @return the cooldown of a specific mode of this instance. + */ + public int getCoolDown(int mode) { + if (mode < 0 || mode >= cooldownList.size()) return 0; + return this.cooldownList.get(mode); + } + + /** + * @return if a specific mode of this instance is on cooldown. + */ + public boolean onCoolDown(int mode) { + if (mode < 0 || mode >= cooldownList.size()) return false; + return this.cooldownList.get(mode) > 0; + } + + /** + * Set the cooldown of a specific mode of this instance. + */ + public void setCoolDown(int coolDown, int mode) { + if (mode < 0 || mode >= cooldownList.size()) return; + this.cooldownList.set(mode, coolDown); + markDirty(); + } + + /** + * Set the cooldown of every mode of this instance. + */ + public void setCoolDowns(int coolDown) { + Collections.fill(this.cooldownList, coolDown); + markDirty(); + } + + /** + * Decrease the cooldown of a specific mode of this instance. + */ + public void decreaseCoolDown(int coolDown, int mode) { + if (mode < 0 || mode >= cooldownList.size()) return; + this.cooldownList.set(mode, Math.max(0, this.cooldownList.get(mode) - coolDown)); + markDirty(); + } + + /** + * Edit the entire cooldown list of this instance. + */ + public void setCoolDownList(List list) { + this.cooldownList = list; + } + + /** + * @return if this skill instance is temporary, which should be removed when its time runs out. + */ + public boolean isTemporarySkill() { + return this.removeTime != -1; + } + + /** + * @return the removal time of this instance. + */ + public int getRemoveTime() { + return this.removeTime; + } + + /** + * @return if this skill instance needs to be removed. + */ + public boolean shouldRemove() { + return this.removeTime == 0; + } + + /** + * Set the remove time of this instance. + */ + public void setRemoveTime(int removeTime) { + this.removeTime = removeTime; + markDirty(); + } + + /** + * Decrease the remove time of this instance. + */ + public void decreaseRemoveTime(int time) { + if (this.removeTime > 0) { + this.removeTime = Math.max(0, this.removeTime - time); + markDirty(); + } + } + + /** + * @return if this instance is toggled. + */ + public boolean isToggled() { + return this.toggled; + } + + /** + * Toggle on/off this instance. + */ + public void setToggled(boolean toggled) { + this.toggled = toggled; + markDirty(); + } + + /** + * @return compound tag of this instance. + */ + @Nullable + public CompoundTag getTag() { + return this.tag; + } + + /** + * Used to add/create additional tags for this instance. + * + * @return compound tag of this instance or create if null. + */ + public CompoundTag getOrCreateTag() { + if (this.tag == null) { + this.setTag(new CompoundTag()); + this.markDirty(); + } + return this.tag; + } + + /** + * Used to add/create additional tags for this instance. + * Set the tag of this instance. + */ + public void setTag(@Nullable CompoundTag tag) { + this.tag = tag; + markDirty(); + } + + /** + * @return the map of sub-instances of this instance. + */ + public Map getSubInstances() { + return this.subInstances; + } + + /** + * Add sub-instance to this instance. + */ + public void addSubInstance(ManasSkillInstance instance) { + instance.setParentSkill(this.getSkill()); + this.subInstances.put(instance.getSkill(), instance); + markDirty(); + } + + /** + * Remove sub-instance from this instance. + */ + public void removeSubInstance(ManasSkill instance) { + this.subInstances.remove(instance); + markDirty(); + } + + /** + * @return the parent skill of this instance. + */ + public ManasSkill getParentSkill() { + return this.parentSkill; + } + + /** + * @return if this skill is a sub-instance. + */ + public boolean isSubInstance() { + return this.parentSkill != null; + } + + /** + * Set the parent skill of this instance. + */ + public void setParentSkill(ManasSkill skill) { + this.parentSkill = skill; + markDirty(); + } + + /** + * @return the amplifier for each attribute modifier that this instance applies. + *

+ * @param entity Affected {@link LivingEntity} owning this Skill. + * @param holder Affected {@link Holder} that this skill provides. + * @param template Affected {@link ManasSkill.AttributeTemplate} that this skill provides for an attribute. + */ + public double getAttributeModifierAmplifier(LivingEntity entity, Holder holder, ManasSkill.AttributeTemplate template, int mode) { + return this.getSkill().getAttributeModifierAmplifier(this, entity, holder, template, mode); + } + + /** + * Applies the attribute modifiers of this instance on the {@link LivingEntity} holding the skill activation button. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public void addHeldAttributeModifiers(LivingEntity entity, int mode) { + this.getSkill().addHeldAttributeModifiers(this, entity, mode); + } + + /** + * Removes the attribute modifiers of this instance from the {@link LivingEntity} holding the skill activation button. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public void removeAttributeModifiers(LivingEntity entity, int mode) { + this.getSkill().removeAttributeModifiers(this, entity, mode); + } + + /** + * Called when the {@link LivingEntity} owning this Skill toggles this {@link ManasSkill} type of this instance on. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public void onToggleOn(LivingEntity entity) { + this.getSkill().onToggleOn(this, entity); + } + + /** + * Called when the {@link LivingEntity} owning this Skill toggles this {@link ManasSkill} type of this instance off. + * + * @param entity Affected {@link LivingEntity} owning this instance. + */ + public void onToggleOff(LivingEntity entity) { + this.getSkill().onToggleOff(this, entity); + } + + /** + * Called every tick if this instance is obtained by {@link LivingEntity}. + * + * @param living Affected {@link LivingEntity} owning this instance. + */ + public void onTick(LivingEntity living) { + this.getSkill().onTick(this, living); + } + + /** + * Called when the {@link LivingEntity} owning this Skill presses the skill activation button. + * + * @param entity Affected {@link LivingEntity} owning this instance. + * @param keyNumber The key number that was pressed. + * @param mode The mode that was activated. + */ + public void onPressed(LivingEntity entity, int keyNumber, int mode) { + this.getSkill().onPressed(this, entity, keyNumber, mode); + } + + /** + * Called when the {@link LivingEntity} owning this Skill holds the skill activation button. + * + * @param entity Affected {@link LivingEntity} owning this instance. + * @param heldTicks The number of ticks the skill activation button is being held down. + * @param mode The mode that is being held down. + * @return true to continue ticking this instance. + */ + public boolean onHeld(LivingEntity entity, int heldTicks, int mode) { + return this.getSkill().onHeld(this, entity, heldTicks, mode); + } + + /** + * Called when the {@link LivingEntity} owning this Skill releases the skill activation button after {@param heldTicks}. + * + * @param entity Affected {@link LivingEntity} owning this instance. + * @param heldTicks The number of ticks the skill activation button is held down. + * @param keyNumber The key number that was pressed. + * @param mode The mode that was activated. + */ + public void onRelease(LivingEntity entity, int heldTicks, int keyNumber, int mode) { + this.getSkill().onRelease(this, entity, heldTicks, keyNumber, mode); + } + + /** + * Called when the {@link LivingEntity} owning this Skill scrolls the mouse when holding the skill activation buttons. + * + * @param entity Affected {@link LivingEntity} owning this instance. + * @param delta The scroll delta of the mouse scroll. + * @param mode The mode that was activated. + */ + public void onScroll(LivingEntity entity, double delta, int mode) { + this.getSkill().onScroll(this, entity, delta, mode); + } + + /** + * Called when the {@link LivingEntity} learns this instance. + * + * @param entity Affected {@link LivingEntity} learning this instance. + */ + public void onLearnSkill(LivingEntity entity) { + this.getSkill().onLearnSkill(this, entity); + } + + /** + * Called when the {@link LivingEntity} forgets this instance. + * + * @param entity Affected {@link LivingEntity} learning this instance. + */ + public void onForgetSkill(LivingEntity entity) { + this.getSkill().onForgetSkill(this, entity); + } + + /** + * Called when the {@link LivingEntity} masters this instance. + * + * @param entity Affected {@link LivingEntity} owning this Skill. + */ + public void onSkillMastered(LivingEntity entity) { + this.getSkill().onSkillMastered(this, entity); + } + + /** + * Called when the {@link LivingEntity} owning this instance gains an effect. + * + * @param entity owning this instance. + */ + public boolean onEffectAdded(LivingEntity entity, @Nullable Entity source, Changeable instance) { + return this.getSkill().onEffectAdded(this, entity, source, instance); + } + + /** + * Called when the {@link LivingEntity} owning this instance starts to be targeted by a mob. + * + * @return false will stop the mob from targeting the owner. + */ + public boolean onBeingTargeted(Changeable owner, LivingEntity mob) { + return this.getSkill().onBeingTargeted(this, owner, mob); + } + + /** + * Called when the {@link LivingEntity} owning this instance starts to be attacked. + *

+ * Gets executed before {@link ManasSkillInstance#onDamageEntity} + * + * @return false will prevent the owner from taking damage. + */ + public boolean onBeingDamaged(LivingEntity entity, DamageSource source, float amount) { + return this.getSkill().onBeingDamaged(this, entity, source, amount); + } + + /** + * Called when the {@link LivingEntity} owning this instance starts attacking another {@link LivingEntity}. + *

+ * Gets executed after {@link ManasSkillInstance#onBeingDamaged}
+ * Gets executed before {@link ManasSkillInstance#onTouchEntity} + * + * @return false will prevent the owner from dealing damage + */ + public boolean onDamageEntity(LivingEntity owner, LivingEntity target, DamageSource source, Changeable amount) { + return this.getSkill().onDamageEntity(this, owner, target, source, amount); + } + + /** + * Called when the {@link LivingEntity} owning this instance hurts another {@link LivingEntity} (after effects like Barriers are consumed the damage amount). + *

+ * Gets executed after {@link ManasSkillInstance#onDamageEntity} + * Gets executed before {@link ManasSkillInstance#onTakenDamage} + * + * @return false will prevent the owner from dealing damage. + */ + public boolean onTouchEntity(LivingEntity owner, LivingEntity target, DamageSource source, Changeable amount) { + return this.getSkill().onTouchEntity(this, owner, target, source, amount); + } + + /** + * Called when the {@link LivingEntity} owning this instance takes damage. + *

+ * Gets executed after {@link ManasSkillInstance#onTouchEntity} + * + * @return false will prevent the owner from taking damage. + */ + public boolean onTakenDamage(LivingEntity owner, DamageSource source, Changeable amount) { + return this.getSkill().onTakenDamage(this, owner, source, amount); + } + + /** + * Called when the {@link LivingEntity} owning this Skill is hit by a projectile. + */ + public void onProjectileHit(LivingEntity living, EntityHitResult hitResult, Projectile projectile, Changeable deflection, Changeable result) { + this.getSkill().onProjectileHit(this, living, hitResult, projectile, deflection, result); + } + + /** + * Called when the {@link LivingEntity} owning this Skill dies. + * + * @return false will prevent the owner from dying. + */ + public boolean onDeath(LivingEntity owner, DamageSource source) { + return this.getSkill().onDeath(this, owner, source); + } + + /** + * Called when the {@link ServerPlayer} owning this Skill respawns. + */ + public void onRespawn(ServerPlayer owner, boolean conqueredEnd) { + this.getSkill().onRespawn(this, owner, conqueredEnd); + } + + public MutableComponent getDisplayName() { + return this.getSkill().getName(); + } + + public MutableComponent getChatDisplayName(boolean withDescription) { + return this.getSkill().getChatDisplayName(withDescription); + } + + public boolean is(TagKey tag) { + return this.skillRegistryObject.is(tag); + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java new file mode 100644 index 00000000..5535f432 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillAPI.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.api; + +import dev.architectury.platform.Platform; +import dev.architectury.registry.registries.Registrar; +import dev.architectury.utils.Env; +import io.github.manasmods.manascore.skill.impl.network.InternalSkillPacketActions; +import io.github.manasmods.manascore.skill.impl.SkillRegistry; +import io.github.manasmods.manascore.skill.impl.SkillStorage; +import lombok.NonNull; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.LivingEntity; + +public class SkillAPI { + private SkillAPI() { + } + + /** + * This Method returns the {@link ManasSkill} Registry. + * It can be used to load {@link ManasSkill}s from the Registry. + */ + public static Registrar getSkillRegistry() { + return SkillRegistry.SKILLS; + } + + /** + * This Method returns the Registry Key of the {@link SkillRegistry}. + * It can be used to create {@link dev.architectury.registry.registries.DeferredRegister} instances + */ + public static ResourceKey> getSkillRegistryKey() { + return SkillRegistry.KEY; + } + + /** + * Can be used to load the {@link SkillStorage} from an {@link LivingEntity}. + */ + public static SkillStorage getSkillsFrom(@NonNull LivingEntity entity) { + return entity.manasCore$getStorage(SkillStorage.getKey()); + } + + /** + * Send {@link InternalSkillPacketActions#sendSkillActivationPacket} with a DistExecutor on client side. + * Used when player press a skill activation key bind. + * + * @see InternalSkillPacketActions#sendSkillActivationPacket + */ + public static void skillActivationPacket(ResourceLocation skill, int keyNumber, int mode) { + if (Platform.getEnvironment() == Env.CLIENT) { + InternalSkillPacketActions.sendSkillActivationPacket(skill, keyNumber, mode); + } + } + + /** + * Send {@link InternalSkillPacketActions#sendSkillReleasePacket} with a DistExecutor on client side. + * Used when player release a skill activation key bind. + * + * @see InternalSkillPacketActions#sendSkillReleasePacket + */ + public static void skillReleasePacket(ResourceLocation skill, int keyNumber, int mode, int heldTicks) { + if (Platform.getEnvironment() == Env.CLIENT) { + InternalSkillPacketActions.sendSkillReleasePacket(skill, keyNumber, mode, heldTicks); + } + } + + /** + * Send {@link InternalSkillPacketActions#sendSkillTogglePacket} with a DistExecutor on client side. + * Used when player press a skill toggle key bind. + * + * @see InternalSkillPacketActions#sendSkillTogglePacket + */ + public static void skillTogglePacket(ResourceLocation skill) { + if (Platform.getEnvironment() == Env.CLIENT) { + InternalSkillPacketActions.sendSkillTogglePacket(skill); + } + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java new file mode 100644 index 00000000..3f4cbaa8 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/SkillEvents.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.api; + +import dev.architectury.event.Event; +import dev.architectury.event.EventFactory; +import dev.architectury.event.EventResult; +import io.github.manasmods.manascore.network.api.util.Changeable; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.LivingEntity; + +public interface SkillEvents { + Event POST_INIT = EventFactory.createLoop(); + Event UNLOCK_SKILL = EventFactory.createEventResult(); + Event REMOVE_SKILL = EventFactory.createEventResult(); + Event ACTIVATE_SKILL = EventFactory.createEventResult(); + Event RELEASE_SKILL = EventFactory.createEventResult(); + Event TOGGLE_SKILL = EventFactory.createEventResult(); + Event SKILL_SCROLL = EventFactory.createEventResult(); + Event SKILL_SCROLL_CLIENT = EventFactory.createEventResult(); + Event SKILL_PRE_TICK = EventFactory.createEventResult(); + Event SKILL_POST_TICK = EventFactory.createLoop(); + Event SKILL_UPDATE_COOLDOWN = EventFactory.createEventResult(); + Event SKILL_MASTERY = EventFactory.createEventResult(); + Event SKILL_DAMAGE_PRE_CALCULATION = EventFactory.createEventResult(); + Event SKILL_DAMAGE_CALCULATION = EventFactory.createEventResult(); + Event SKILL_DAMAGE_POST_CALCULATION = EventFactory.createEventResult(); + + + @FunctionalInterface + interface UnlockSkillEvent { + EventResult unlockSkill(ManasSkillInstance skillInstance, LivingEntity owner, Changeable unlockMessage); + } + + @FunctionalInterface + interface RemoveSkillEvent { + EventResult removeSkill(ManasSkillInstance skillInstance, LivingEntity owner, Changeable forgetMessage); + } + + @FunctionalInterface + interface SkillActivationEvent { + EventResult activateSkill(Changeable skillInstance, LivingEntity owner, int keyNumber, int mode); + } + + @FunctionalInterface + interface SkillReleaseEvent { + EventResult releaseSkill(Changeable skillInstance, LivingEntity owner, int keyNumber, int mode, int heldTicks); + } + + @FunctionalInterface + interface SkillToggleEvent { + EventResult toggleSkill(Changeable skillInstance, LivingEntity owner); + } + + @FunctionalInterface + interface SkillScrollEvent { + EventResult scroll(Changeable skillInstance, LivingEntity owner, Changeable mode, Changeable delta); + } + + @FunctionalInterface + interface SkillScrollClientEvent { + EventResult scroll(ManasSkillInstance skillInstance, LivingEntity owner, Changeable mode, double delta); + } + + @FunctionalInterface + interface SkillTickEvent { + EventResult tick(ManasSkillInstance skillInstance, LivingEntity owner); + } + + @FunctionalInterface + interface SkillPostTickEvent { + void tick(ManasSkillInstance skillInstance, LivingEntity owner); + } + + @FunctionalInterface + interface SkillUpdateCooldownEvent { + EventResult cooldown(ManasSkillInstance skillInstance, LivingEntity owner, int mode, int currentCooldown, Changeable newCooldown); + } + + @FunctionalInterface + interface SkillMasteryEvent { + EventResult master(ManasSkillInstance instance, LivingEntity owner, Changeable newMastery); + } + + @FunctionalInterface + interface SkillDamageCalculationEvent { + EventResult calculate(Skills storage, LivingEntity entity, DamageSource source, Changeable amount); + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/Skills.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/Skills.java new file mode 100644 index 00000000..08ef8a04 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/api/Skills.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.api; + +import io.github.manasmods.manascore.skill.impl.SkillStorage; +import lombok.NonNull; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Optional; +import java.util.function.BiConsumer; + +public interface Skills { + void markDirty(); + + default void checkAndMarkDirty(ManasSkillInstance instance) { + if (instance.isDirty()) this.markDirty(); + } + + Collection getLearnedSkills(); + + /** + * Updates a skill instance and optionally synchronizes the change across the network. + *

+ * @param updatedInstance The instance to update + * @param sync If true, synchronizes the change to all clients/server + */ + void updateSkill(ManasSkillInstance updatedInstance, boolean sync); + + default boolean learnSkill(@NotNull ResourceLocation skillId) { + return learnSkill(SkillAPI.getSkillRegistry().get(skillId).createDefaultInstance()); + } + + default boolean learnSkill(@NotNull ResourceLocation skillId, MutableComponent component) { + return learnSkill(SkillAPI.getSkillRegistry().get(skillId).createDefaultInstance(), component); + } + + default boolean learnSkill(@NonNull ManasSkill skill) { + return learnSkill(skill.createDefaultInstance()); + } + + default boolean learnSkill(@NonNull ManasSkill skill, MutableComponent component) { + return learnSkill(skill.createDefaultInstance(), component); + } + + default boolean learnSkill(ManasSkillInstance instance) { + return learnSkill(instance, Component.translatable("manascore.skill.learn_skill", instance.getChatDisplayName(true))); + } + + boolean learnSkill(ManasSkillInstance instance, MutableComponent component); + + Optional getSkill(@NotNull ResourceLocation skillId); + + default Optional getSkill(@NonNull ManasSkill skill) { + return getSkill(skill.getRegistryName()); + } + + void forgetSkill(@NotNull ResourceLocation skillId, @Nullable MutableComponent component); + + default void forgetSkill(@NotNull ResourceLocation skillId) { + forgetSkill(skillId, null); + } + + default void forgetSkill(@NonNull ManasSkill skill, @Nullable MutableComponent component) { + forgetSkill(skill.getRegistryName(), component); + } + + default void forgetSkill(@NonNull ManasSkill skill) { + forgetSkill(skill.getRegistryName()); + } + + default void forgetSkill(@NonNull ManasSkillInstance instance, @Nullable MutableComponent component) { + forgetSkill(instance.getSkillId(), component); + } + + default void forgetSkill(@NonNull ManasSkillInstance instance) { + forgetSkill(instance.getSkillId()); + } + + void forEachSkill(BiConsumer skillInstanceConsumer); +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java new file mode 100644 index 00000000..896f8575 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillRegistry.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.impl; + +import dev.architectury.event.EventResult; +import dev.architectury.event.events.common.PlayerEvent; +import dev.architectury.registry.registries.Registrar; +import dev.architectury.registry.registries.RegistrarManager; +import io.github.manasmods.manascore.skill.ModuleConstants; +import io.github.manasmods.manascore.skill.api.*; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.phys.EntityHitResult; + +public class SkillRegistry { + private static final RegistrarManager MANAGER = RegistrarManager.get(ModuleConstants.MOD_ID); + private static final ResourceLocation registryId = ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "skills"); + public static final Registrar SKILLS = MANAGER.builder(registryId).syncToClients().build(); + public static final ResourceKey> KEY = createKey(SKILLS); + + public static void init() { + EntityEvents.LIVING_EFFECT_ADDED.register((entity, source, changeableTarget) -> { + Skills storage = SkillAPI.getSkillsFrom(entity); + for (ManasSkillInstance instance : storage.getLearnedSkills()) { + if (!instance.canInteractSkill(entity)) continue; + if (!instance.onEffectAdded(entity, source, changeableTarget)) { + storage.checkAndMarkDirty(instance); + return EventResult.interruptFalse(); + } else storage.checkAndMarkDirty(instance); + } + return EventResult.pass(); + }); + + EntityEvents.LIVING_CHANGE_TARGET_LATE.register((entity, changeableTarget) -> { + if (!changeableTarget.isPresent()) return EventResult.pass(); + LivingEntity owner = changeableTarget.get(); + if (owner == null) return EventResult.pass(); + + Skills storage = SkillAPI.getSkillsFrom(owner); + for (ManasSkillInstance instance : storage.getLearnedSkills()) { + if (!instance.canInteractSkill(owner)) continue; + if (!instance.onBeingTargeted(changeableTarget, entity)) { + storage.checkAndMarkDirty(instance); + return EventResult.interruptFalse(); + } else storage.checkAndMarkDirty(instance); + } + return EventResult.pass(); + }); + + EntityEvents.LIVING_ON_BEING_DAMAGED.register((entity, source, amount) -> { + Skills storage = SkillAPI.getSkillsFrom(entity); + for (ManasSkillInstance instance : storage.getLearnedSkills()) { + if (!instance.canInteractSkill(entity)) continue; + if (!instance.onBeingDamaged(entity, source, amount)) { + storage.checkAndMarkDirty(instance); + return EventResult.interruptFalse(); + } else storage.checkAndMarkDirty(instance); + } + return EventResult.pass(); + }); + + SkillEvents.SKILL_DAMAGE_PRE_CALCULATION.register((storage, target, source, amount) -> { + if (!(source.getEntity() instanceof LivingEntity owner)) return EventResult.pass(); + + Skills ownerStorage = SkillAPI.getSkillsFrom(owner); + for (ManasSkillInstance instance : ownerStorage.getLearnedSkills()) { + if (!instance.canInteractSkill(owner)) continue; + if (!instance.onDamageEntity(owner, target, source, amount)) { + storage.checkAndMarkDirty(instance); + return EventResult.interruptFalse(); + } else storage.checkAndMarkDirty(instance); + } + return EventResult.pass(); + }); + + SkillEvents.SKILL_DAMAGE_POST_CALCULATION.register((storage, target, source, amount) -> { + if (!(source.getEntity() instanceof LivingEntity owner)) return EventResult.pass(); + + Skills ownerStorage = SkillAPI.getSkillsFrom(owner); + for (ManasSkillInstance instance : ownerStorage.getLearnedSkills()) { + if (!instance.canInteractSkill(owner)) continue; + if (!instance.onTouchEntity(owner, target, source, amount)) { + storage.checkAndMarkDirty(instance); + return EventResult.interruptFalse(); + } else storage.checkAndMarkDirty(instance); + } + return EventResult.pass(); + }); + + EntityEvents.LIVING_DAMAGE.register((entity, source, amount) -> { + Skills storage = SkillAPI.getSkillsFrom(entity); + for (ManasSkillInstance instance : storage.getLearnedSkills()) { + if (!instance.canInteractSkill(entity)) continue; + if (!instance.onTakenDamage(entity, source, amount)) { + storage.checkAndMarkDirty(instance); + return EventResult.interruptFalse(); + } else storage.checkAndMarkDirty(instance); + } + return EventResult.pass(); + }); + + EntityEvents.DEATH_EVENT_HIGH.register((entity, source) -> { + Skills storage = SkillAPI.getSkillsFrom(entity); + for (ManasSkillInstance instance : storage.getLearnedSkills()) { + if (!instance.canInteractSkill(entity)) continue; + if (!instance.onDeath(entity, source)) { + storage.checkAndMarkDirty(instance); + return EventResult.interruptFalse(); + } else storage.checkAndMarkDirty(instance); + } + return EventResult.pass(); + }); + + PlayerEvent.PLAYER_RESPAWN.register((newPlayer, conqueredEnd, removalReason) -> { + Skills storage = SkillAPI.getSkillsFrom(newPlayer); + for (ManasSkillInstance instance : storage.getLearnedSkills()) { + if (!instance.canInteractSkill(newPlayer)) continue; + instance.onRespawn(newPlayer, conqueredEnd); + storage.checkAndMarkDirty(instance); + } + }); + + EntityEvents.PROJECTILE_HIT.register((result, projectile, deflectionChangeable, hitResultChangeable) -> { + if (!(result instanceof EntityHitResult hitResult)) return; + if (!(hitResult.getEntity() instanceof LivingEntity hitEntity)) return; + + Skills storage = SkillAPI.getSkillsFrom(hitEntity); + for (ManasSkillInstance instance : storage.getLearnedSkills()) { + if (!instance.canInteractSkill(hitEntity)) continue; + instance.onProjectileHit(hitEntity, hitResult, projectile, deflectionChangeable, hitResultChangeable); + storage.checkAndMarkDirty(instance); + } + }); + } + + private SkillRegistry() { + } + + private static ResourceKey> createKey(final Registrar registrar) { + return ResourceKey.createRegistryKey(registrar.key().location()); + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java new file mode 100644 index 00000000..68166391 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/SkillStorage.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.impl; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import dev.architectury.event.EventResult; +import dev.architectury.event.events.common.EntityEvent; +import dev.architectury.event.events.common.PlayerEvent; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.ManasCoreSkill; +import io.github.manasmods.manascore.skill.ModuleConstants; +import io.github.manasmods.manascore.skill.api.*; +import io.github.manasmods.manascore.storage.api.Storage; +import io.github.manasmods.manascore.storage.api.StorageEvents; +import io.github.manasmods.manascore.storage.api.StorageKey; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.log4j.Log4j2; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; + +@Log4j2 +public class SkillStorage extends Storage implements Skills { + @Getter + private static StorageKey key = null; + public static final int INSTANCE_UPDATE = 20; + public static final int PASSIVE_SKILL = 100; + public static final Multimap tickingSkills = ArrayListMultimap.create(); + private static final String SKILL_LIST_KEY = "skills"; + + public static void init() { + StorageEvents.REGISTER_ENTITY_STORAGE.register(registry -> key = registry.register(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "skill_storage"), SkillStorage.class, LivingEntity.class::isInstance, target -> new SkillStorage((LivingEntity) target))); + + EntityEvents.LIVING_CHANGE_TARGET.register((entity, changeableTarget) -> { + if (EntityEvents.LIVING_CHANGE_TARGET_EARLY.invoker().changeTarget(entity, changeableTarget).isFalse()) return EventResult.interruptFalse(); + if (EntityEvents.LIVING_CHANGE_TARGET_LATE.invoker().changeTarget(entity, changeableTarget).isFalse()) return EventResult.interruptFalse(); + return EventResult.pass(); + }); + + EntityEvent.LIVING_HURT.register((entity, source, amount) -> { + if (EntityEvents.LIVING_PRE_DAMAGED.invoker().hurt(entity, source, amount).isFalse()) return EventResult.interruptFalse(); + if (EntityEvents.LIVING_ON_BEING_DAMAGED.invoker().hurt(entity, source, amount).isFalse()) return EventResult.interruptFalse(); + return EventResult.pass(); + }); + + EntityEvents.LIVING_HURT.register((entity, source, changeable) -> { + Skills skills = SkillAPI.getSkillsFrom(entity); + if (SkillEvents.SKILL_DAMAGE_PRE_CALCULATION.invoker().calculate(skills, entity, source, changeable).isFalse()) return EventResult.interruptFalse(); + if (SkillEvents.SKILL_DAMAGE_CALCULATION.invoker().calculate(skills, entity, source, changeable).isFalse()) return EventResult.interruptFalse(); + if (SkillEvents.SKILL_DAMAGE_POST_CALCULATION.invoker().calculate(skills, entity, source, changeable).isFalse()) return EventResult.interruptFalse(); + return EventResult.pass(); + }); + + EntityEvent.LIVING_DEATH.register((entity, source) -> { + if (EntityEvents.DEATH_EVENT_FIRST.invoker().die(entity, source).isFalse()) return EventResult.interruptFalse(); + if (EntityEvents.DEATH_EVENT_HIGH.invoker().die(entity, source).isFalse()) return EventResult.interruptFalse(); + if (EntityEvents.DEATH_EVENT_NORMAL.invoker().die(entity, source).isFalse()) return EventResult.interruptFalse(); + if (EntityEvents.DEATH_EVENT_LOW.invoker().die(entity, source).isFalse()) return EventResult.interruptFalse(); + if (EntityEvents.DEATH_EVENT_LAST.invoker().die(entity, source).isFalse()) return EventResult.interruptFalse(); + return EventResult.pass(); + }); + + EntityEvents.LIVING_POST_TICK.register(entity -> { + Level level = entity.level(); + if (level.isClientSide()) return; + SkillStorage storage = SkillAPI.getSkillsFrom(entity); + handleSkillTick(entity, level, storage); + if (entity instanceof Player player) handleSkillHeldTick(player, storage); + }); + + PlayerEvent.PLAYER_QUIT.register(SkillStorage::removeTickingSkill); + PlayerEvent.CHANGE_DIMENSION.register((player, resourceKey, resourceKey1) -> SkillStorage.removeTickingSkill(player)); + } + + private static void handleSkillTick(LivingEntity entity, Level level, Skills storage) { + MinecraftServer server = level.getServer(); + if (server == null) return; + + boolean shouldPassiveConsume = server.getTickCount() % INSTANCE_UPDATE == 0; + if (!shouldPassiveConsume) return; + checkPlayerOnlyEffects(entity, storage); + + boolean passiveSkillActivate = server.getTickCount() % PASSIVE_SKILL == 0; + if (!passiveSkillActivate) return; + + tickSkills(entity, storage); + } + + private static void tickSkills(LivingEntity entity, Skills storage) { + List tickingSkills = new ArrayList<>(); + for (ManasSkillInstance instance : storage.getLearnedSkills()) { + Optional optional = storage.getSkill(instance.getSkill()); + if (optional.isEmpty()) continue; + + ManasSkillInstance skillInstance = optional.get(); + if (!skillInstance.canInteractSkill(entity)) continue; + if (!skillInstance.canTick(entity)) continue; + if (SkillEvents.SKILL_PRE_TICK.invoker().tick(skillInstance, entity).isFalse()) continue; + tickingSkills.add(skillInstance); + } + + for (ManasSkillInstance instance : tickingSkills) { + instance.onTick(entity); + SkillEvents.SKILL_POST_TICK.invoker().tick(instance, entity); + storage.checkAndMarkDirty(instance); + } + } + + private static void checkPlayerOnlyEffects(LivingEntity entity, Skills storage) { + if (!(entity instanceof Player)) return; + List toBeRemoved = new ArrayList<>(); + + for (ManasSkillInstance instance : storage.getLearnedSkills()) { + // Update cooldown + for (int i = 0; i < instance.getModes(); i++) { + if (!instance.onCoolDown(i)) continue; + int currentCooldown = instance.getCoolDown(i); + Changeable newCooldown = Changeable.of(Math.max(0, currentCooldown - 1)); + if (!SkillEvents.SKILL_UPDATE_COOLDOWN.invoker().cooldown(instance, entity, i, currentCooldown, newCooldown).isFalse()) + instance.setCoolDown(newCooldown.get(), i); + storage.checkAndMarkDirty(instance); + } + + // Update temporary skill timer + if (!instance.isTemporarySkill()) continue; + instance.decreaseRemoveTime(1); + storage.checkAndMarkDirty(instance); + + if (!instance.shouldRemove()) continue; + toBeRemoved.add(instance); + } + + // Remove temporary skills + for (ManasSkillInstance instance : toBeRemoved) { + storage.forgetSkill(instance); + } + } + + private static void handleSkillHeldTick(Player player, SkillStorage storage) { + if (!tickingSkills.containsKey(player.getUUID())) return; + tickingSkills.get(player.getUUID()).removeIf(skill -> { + if (!skill.tick(storage, player)) { + Optional instance = storage.getSkill(skill.getSkill()); + if (instance.isEmpty()) return true; + skill.getSkill().removeAttributeModifiers(instance.get(), player, skill.getMode()); + storage.checkAndMarkDirty(instance.get()); + return true; + } else storage.markDirty(); + return false; + }); + } + + private final Map skillInstances = new ConcurrentHashMap<>(); + private boolean hasRemovedSkills = false; + + protected SkillStorage(LivingEntity holder) { + super(holder); + } + + public Collection getLearnedSkills() { + return this.skillInstances.values(); + } + + public void updateSkill(@NonNull ManasSkillInstance updatedInstance, boolean sync) { + updatedInstance.markDirty(); + this.skillInstances.put(updatedInstance.getSkillId(), updatedInstance); + if (sync) markDirty(); + } + + public boolean learnSkill(@NonNull ManasSkillInstance instance, MutableComponent component) { + if (this.skillInstances.containsKey(instance.getSkillId())) { + log.debug("Tried to register a deduplicate of {}.", instance.getSkillId()); + return false; + } + + Changeable unlockMessage = Changeable.of(component); + EventResult result = SkillEvents.UNLOCK_SKILL.invoker().unlockSkill(instance, getOwner(), unlockMessage); + if (result.isFalse()) return false; + + instance.markDirty(); + this.skillInstances.put(instance.getSkillId(), instance); + if (unlockMessage.isPresent()) getOwner().sendSystemMessage(unlockMessage.get()); + instance.onLearnSkill(this.getOwner()); + markDirty(); + return true; + } + + public Optional getSkill(@NonNull ResourceLocation skillId) { + return Optional.ofNullable(this.skillInstances.get(skillId)); + } + + public void forgetSkill(@NotNull ResourceLocation skillId, @Nullable MutableComponent component) { + if (!this.skillInstances.containsKey(skillId)) return; + ManasSkillInstance instance = this.skillInstances.get(skillId); + + Changeable forgetMessage = Changeable.of(component); + EventResult result = SkillEvents.REMOVE_SKILL.invoker().removeSkill(instance, getOwner(), forgetMessage); + if (result.isFalse()) return; + + if (forgetMessage.isPresent()) getOwner().sendSystemMessage(forgetMessage.get()); + instance.onForgetSkill(this.getOwner()); + instance.markDirty(); + + this.getLearnedSkills().remove(instance); + this.hasRemovedSkills = true; + markDirty(); + } + + public void forEachSkill(BiConsumer skillInstanceConsumer) { + List.copyOf(this.skillInstances.values()).forEach(skillInstance -> skillInstanceConsumer.accept(this, skillInstance)); + markDirty(); + } + + public void handleSkillRelease(ManasSkillInstance skillInstance, int heldTick, int keyNumber, int mode, boolean heldInterrupt) { + Changeable changeable = Changeable.of(skillInstance); + if (SkillEvents.RELEASE_SKILL.invoker().releaseSkill(changeable, this.getOwner(), keyNumber, mode, heldTick).isFalse()) return; + ManasSkillInstance skill = changeable.get(); + if (skill == null) return; + + if ((heldInterrupt || skill.canInteractSkill(getOwner())) && mode < skill.getModes()) { + if (!skill.onCoolDown(mode) || skill.canIgnoreCoolDown(getOwner(), mode)) { + skill.onRelease(getOwner(), heldTick, keyNumber, mode); + this.checkAndMarkDirty(skillInstance); + } + } + + skill.removeAttributeModifiers(getOwner(), mode); + if (!heldInterrupt) { + UUID ownerID = getOwner().getUUID(); + if (tickingSkills.containsKey(ownerID)) + tickingSkills.get(ownerID).removeIf(tickingSkill -> tickingSkill.matches(skill.getSkill(), mode)); + } + this.checkAndMarkDirty(skillInstance); + } + + @Override + public void save(CompoundTag data) { + ListTag skillList = new ListTag(); + this.skillInstances.values().forEach(instance -> { + skillList.add(instance.toNBT()); + instance.resetDirty(); + }); + data.put(SKILL_LIST_KEY, skillList); + } + + @Override + public void load(CompoundTag data) { + if (data.contains("resetExistingData")) { + this.skillInstances.clear(); + } + + for (Tag tag : data.getList(SKILL_LIST_KEY, Tag.TAG_COMPOUND)) { + try { + ManasSkillInstance instance = ManasSkillInstance.fromNBT((CompoundTag) tag); + this.skillInstances.put(instance.getSkillId(), instance); + } catch (Exception e) { + ManasCoreSkill.LOG.error("Failed to load skill instance from NBT", e); + } + } + } + + @Override + public void saveOutdated(CompoundTag data) { + if (this.hasRemovedSkills) { + this.hasRemovedSkills = false; + data.putBoolean("resetExistingData", true); + super.saveOutdated(data); + } else { + ListTag skillList = new ListTag(); + for (ManasSkillInstance instance : this.skillInstances.values()) { + if (!instance.isDirty()) continue; + skillList.add(instance.toNBT()); + instance.resetDirty(); + } + data.put(SKILL_LIST_KEY, skillList); + } + } + + protected LivingEntity getOwner() { + return (LivingEntity) this.holder; + } + + public static void removeTickingSkill(Player player) { + Multimap multimap = tickingSkills; + if (multimap.containsKey(player.getUUID())) { + for (TickingSkill skill : multimap.get(player.getUUID())) { + Optional instance = SkillAPI.getSkillsFrom(player).getSkill(skill.getSkill()); + if (instance.isEmpty()) continue; + skill.getSkill().removeAttributeModifiers(instance.get(), player, skill.getMode()); + } + multimap.removeAll(player.getUUID()); + } + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/TickingSkill.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/TickingSkill.java new file mode 100644 index 00000000..2b84e4a3 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/TickingSkill.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.impl; + +import io.github.manasmods.manascore.skill.api.ManasSkill; +import io.github.manasmods.manascore.skill.api.ManasSkillInstance; +import lombok.Getter; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; + +import java.util.Collection; +import java.util.Optional; +import java.util.UUID; + +/** + * This is the Registry Object for Ticking Skills when a {@link ManasSkill} is held down in specific mode. + */ +public class TickingSkill { + private int duration = 0; + @Getter + private final ManasSkill skill; + @Getter + private final int mode; + @Getter + private final int keyNumber; + public TickingSkill(ManasSkill skill, int mode, int keyNumber) { + this.skill = skill; + this.mode = mode; + this.keyNumber = keyNumber; + } + + public boolean tick(SkillStorage storage, LivingEntity entity) { + if (!entity.isAlive()) return false; + Optional optional = storage.getSkill(skill); + if (optional.isEmpty()) return false; + + ManasSkillInstance instance = optional.get(); + if (this.reachedMaxDuration(instance, entity) || !instance.canInteractSkill(entity)) { + if (instance.shouldTriggerReleaseOnHeldInterrupt(entity, keyNumber, mode)) + storage.handleSkillRelease(instance, this.duration, this.keyNumber, this.mode, true); + return false; + } + return instance.onHeld(entity, this.duration++, this.mode); + } + + public boolean reachedMaxDuration(ManasSkillInstance instance, LivingEntity entity) { + int maxDuration = instance.getMaxHeldTime(entity); + if (maxDuration == -1) return false; + return this.duration >= maxDuration; + } + + public boolean matches(ManasSkill skill, int mode) { + return this.skill == skill && this.mode == mode; + } + + public boolean matches(ManasSkill skill, int mode, int keyNumber) { + return this.skill == skill && this.mode == mode && this.keyNumber == keyNumber; + } + + public static void addTickingSkill(Player player, ManasSkill skill, int mode, int keyNumber) { + UUID uuid = player.getUUID(); + Collection skills = SkillStorage.tickingSkills.get(uuid); + for (TickingSkill tickingSkill : skills) if (tickingSkill.matches(skill, mode)) return; + SkillStorage.tickingSkills.put(uuid, new TickingSkill(skill, mode, keyNumber)); + } + + public static boolean isTickingSkill(LivingEntity entity, ManasSkill skill, int mode) { + UUID uuid = entity.getUUID(); + for (TickingSkill tickingSkill : SkillStorage.tickingSkills.get(uuid)) { + if (tickingSkill.matches(skill, mode)) return true; + } + return false; + } + + public static boolean isTickingSkill(LivingEntity entity, ManasSkill skill) { + UUID uuid = entity.getUUID(); + for (TickingSkill tickingSkill : SkillStorage.tickingSkills.get(uuid)) { + if (tickingSkill.getSkill() == skill) return true; + } + return false; + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/data/SkillTagProvider.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/data/SkillTagProvider.java new file mode 100644 index 00000000..e876e8ab --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/data/SkillTagProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.impl.data; + +import io.github.manasmods.manascore.skill.api.ManasSkill; +import io.github.manasmods.manascore.skill.impl.SkillRegistry; +import net.minecraft.core.HolderLookup; +import net.minecraft.data.PackOutput; +import net.minecraft.data.tags.IntrinsicHolderTagsProvider; + +import java.util.concurrent.CompletableFuture; + +/** + * Provider for skill tags that allows registration of tags for {@link ManasSkill} entities. + * This class handles both synchronous and parent-dependent tag generation. + */ +public abstract class SkillTagProvider extends IntrinsicHolderTagsProvider { + public SkillTagProvider(PackOutput output, CompletableFuture lookupProvider) { + super(output, SkillRegistry.KEY, lookupProvider, manasSkill -> SkillRegistry.SKILLS.getKey(manasSkill).orElseThrow()); + } + + public SkillTagProvider(PackOutput output, CompletableFuture lookupProvider, CompletableFuture> parentProvider) { + super(output, SkillRegistry.KEY, lookupProvider, parentProvider, manasSkill -> SkillRegistry.SKILLS.getKey(manasSkill).orElseThrow()); + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/InternalSkillPacketActions.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/InternalSkillPacketActions.java new file mode 100644 index 00000000..b03ffc9d --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/InternalSkillPacketActions.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.impl.network; + +import dev.architectury.networking.NetworkManager; +import io.github.manasmods.manascore.skill.api.ManasSkill; +import io.github.manasmods.manascore.skill.api.SkillEvents; +import io.github.manasmods.manascore.skill.impl.network.c2s.RequestSkillActivationPacket; +import io.github.manasmods.manascore.skill.impl.network.c2s.RequestSkillReleasePacket; +import io.github.manasmods.manascore.skill.impl.network.c2s.RequestSkillTogglePacket; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; + +public class InternalSkillPacketActions { + private InternalSkillPacketActions() { + } + + /** + * This Method filters {@link ManasSkill} that meets the conditions of the {@link SkillEvents.SkillActivationEvent} then send packet for them. + * Only executes on client using the dist executor. + */ + public static void sendSkillActivationPacket(ResourceLocation skillId, int keyNumber, int mode) { + var minecraft = Minecraft.getInstance(); + Player player = minecraft.player; + if (player == null) return; + NetworkManager.sendToServer(new RequestSkillActivationPacket(keyNumber, skillId, mode)); + } + + /** + * This Method filters {@link ManasSkill} that meets the conditions of the {@link SkillEvents.SkillReleaseEvent} then send packet for them. + * Only executes on client using the dist executor. + */ + public static void sendSkillReleasePacket(ResourceLocation skillId, int keyNumber, int mode, int heldTicks) { + var minecraft = Minecraft.getInstance(); + Player player = minecraft.player; + if (player == null || heldTicks < 0) return; + NetworkManager.sendToServer(new RequestSkillReleasePacket(heldTicks, keyNumber, mode, skillId)); + } + + /** + * This Method filters {@link ManasSkill} that meets the conditions of the {@link SkillEvents.SkillToggleEvent} then send packet for them. + * Only executes on client using the dist executor. + */ + public static void sendSkillTogglePacket(ResourceLocation skillId) { + var minecraft = Minecraft.getInstance(); + Player player = minecraft.player; + if (player == null) return; + NetworkManager.sendToServer(new RequestSkillTogglePacket(skillId)); + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/ManasSkillNetwork.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/ManasSkillNetwork.java new file mode 100644 index 00000000..d1c41539 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/ManasSkillNetwork.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.impl.network; + +import io.github.manasmods.manascore.network.api.util.NetworkUtils; +import io.github.manasmods.manascore.skill.impl.network.c2s.RequestSkillActivationPacket; +import io.github.manasmods.manascore.skill.impl.network.c2s.RequestSkillReleasePacket; +import io.github.manasmods.manascore.skill.impl.network.c2s.RequestSkillScrollPacket; +import io.github.manasmods.manascore.skill.impl.network.c2s.RequestSkillTogglePacket; + +public class ManasSkillNetwork { + public static void init() { + NetworkUtils.registerC2SPayload(RequestSkillActivationPacket.TYPE, RequestSkillActivationPacket.STREAM_CODEC, RequestSkillActivationPacket::handle); + NetworkUtils.registerC2SPayload(RequestSkillReleasePacket.TYPE, RequestSkillReleasePacket.STREAM_CODEC, RequestSkillReleasePacket::handle); + NetworkUtils.registerC2SPayload(RequestSkillScrollPacket.TYPE, RequestSkillScrollPacket.STREAM_CODEC, RequestSkillScrollPacket::handle); + NetworkUtils.registerC2SPayload(RequestSkillTogglePacket.TYPE, RequestSkillTogglePacket.STREAM_CODEC, RequestSkillTogglePacket::handle); + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillActivationPacket.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillActivationPacket.java new file mode 100644 index 00000000..c1df441b --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillActivationPacket.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.impl.network.c2s; + +import dev.architectury.networking.NetworkManager; +import dev.architectury.utils.Env; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.ModuleConstants; +import io.github.manasmods.manascore.skill.api.ManasSkillInstance; +import io.github.manasmods.manascore.skill.api.SkillAPI; +import io.github.manasmods.manascore.skill.api.SkillEvents; +import io.github.manasmods.manascore.skill.api.Skills; +import io.github.manasmods.manascore.skill.impl.TickingSkill; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; + +public record RequestSkillActivationPacket( + int keyNumber, + ResourceLocation skillId, + int mode +) implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "request_skill_activation")); + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(RequestSkillActivationPacket::encode, RequestSkillActivationPacket::new); + + public RequestSkillActivationPacket(FriendlyByteBuf buf) { + this(buf.readInt(), buf.readResourceLocation(), buf.readInt()); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeInt(this.keyNumber); + buf.writeResourceLocation(this.skillId); + buf.writeInt(this.mode); + } + + public void handle(NetworkManager.PacketContext context) { + if (context.getEnvironment() != Env.SERVER) return; + context.queue(() -> { + Player player = context.getPlayer(); + if(player == null) return; + Skills storage = SkillAPI.getSkillsFrom(player); + storage.getSkill(skillId).ifPresent(skillInstance -> { + Changeable changeable = Changeable.of(skillInstance); + if (SkillEvents.ACTIVATE_SKILL.invoker().activateSkill(changeable, player, keyNumber, mode).isFalse()) return; + + ManasSkillInstance skill = changeable.get(); + if (skill == null) return; + if(!skill.canInteractSkill(player)) return; + + if (mode < 0 || mode >= skill.getModes()) return; + if (skill.onCoolDown(mode) && !skill.canIgnoreCoolDown(player, mode)) return; + + skill.onPressed(player, keyNumber, mode); + skill.addHeldAttributeModifiers(player, mode); + TickingSkill.addTickingSkill(player, skill.getSkill(), mode, keyNumber); + storage.checkAndMarkDirty(skill); + }); + }); + } + + public @NotNull Type type() { + return TYPE; + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillReleasePacket.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillReleasePacket.java new file mode 100644 index 00000000..280d83bb --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillReleasePacket.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.impl.network.c2s; + +import dev.architectury.networking.NetworkManager; +import dev.architectury.utils.Env; +import io.github.manasmods.manascore.skill.impl.SkillStorage; +import io.github.manasmods.manascore.skill.ModuleConstants; +import io.github.manasmods.manascore.storage.impl.StorageManager; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; + +public record RequestSkillReleasePacket( + int heldTick, + int keyNumber, + int mode, + ResourceLocation skillId +) implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "request_skill_release")); + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(RequestSkillReleasePacket::encode, RequestSkillReleasePacket::new); + + public RequestSkillReleasePacket(FriendlyByteBuf buf) { + this(buf.readInt(), buf.readInt(), buf.readInt(), buf.readResourceLocation()); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeInt(this.heldTick); + buf.writeInt(this.keyNumber); + buf.writeInt(this.mode); + buf.writeResourceLocation(this.skillId); + } + + public void handle(NetworkManager.PacketContext context) { + if (context.getEnvironment() != Env.SERVER) return; + context.queue(() -> { + Player player = context.getPlayer(); + if (player == null) return; + SkillStorage storage = StorageManager.getStorage(player, SkillStorage.getKey()); + if (storage == null) return; + storage.getSkill(skillId).ifPresent(skillInstance -> storage.handleSkillRelease(skillInstance, heldTick, keyNumber, mode, false)); + }); + } + + public @NotNull Type type() { + return TYPE; + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.java new file mode 100644 index 00000000..25c19a82 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillScrollPacket.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.impl.network.c2s; + +import dev.architectury.networking.NetworkManager; +import dev.architectury.utils.Env; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.ModuleConstants; +import io.github.manasmods.manascore.skill.api.ManasSkillInstance; +import io.github.manasmods.manascore.skill.api.SkillAPI; +import io.github.manasmods.manascore.skill.api.SkillEvents; +import io.github.manasmods.manascore.skill.api.Skills; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +public record RequestSkillScrollPacket( + double delta, + Map skillList +) implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "request_skill_scroll")); + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(RequestSkillScrollPacket::encode, RequestSkillScrollPacket::new); + + public RequestSkillScrollPacket(FriendlyByteBuf buf) { + this(buf.readDouble(), validateList(buf.readMap(FriendlyByteBuf::readResourceLocation, FriendlyByteBuf::readInt))); + } + + private static Map validateList(Map map) { + int maxSize = 100; + if (map.size() > maxSize) throw new IllegalArgumentException("Skill map exceeds maximum size of " + maxSize); + return map; + } + + public void encode(FriendlyByteBuf buf) { + buf.writeDouble(this.delta); + buf.writeMap(this.skillList, FriendlyByteBuf::writeResourceLocation, FriendlyByteBuf::writeInt); + } + + public void handle(NetworkManager.PacketContext context) { + if (context.getEnvironment() != Env.SERVER) return; + context.queue(() -> { + Player player = context.getPlayer(); + if (player == null) return; + + Skills storage = SkillAPI.getSkillsFrom(player); + for (Map.Entry entry : skillList.entrySet()) { + storage.getSkill(entry.getKey()).ifPresent(skillInstance -> { + + Changeable skillChangeable = Changeable.of(skillInstance); + Changeable modeChangeable = Changeable.of(entry.getValue()); + Changeable deltaChangeable = Changeable.of(delta); + if (SkillEvents.SKILL_SCROLL.invoker().scroll(skillChangeable, player, modeChangeable, deltaChangeable).isFalse()) return; + + ManasSkillInstance skill = skillChangeable.get(); + if (skill == null || deltaChangeable.isEmpty()) return; + if (!skill.canScroll(player, modeChangeable.get())) return; + if (!skill.canInteractSkill(player)) return; + + skill.onScroll(player, deltaChangeable.get(), modeChangeable.get()); + storage.checkAndMarkDirty(skill); + }); + } + }); + } + + public @NotNull Type type() { + return TYPE; + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillTogglePacket.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillTogglePacket.java new file mode 100644 index 00000000..c5f081a1 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/impl/network/c2s/RequestSkillTogglePacket.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.impl.network.c2s; + +import dev.architectury.networking.NetworkManager; +import dev.architectury.utils.Env; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.ManasSkillInstance; +import io.github.manasmods.manascore.skill.api.SkillAPI; +import io.github.manasmods.manascore.skill.api.SkillEvents; +import io.github.manasmods.manascore.skill.api.Skills; +import io.github.manasmods.manascore.skill.ModuleConstants; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; + +public record RequestSkillTogglePacket( + ResourceLocation skillId +) implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "request_skill_toggle")); + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(RequestSkillTogglePacket::encode, RequestSkillTogglePacket::new); + + public RequestSkillTogglePacket(FriendlyByteBuf buf) { + this(buf.readResourceLocation()); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeResourceLocation(this.skillId); + } + + public void handle(NetworkManager.PacketContext context) { + if (context.getEnvironment() != Env.SERVER) return; + context.queue(() -> { + Player player = context.getPlayer(); + if(player == null) return; + Skills storage = SkillAPI.getSkillsFrom(player); + storage.getSkill(skillId).ifPresent(skillInstance -> { + Changeable changeable = Changeable.of(skillInstance); + if (SkillEvents.TOGGLE_SKILL.invoker().toggleSkill(changeable, player).isFalse()) return; + + ManasSkillInstance skill = changeable.get(); + if (skill == null) return; + if(!skill.canInteractSkill(player)) return; + + if (skill.isToggled()) { + skill.setToggled(false); + skill.onToggleOff(player); + } else { + skill.setToggled(true); + skill.onToggleOn(player); + } + storage.checkAndMarkDirty(skill); + }); + }); + } + + public @NotNull Type type() { + return TYPE; + } +} diff --git a/skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinLivingEntity.java b/skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinLivingEntity.java new file mode 100644 index 00000000..602616d1 --- /dev/null +++ b/skill-common/src/main/java/io/github/manasmods/manascore/skill/mixin/MixinLivingEntity.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +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; + +@Mixin(LivingEntity.class) +public abstract class MixinLivingEntity { + @Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;tick()V", shift = At.Shift.BEFORE)) + void onPreTick(CallbackInfo ci) { + EntityEvents.LIVING_PRE_TICK.invoker().tick((LivingEntity) (Object) this); + } + + @Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;refreshDirtyAttributes()V", shift = At.Shift.AFTER)) + void onPostTick(CallbackInfo ci) { + EntityEvents.LIVING_POST_TICK.invoker().tick((LivingEntity) (Object) this); + } + + @Inject(method = "addEffect(Lnet/minecraft/world/effect/MobEffectInstance;Lnet/minecraft/world/entity/Entity;)Z", at = @At(value = "HEAD"), cancellable = true) + void onEffectAdded(MobEffectInstance mobEffectInstance, Entity entity, CallbackInfoReturnable cir, @Local(argsOnly = true) LocalRef instance) { + Changeable instanceChangeable = Changeable.of(mobEffectInstance); + if (EntityEvents.LIVING_EFFECT_ADDED.invoker().effectAdd((LivingEntity) (Object) this, entity, instanceChangeable).isFalse()) { + cir.setReturnValue(false); + cir.cancel(); + } else instance.set(instanceChangeable.get()); + } +} diff --git a/skill-common/src/main/resources/architectury.common.json b/skill-common/src/main/resources/architectury.common.json new file mode 100644 index 00000000..d550112b --- /dev/null +++ b/skill-common/src/main/resources/architectury.common.json @@ -0,0 +1,6 @@ +{ + "injected_interfaces": { + + }, + "accessWidener": "manascore_skill.accesswidener" +} \ No newline at end of file diff --git a/skill-common/src/main/resources/assets/manascore/lang/en_us.json b/skill-common/src/main/resources/assets/manascore/lang/en_us.json new file mode 100644 index 00000000..094c0112 --- /dev/null +++ b/skill-common/src/main/resources/assets/manascore/lang/en_us.json @@ -0,0 +1,3 @@ +{ + "manascore.skill.learn_skill": "%s has been acquired." +} \ No newline at end of file diff --git a/skill-common/src/main/resources/manascore_skill.accesswidener b/skill-common/src/main/resources/manascore_skill.accesswidener new file mode 100644 index 00000000..2464d6d5 --- /dev/null +++ b/skill-common/src/main/resources/manascore_skill.accesswidener @@ -0,0 +1 @@ +accessWidener v2 named \ No newline at end of file diff --git a/skill-common/src/main/resources/manascore_skill.mixins.json b/skill-common/src/main/resources/manascore_skill.mixins.json new file mode 100644 index 00000000..22a86df0 --- /dev/null +++ b/skill-common/src/main/resources/manascore_skill.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "package": "io.github.manasmods.manascore.skill.mixin", + "compatibilityLevel": "JAVA_21", + "minVersion": "0.8", + "client": [ + ], + "mixins": [ + "MixinLivingEntity" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/skill-fabric/build.gradle b/skill-fabric/build.gradle new file mode 100644 index 00000000..0da26ca8 --- /dev/null +++ b/skill-fabric/build.gradle @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +loom { + accessWidenerPath = project(":skill-common").loom.accessWidenerPath +} + +dependencies { + implementation project(path: ":network-common", configuration: 'transformProductionFabric') + implementation project(path: ":storage-common", configuration: 'transformProductionFabric') +} diff --git a/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/ManasCoreSkillFabric.java b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/ManasCoreSkillFabric.java new file mode 100644 index 00000000..2ddb8a9f --- /dev/null +++ b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/ManasCoreSkillFabric.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.fabric; + +import io.github.manasmods.manascore.skill.ManasCoreSkill; +import net.fabricmc.api.ModInitializer; + +public class ManasCoreSkillFabric implements ModInitializer { + @Override + public void onInitialize() { + ManasCoreSkill.init(); + } +} \ No newline at end of file diff --git a/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractArrow.java b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractArrow.java new file mode 100644 index 00000000..ea4b5a97 --- /dev/null +++ b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractArrow.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.fabric.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.projectile.AbstractArrow; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.entity.projectile.ProjectileDeflection; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.HitResult; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(AbstractArrow.class) +public abstract class MixinAbstractArrow extends Projectile { + public MixinAbstractArrow(EntityType entityType, Level level) { + super(entityType, level); + } + + @Shadow + protected abstract void setPierceLevel(byte pierceLevel); + + @Unique + private EntityEvents.ProjectileHitResult onHitEventResult = EntityEvents.ProjectileHitResult.DEFAULT; + @Unique + private final IntOpenHashSet ignoredEntities = new IntOpenHashSet(); + + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/AbstractArrow;hitTargetOrDeflectSelf(Lnet/minecraft/world/phys/HitResult;)Lnet/minecraft/world/entity/projectile/ProjectileDeflection;")) + ProjectileDeflection onHit(AbstractArrow instance, HitResult result, Operation original, @Local LocalRef entityHitResult) { + Changeable resultChangeable = Changeable.of(EntityEvents.ProjectileHitResult.DEFAULT); + Changeable deflectionChangeable = Changeable.of(ProjectileDeflection.NONE); + EntityEvents.PROJECTILE_HIT.invoker().hit(result, instance, deflectionChangeable, resultChangeable); + this.onHitEventResult = resultChangeable.get(); + if (this.onHitEventResult == null) return original.call(instance, result); + + switch (this.onHitEventResult) { + case DEFAULT -> { + original.call(instance, result); + this.onHitEventResult = null; + return deflectionChangeable.get(); + } + case HIT -> { + this.setPierceLevel((byte) 0); + original.call(instance, result); + this.onHitEventResult = null; + return deflectionChangeable.get(); + } + case HIT_NO_DAMAGE -> { + this.discard(); + entityHitResult.set(null); + } + case PASS -> { + if (result.getType() != HitResult.Type.ENTITY) { + original.call(instance, result); + this.onHitEventResult = null; + } else { + this.ignoredEntities.add(entityHitResult.get().getEntity().getId()); + entityHitResult.set(null); + } + } + } + return deflectionChangeable.get(); + } + + @WrapOperation(method = "tick", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/projectile/AbstractArrow;hasImpulse:Z")) + void onImpulseSet(AbstractArrow instance, boolean value, Operation original) { + if (this.onHitEventResult == null) original.call(instance, value); + this.onHitEventResult = null; + } + + @Inject(method = "canHitEntity", at = @At("RETURN"), cancellable = true) + void ignoreEntities(Entity target, CallbackInfoReturnable cir) { + if (!cir.getReturnValue()) return; + cir.setReturnValue(!this.ignoredEntities.contains(target.getId())); + } +} diff --git a/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractHurtingProjectile.java b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractHurtingProjectile.java new file mode 100644 index 00000000..fe39137d --- /dev/null +++ b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinAbstractHurtingProjectile.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.fabric.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.entity.projectile.AbstractHurtingProjectile; +import net.minecraft.world.entity.projectile.ProjectileDeflection; +import net.minecraft.world.phys.HitResult; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(AbstractHurtingProjectile.class) +public abstract class MixinAbstractHurtingProjectile { + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/AbstractHurtingProjectile;hitTargetOrDeflectSelf(Lnet/minecraft/world/phys/HitResult;)Lnet/minecraft/world/entity/projectile/ProjectileDeflection;")) + ProjectileDeflection onHit(AbstractHurtingProjectile instance, HitResult result, Operation original) { + Changeable resultChangeable = Changeable.of(EntityEvents.ProjectileHitResult.DEFAULT); + Changeable deflectionChangeable = Changeable.of(ProjectileDeflection.NONE); + EntityEvents.PROJECTILE_HIT.invoker().hit(result, instance, deflectionChangeable, resultChangeable); + if (resultChangeable.get() != EntityEvents.ProjectileHitResult.DEFAULT) return deflectionChangeable.get(); + original.call(instance, result); + return deflectionChangeable.get(); + } +} diff --git a/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFireworkRocketEntity.java b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFireworkRocketEntity.java new file mode 100644 index 00000000..54882c00 --- /dev/null +++ b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFireworkRocketEntity.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.fabric.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.projectile.FireworkRocketEntity; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.entity.projectile.ProjectileDeflection; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.HitResult; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(FireworkRocketEntity.class) +public abstract class MixinFireworkRocketEntity extends Projectile { + public MixinFireworkRocketEntity(EntityType entityType, Level level) { + super(entityType, level); + } + + @Unique + @Nullable + private EntityEvents.ProjectileHitResult onHitEventResult = null; + + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/FireworkRocketEntity;hitTargetOrDeflectSelf(Lnet/minecraft/world/phys/HitResult;)Lnet/minecraft/world/entity/projectile/ProjectileDeflection;")) + ProjectileDeflection onHit(FireworkRocketEntity instance, HitResult result, Operation original) { + Changeable resultChangeable = Changeable.of(EntityEvents.ProjectileHitResult.DEFAULT); + Changeable deflectionChangeable = Changeable.of(ProjectileDeflection.NONE); + EntityEvents.PROJECTILE_HIT.invoker().hit(result, instance, deflectionChangeable, resultChangeable); + this.onHitEventResult = resultChangeable.get(); + if (this.onHitEventResult == null) return original.call(instance, result); + + switch (this.onHitEventResult) { + case DEFAULT, HIT -> { + original.call(instance, result); + this.onHitEventResult = null; + return deflectionChangeable.get(); + } + case HIT_NO_DAMAGE -> this.discard(); + case PASS -> { + if (result.getType() != HitResult.Type.ENTITY) { + original.call(instance, result); + this.onHitEventResult = null; + return deflectionChangeable.get(); + } + } + } + return deflectionChangeable.get(); + } + + @WrapOperation(method = "tick", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/projectile/FireworkRocketEntity;hasImpulse:Z")) + void onImpulseSet(FireworkRocketEntity instance, boolean value, Operation original) { + if (this.onHitEventResult == null) original.call(instance, value); + this.onHitEventResult = null; + } +} diff --git a/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFishingHook.java b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFishingHook.java new file mode 100644 index 00000000..07f9cb32 --- /dev/null +++ b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinFishingHook.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.fabric.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.entity.projectile.ProjectileDeflection; +import net.minecraft.world.phys.HitResult; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.Objects; + +@Mixin(FishingHook.class) +public abstract class MixinFishingHook { + @WrapOperation(method = "checkCollision", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/FishingHook;hitTargetOrDeflectSelf(Lnet/minecraft/world/phys/HitResult;)Lnet/minecraft/world/entity/projectile/ProjectileDeflection;")) + ProjectileDeflection onHit(FishingHook instance, HitResult result, Operation original) { + Changeable resultChangeable = Changeable.of(EntityEvents.ProjectileHitResult.DEFAULT); + Changeable deflectionChangeable = Changeable.of(ProjectileDeflection.NONE); + EntityEvents.PROJECTILE_HIT.invoker().hit(result, instance, deflectionChangeable, resultChangeable); + if (!Objects.equals(resultChangeable.get(), EntityEvents.ProjectileHitResult.DEFAULT)) return deflectionChangeable.get(); + original.call(instance, result); + return deflectionChangeable.get(); + } +} diff --git a/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLivingEntity.java b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLivingEntity.java new file mode 100644 index 00000000..49f40f8a --- /dev/null +++ b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLivingEntity.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.fabric.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.LivingEntity; +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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LivingEntity.class) +public abstract class MixinLivingEntity { + @ModifyVariable(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getDamageAfterArmorAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F", shift = At.Shift.BEFORE), argsOnly = true) + float modifyDamage(float amount, @Local(argsOnly = true) DamageSource damageSource) { + Changeable changeable = Changeable.of(amount); + if (EntityEvents.LIVING_HURT.invoker().hurt((LivingEntity) (Object) this, damageSource, changeable).isFalse()) return 0.0F; + return changeable.get(); + } + + @Inject(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getDamageAfterArmorAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F", shift = At.Shift.BEFORE), cancellable = true) + void cancelActuallyHurt(DamageSource damageSource, float damageAmount, CallbackInfo ci) { + if (damageAmount <= 0F) ci.cancel(); + } + + @ModifyVariable(method = "actuallyHurt", at = @At(value = "LOAD", ordinal = 6), argsOnly = true) + float modifyTotalDamage(float amount, @Local(argsOnly = true) DamageSource damageSource) { + Changeable changeable = Changeable.of(amount); + if (EntityEvents.LIVING_DAMAGE.invoker().damage((LivingEntity) (Object) this, damageSource, changeable).isFalse()) return 0.0F; + return changeable.get(); + } +} diff --git a/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLlamaSpit.java b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLlamaSpit.java new file mode 100644 index 00000000..a5fb8173 --- /dev/null +++ b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinLlamaSpit.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.fabric.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.entity.projectile.LlamaSpit; +import net.minecraft.world.entity.projectile.ProjectileDeflection; +import net.minecraft.world.phys.HitResult; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.Objects; + +@Mixin(LlamaSpit.class) +public abstract class MixinLlamaSpit { + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/LlamaSpit;hitTargetOrDeflectSelf(Lnet/minecraft/world/phys/HitResult;)Lnet/minecraft/world/entity/projectile/ProjectileDeflection;")) + ProjectileDeflection onHit(LlamaSpit instance, HitResult result, Operation original) { + Changeable resultChangeable = Changeable.of(EntityEvents.ProjectileHitResult.DEFAULT); + Changeable deflectionChangeable = Changeable.of(ProjectileDeflection.NONE); + EntityEvents.PROJECTILE_HIT.invoker().hit(result, instance, deflectionChangeable, resultChangeable); + if (!Objects.equals(resultChangeable.get(), EntityEvents.ProjectileHitResult.DEFAULT)) return deflectionChangeable.get(); + original.call(instance, result); + return deflectionChangeable.get(); + } +} diff --git a/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinMob.java b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinMob.java new file mode 100644 index 00000000..613e7e7a --- /dev/null +++ b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinMob.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.fabric.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Mob; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(Mob.class) +public class MixinMob { + @WrapOperation(method = "setTarget", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/Mob;target:Lnet/minecraft/world/entity/LivingEntity;")) + private void onSetTarget(Mob instance, LivingEntity value, Operation original) { + Changeable target = Changeable.of(value); + if (EntityEvents.LIVING_CHANGE_TARGET.invoker().changeTarget(instance, target).isFalse()) { + original.call(instance, value); + } else original.call(instance, target.get()); + } +} \ No newline at end of file diff --git a/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinPlayer.java b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinPlayer.java new file mode 100644 index 00000000..fe7b7553 --- /dev/null +++ b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinPlayer.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.fabric.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Player.class) +public abstract class MixinPlayer { + @ModifyVariable(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getDamageAfterArmorAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F", shift = At.Shift.BEFORE), argsOnly = true) + float modifyDamage(float amount, @Local(argsOnly = true) DamageSource damageSource) { + Changeable changeable = Changeable.of(amount); + if (EntityEvents.LIVING_HURT.invoker().hurt((LivingEntity) (Object) this, damageSource, changeable).isFalse()) return 0.0F; + return changeable.get(); + } + + @Inject(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getDamageAfterArmorAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F", shift = At.Shift.BEFORE), cancellable = true) + void cancelActuallyHurt(DamageSource damageSource, float damageAmount, CallbackInfo ci) { + if (damageAmount <= 0F) ci.cancel(); + } + + @ModifyVariable(method = "actuallyHurt", at = @At(value = "LOAD", ordinal = 6), argsOnly = true) + float modifyTotalDamage(float amount, @Local(argsOnly = true) DamageSource damageSource) { + Changeable changeable = Changeable.of(amount); + if (EntityEvents.LIVING_DAMAGE.invoker().damage((LivingEntity) (Object) this, damageSource, changeable).isFalse()) return 0.0F; + return changeable.get(); + } +} diff --git a/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinShulkerBullet.java b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinShulkerBullet.java new file mode 100644 index 00000000..070fd04a --- /dev/null +++ b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinShulkerBullet.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.fabric.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.entity.projectile.ProjectileDeflection; +import net.minecraft.world.entity.projectile.ShulkerBullet; +import net.minecraft.world.phys.HitResult; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.Objects; + +@Mixin(ShulkerBullet.class) +public abstract class MixinShulkerBullet { + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/ShulkerBullet;hitTargetOrDeflectSelf(Lnet/minecraft/world/phys/HitResult;)Lnet/minecraft/world/entity/projectile/ProjectileDeflection;")) + ProjectileDeflection onHit(ShulkerBullet instance, HitResult result, Operation original) { + Changeable resultChangeable = Changeable.of(EntityEvents.ProjectileHitResult.DEFAULT); + Changeable deflectionChangeable = Changeable.of(ProjectileDeflection.NONE); + EntityEvents.PROJECTILE_HIT.invoker().hit(result, instance, deflectionChangeable, resultChangeable); + if (!Objects.equals(resultChangeable.get(), EntityEvents.ProjectileHitResult.DEFAULT)) return deflectionChangeable.get(); + original.call(instance, result); + return deflectionChangeable.get(); + } +} diff --git a/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinThrowableProjectile.java b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinThrowableProjectile.java new file mode 100644 index 00000000..c2057353 --- /dev/null +++ b/skill-fabric/src/main/java/io/github/manasmods/manascore/skill/fabric/mixin/MixinThrowableProjectile.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.fabric.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.entity.projectile.ProjectileDeflection; +import net.minecraft.world.entity.projectile.ThrowableProjectile; +import net.minecraft.world.phys.HitResult; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.Objects; + +@Mixin(ThrowableProjectile.class) +public abstract class MixinThrowableProjectile{ + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/ThrowableProjectile;hitTargetOrDeflectSelf(Lnet/minecraft/world/phys/HitResult;)Lnet/minecraft/world/entity/projectile/ProjectileDeflection;")) + ProjectileDeflection onHit(ThrowableProjectile instance, HitResult result, Operation original) { + Changeable resultChangeable = Changeable.of(EntityEvents.ProjectileHitResult.DEFAULT); + Changeable deflectionChangeable = Changeable.of(ProjectileDeflection.NONE); + EntityEvents.PROJECTILE_HIT.invoker().hit(result, instance, deflectionChangeable, resultChangeable); + if (!Objects.equals(resultChangeable.get(), EntityEvents.ProjectileHitResult.DEFAULT)) return deflectionChangeable.get(); + original.call(instance, result); + return deflectionChangeable.get(); + } +} diff --git a/skill-fabric/src/main/resources/assets/manascore/icon.png b/skill-fabric/src/main/resources/assets/manascore/icon.png new file mode 100644 index 00000000..a38ae1d1 Binary files /dev/null and b/skill-fabric/src/main/resources/assets/manascore/icon.png differ diff --git a/skill-fabric/src/main/resources/assets/manascore/logo.png b/skill-fabric/src/main/resources/assets/manascore/logo.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/skill-fabric/src/main/resources/assets/manascore/logo.png differ diff --git a/skill-fabric/src/main/resources/fabric.mod.json b/skill-fabric/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..455f5b50 --- /dev/null +++ b/skill-fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "Utility and Core Library for Manas Mods", + "authors": [ + "ManasMods" + ], + "contact": { + "homepage": "" + }, + "license": "${license}", + "icon": "assets/manascore/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "io.github.manasmods.manascore.skill.fabric.ManasCoreSkillFabric" + ], + "client": [] + }, + "mixins": [ + "${mod_id}.mixins.json", + "${mod_id}-fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=${fabric_loader_version}", + "minecraft": "~${minecraft_version}", + "java": ">=21", + "architectury": ">=${architectury_version}", + "fabric-api": "*", + "manascore_network": ">=${version}" + }, + "suggests": { + } +} diff --git a/skill-fabric/src/main/resources/manascore_skill-fabric.mixins.json b/skill-fabric/src/main/resources/manascore_skill-fabric.mixins.json new file mode 100644 index 00000000..ddcf2fcf --- /dev/null +++ b/skill-fabric/src/main/resources/manascore_skill-fabric.mixins.json @@ -0,0 +1,23 @@ +{ + "required": true, + "package": "io.github.manasmods.manascore.skill.fabric.mixin", + "compatibilityLevel": "JAVA_21", + "minVersion": "0.8", + "client": [ + ], + "mixins": [ + "MixinAbstractArrow", + "MixinAbstractHurtingProjectile", + "MixinFireworkRocketEntity", + "MixinFishingHook", + "MixinLivingEntity", + "MixinLlamaSpit", + "MixinMob", + "MixinPlayer", + "MixinShulkerBullet", + "MixinThrowableProjectile" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/skill-neoforge/build.gradle b/skill-neoforge/build.gradle new file mode 100644 index 00000000..c03c466d --- /dev/null +++ b/skill-neoforge/build.gradle @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +loom { + accessWidenerPath = project(":skill-common").loom.accessWidenerPath +} + +dependencies { + implementation project(path: ":network-common", configuration: 'transformProductionNeoForge') + implementation project(path: ":storage-common", configuration: 'transformProductionNeoForge') +} + +remapJar { + atAccessWideners.add("${project.mod_id}.accesswidener") +} diff --git a/skill-neoforge/gradle.properties b/skill-neoforge/gradle.properties new file mode 100644 index 00000000..7da18ea6 --- /dev/null +++ b/skill-neoforge/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge diff --git a/skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/ManasCoreSkillNeoForge.java b/skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/ManasCoreSkillNeoForge.java new file mode 100644 index 00000000..7c1e2177 --- /dev/null +++ b/skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/ManasCoreSkillNeoForge.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.neoforge; + +import io.github.manasmods.manascore.skill.ManasCoreSkill; +import io.github.manasmods.manascore.skill.ModuleConstants; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.common.Mod; + +@Mod(ModuleConstants.MOD_ID) +public final class ManasCoreSkillNeoForge { + public ManasCoreSkillNeoForge(IEventBus modEventBus) { + ManasCoreSkill.init(); + } +} diff --git a/skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/NeoForgeCommonEventInvoker.java b/skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/NeoForgeCommonEventInvoker.java new file mode 100644 index 00000000..024e0b9d --- /dev/null +++ b/skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/NeoForgeCommonEventInvoker.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.neoforge; + +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.projectile.ProjectileDeflection; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.entity.ProjectileImpactEvent; +import net.neoforged.neoforge.event.entity.living.LivingChangeTargetEvent; +import net.neoforged.neoforge.event.entity.living.LivingDamageEvent; + +@EventBusSubscriber +public class NeoForgeCommonEventInvoker { + private NeoForgeCommonEventInvoker() { + } + + @SubscribeEvent + static void onLivingChangeTarget(final LivingChangeTargetEvent e) { + if (!e.getTargetType().equals(LivingChangeTargetEvent.LivingTargetType.MOB_TARGET)) return; + Changeable changeableTarget = Changeable.of(e.getNewAboutToBeSetTarget()); + if (EntityEvents.LIVING_CHANGE_TARGET.invoker().changeTarget(e.getEntity(), changeableTarget).isFalse()) { + e.setCanceled(true); + } else { + e.setNewAboutToBeSetTarget(changeableTarget.get()); + } + } + + @SubscribeEvent + static void onLivingDamage(final LivingDamageEvent.Pre e) { + Changeable changeableDamage = Changeable.of(e.getNewDamage()); + if (EntityEvents.LIVING_DAMAGE.invoker().damage(e.getEntity(), e.getSource(), changeableDamage).isFalse()) { + e.setNewDamage(0); + } else { + e.setNewDamage(changeableDamage.get()); + } + } + + @SubscribeEvent + static void onProjectileHit(final ProjectileImpactEvent e) { + Changeable result = Changeable.of(EntityEvents.ProjectileHitResult.DEFAULT); + Changeable deflection = Changeable.of(ProjectileDeflection.NONE); + EntityEvents.PROJECTILE_HIT.invoker().hit(e.getRayTraceResult(), e.getProjectile(), deflection, result); + if (result.get() != EntityEvents.ProjectileHitResult.DEFAULT) e.setCanceled(true); + } +} diff --git a/skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinLivingEntity.java b/skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinLivingEntity.java new file mode 100644 index 00000000..bbe7900f --- /dev/null +++ b/skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinLivingEntity.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.neoforge.mixin; + +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.LivingEntity; +import net.neoforged.neoforge.common.damagesource.DamageContainer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Stack; + +@Mixin(LivingEntity.class) +public abstract class MixinLivingEntity { + @Shadow public Stack damageContainers; + @Inject(method = "actuallyHurt", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/entity/LivingEntity;isInvulnerableTo(Lnet/minecraft/world/damagesource/DamageSource;)Z", shift = At.Shift.AFTER), cancellable = true) + void onHurt(DamageSource source, float amount, CallbackInfo ci) { + Changeable changeable = Changeable.of(amount); + if (EntityEvents.LIVING_HURT.invoker().hurt((LivingEntity) (Object) this, source, changeable).isFalse()) ci.cancel(); + else damageContainers.peek().setNewDamage(changeable.get()); + } +} diff --git a/skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinPlayer.java b/skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinPlayer.java new file mode 100644 index 00000000..b174a20e --- /dev/null +++ b/skill-neoforge/src/main/java/io/github/manasmods/manascore/skill/neoforge/mixin/MixinPlayer.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.skill.neoforge.mixin; + +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +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(Player.class) +public abstract class MixinPlayer extends LivingEntity { + protected MixinPlayer(EntityType type, Level level) { + super(type, level); + } + + @Inject(method = "actuallyHurt", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/entity/player/Player;isInvulnerableTo(Lnet/minecraft/world/damagesource/DamageSource;)Z", shift = At.Shift.AFTER), cancellable = true) + void onHurt(DamageSource source, float amount, CallbackInfo ci) { + Changeable changeable = Changeable.of(amount); + if (EntityEvents.LIVING_HURT.invoker().hurt((Player) (Object) this, source, changeable).isFalse()) ci.cancel(); + else damageContainers.peek().setNewDamage(changeable.get()); + } +} diff --git a/skill-neoforge/src/main/resources/META-INF/neoforge.mods.toml b/skill-neoforge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..3dedce5c --- /dev/null +++ b/skill-neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,55 @@ +modLoader = "javafml" +loaderVersion = "[2,)" +issueTrackerURL = "" +license = "${license}" + +[[mods]] +modId = "${mod_id}" +version = "${version}" +displayName = "${mod_name}" +authors = "ManasMods" +description = ''' +Utility and Core Library for Manas Mods +''' +logoFile = "icon.png" + +[[dependencies.${mod_id}]] + modId = "neoforge" + type = "required" + versionRange = "[21,)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "minecraft" + type = "required" + versionRange = "[${minecraft_version},)" + ordering = "NONE" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "architectury" + type = "required" + versionRange = "[${architectury_version},)" + ordering = "AFTER" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "manascore_storage" + type = "required" + versionRange = "[${version},)" + ordering = "AFTER" + side = "BOTH" + +[[dependencies.${mod_id}]] + modId = "manascore_network" + type = "required" + versionRange = "[${version},)" + ordering = "AFTER" + side = "BOTH" + +[[mixins]] +config = "${mod_id}.mixins.json" + +[[mixins]] +config = "manascore_skill-neoforge.mixins.json" diff --git a/skill-neoforge/src/main/resources/icon.png b/skill-neoforge/src/main/resources/icon.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/skill-neoforge/src/main/resources/icon.png differ diff --git a/skill-neoforge/src/main/resources/manascore_skill-neoforge.mixins.json b/skill-neoforge/src/main/resources/manascore_skill-neoforge.mixins.json new file mode 100644 index 00000000..224014bb --- /dev/null +++ b/skill-neoforge/src/main/resources/manascore_skill-neoforge.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "package": "io.github.manasmods.manascore.skill.neoforge.mixin", + "compatibilityLevel": "JAVA_21", + "minVersion": "0.8", + "client": [ + ], + "mixins": [ + "MixinLivingEntity", + "MixinPlayer" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/storage-common/build.gradle b/storage-common/build.gradle index 5814b528..8ad39c58 100644 --- a/storage-common/build.gradle +++ b/storage-common/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/storage-common/src/main/java/io/github/manasmods/manascore/storage/ManasCoreStorage.java b/storage-common/src/main/java/io/github/manasmods/manascore/storage/ManasCoreStorage.java index 6d7b94f1..35f54e00 100644 --- a/storage-common/src/main/java/io/github/manasmods/manascore/storage/ManasCoreStorage.java +++ b/storage-common/src/main/java/io/github/manasmods/manascore/storage/ManasCoreStorage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/storage-common/src/main/java/io/github/manasmods/manascore/storage/impl/StorageManager.java b/storage-common/src/main/java/io/github/manasmods/manascore/storage/impl/StorageManager.java index 47a074ce..baea3f2d 100644 --- a/storage-common/src/main/java/io/github/manasmods/manascore/storage/impl/StorageManager.java +++ b/storage-common/src/main/java/io/github/manasmods/manascore/storage/impl/StorageManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -53,7 +53,11 @@ public static void init() { }); // Copy storage from old player to new player - PlayerEvent.PLAYER_CLONE.register((oldPlayer, newPlayer, wonGame) -> newPlayer.manasCore$setCombinedStorage(oldPlayer.manasCore$getCombinedStorage())); + PlayerEvent.PLAYER_CLONE.register((oldPlayer, newPlayer, wonGame) -> { + CombinedStorage newStorage = new CombinedStorage(newPlayer); + newStorage.load(oldPlayer.manasCore$getCombinedStorage().toNBT()); + newPlayer.manasCore$setCombinedStorage(newStorage); + }); } public static void initialStorageFilling(StorageHolder holder) { diff --git a/storage-common/src/main/java/io/github/manasmods/manascore/storage/mixin/MixinServerLevel.java b/storage-common/src/main/java/io/github/manasmods/manascore/storage/mixin/MixinServerLevel.java index 38137491..b4454fda 100644 --- a/storage-common/src/main/java/io/github/manasmods/manascore/storage/mixin/MixinServerLevel.java +++ b/storage-common/src/main/java/io/github/manasmods/manascore/storage/mixin/MixinServerLevel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ package io.github.manasmods.manascore.storage.mixin; diff --git a/storage-fabric/build.gradle b/storage-fabric/build.gradle index 3c3a1824..9fb2809b 100644 --- a/storage-fabric/build.gradle +++ b/storage-fabric/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/storage-fabric/src/main/java/io/github/manasmods/manascore/storage/fabric/ManasCoreStorageFabric.java b/storage-fabric/src/main/java/io/github/manasmods/manascore/storage/fabric/ManasCoreStorageFabric.java index e11368c5..7b14f953 100644 --- a/storage-fabric/src/main/java/io/github/manasmods/manascore/storage/fabric/ManasCoreStorageFabric.java +++ b/storage-fabric/src/main/java/io/github/manasmods/manascore/storage/fabric/ManasCoreStorageFabric.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/storage-fabric/src/main/java/io/github/manasmods/manascore/storage/fabric/mixin/MixinPlayerChunkSender.java b/storage-fabric/src/main/java/io/github/manasmods/manascore/storage/fabric/mixin/MixinPlayerChunkSender.java index ba95e9cf..9fe5295f 100644 --- a/storage-fabric/src/main/java/io/github/manasmods/manascore/storage/fabric/mixin/MixinPlayerChunkSender.java +++ b/storage-fabric/src/main/java/io/github/manasmods/manascore/storage/fabric/mixin/MixinPlayerChunkSender.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/storage-fabric/src/main/resources/assets/manascore/icon.png b/storage-fabric/src/main/resources/assets/manascore/icon.png index c9a7b039..a38ae1d1 100644 Binary files a/storage-fabric/src/main/resources/assets/manascore/icon.png and b/storage-fabric/src/main/resources/assets/manascore/icon.png differ diff --git a/storage-fabric/src/main/resources/assets/manascore/logo.png b/storage-fabric/src/main/resources/assets/manascore/logo.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/storage-fabric/src/main/resources/assets/manascore/logo.png differ diff --git a/storage-neoforge/src/main/java/io/github/manasmods/manascore/storage/neoforge/ManasCoreStorageNeoForge.java b/storage-neoforge/src/main/java/io/github/manasmods/manascore/storage/neoforge/ManasCoreStorageNeoForge.java index c890cc9f..864773d9 100644 --- a/storage-neoforge/src/main/java/io/github/manasmods/manascore/storage/neoforge/ManasCoreStorageNeoForge.java +++ b/storage-neoforge/src/main/java/io/github/manasmods/manascore/storage/neoforge/ManasCoreStorageNeoForge.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/testing-common/build.gradle b/testing-common/build.gradle index 359dbaf6..f5610967 100644 --- a/testing-common/build.gradle +++ b/testing-common/build.gradle @@ -1,14 +1,19 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ ext { includedProjects = [ + ":attribute", + ":command", + ":config", + ":inventory", + ":keybind", ":network", + ":race", ":storage", - ":inventory", - ':command' + ":skill" ] } diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/ManasCoreTesting.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/ManasCoreTesting.java index e735037f..339e2a40 100644 --- a/testing-common/src/main/java/io/github/manasmods/manascore/testing/ManasCoreTesting.java +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/ManasCoreTesting.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -8,7 +8,10 @@ import dev.architectury.platform.Platform; import io.github.manasmods.manascore.testing.client.ManasCoreTestingClient; import io.github.manasmods.manascore.testing.module.CommandModuleTest; +import io.github.manasmods.manascore.testing.module.ConfigModuleTest; import io.github.manasmods.manascore.testing.module.StorageModuleTest; +import io.github.manasmods.manascore.testing.registry.RegistryTest; +import io.github.manasmods.manascore.testing.registry.TestAttributeRegistry; import net.fabricmc.api.EnvType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,10 +20,14 @@ public final class ManasCoreTesting { public static final Logger LOG = LoggerFactory.getLogger("ManasCore - Testing"); public static void init() { + ConfigModuleTest.init(); + StorageModuleTest.init(); + TestAttributeRegistry.init(); + RegistryTest.init(); + CommandModuleTest.init(); + if (Platform.getEnv() == EnvType.CLIENT) { ManasCoreTestingClient.init(); } - StorageModuleTest.init(); - CommandModuleTest.init(); } } diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/client/KeybindingTest.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/client/KeybindingTest.java new file mode 100644 index 00000000..96ce55d2 --- /dev/null +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/client/KeybindingTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.testing.client; + +import io.github.manasmods.manascore.keybind.api.KeybindingCategory; +import io.github.manasmods.manascore.keybind.api.KeybindingManager; +import io.github.manasmods.manascore.keybind.api.ManasKeybinding; +import io.github.manasmods.manascore.race.api.RaceAPI; +import io.github.manasmods.manascore.skill.api.SkillAPI; +import io.github.manasmods.manascore.testing.ManasCoreTesting; +import io.github.manasmods.manascore.testing.ModuleConstants; +import net.minecraft.resources.ResourceLocation; + +public class KeybindingTest { + public static void init() { + KeybindingCategory category = KeybindingCategory.of("test.category"); + KeybindingManager.register( + new ManasKeybinding("manascore.keybinding.test", category, () -> { + ManasCoreTesting.LOG.info("Pressing"); + RaceAPI.raceAbilityActivationPacket(); + }, duration -> { + ManasCoreTesting.LOG.info("Released in {} Seconds", duration / 1000.0); + RaceAPI.raceAbilityReleasePacket((int) (duration / 50)); + }), + new ManasKeybinding("manascore.keybinding.test_press", category, () -> { + ManasCoreTesting.LOG.info("Pressed"); + RaceAPI.raceEvolutionPacket(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "test_race_evolved")); + }), + new ManasKeybinding("manascore.keybinding.skill", category, + () -> SkillAPI.skillActivationPacket(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, + "test_skill"), 0, 0), + duration -> SkillAPI.skillReleasePacket(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, + "test_skill"), 0, 0, (int) (duration / 50))), + new ManasKeybinding("manascore.keybinding.skill_2", category, + () -> SkillAPI.skillActivationPacket(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, + "test_skill"), 1, 1), + duration -> SkillAPI.skillReleasePacket(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, + "test_skill"), 1, 1, (int) (duration / 50))), + new ManasKeybinding("manascore.keybinding.skill_toggle", category, + () -> SkillAPI.skillTogglePacket(ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "test_skill"))) + ); + } +} diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/client/ManasCoreTestingClient.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/client/ManasCoreTestingClient.java index 49841199..01aad67b 100644 --- a/testing-common/src/main/java/io/github/manasmods/manascore/testing/client/ManasCoreTestingClient.java +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/client/ManasCoreTestingClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -8,18 +8,27 @@ import dev.architectury.event.CompoundEventResult; import dev.architectury.event.events.client.ClientChatEvent; import dev.architectury.event.events.client.ClientLifecycleEvent; +import dev.architectury.registry.client.level.entity.EntityRendererRegistry; +import io.github.manasmods.manascore.testing.configs.TestConfig; import io.github.manasmods.manascore.testing.module.InventoryTabsTest; import io.github.manasmods.manascore.testing.module.StorageModuleTest; +import io.github.manasmods.manascore.testing.registry.RegistryTest; import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.entity.VillagerRenderer; public class ManasCoreTestingClient { public static void init() { + KeybindingTest.init(); ClientChatEvent.RECEIVED.register((type, message) -> { var player = Minecraft.getInstance().player; - if (player != null) StorageModuleTest.printTestStorage(player); + if (player != null) { + StorageModuleTest.printTestStorage(player); + TestConfig.printTestConfig(player); + } return CompoundEventResult.pass(); }); ClientLifecycleEvent.CLIENT_SETUP.register(instance -> InventoryTabsTest.init(19)); + EntityRendererRegistry.register(RegistryTest.TEST_ENTITY_TYPE::value, VillagerRenderer::new); } } diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/configs/SkillConfig.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/configs/SkillConfig.java new file mode 100644 index 00000000..9507a839 --- /dev/null +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/configs/SkillConfig.java @@ -0,0 +1,20 @@ +package io.github.manasmods.manascore.testing.configs; + +import io.github.manasmods.manascore.command.api.Permission; +import io.github.manasmods.manascore.config.api.Comment; +import io.github.manasmods.manascore.config.api.ManasConfig; + +public class SkillConfig extends ManasConfig { + @Comment("Multiplier for Iron Golem damage.") + public float ironGolemDamageMultiplier = 100; + + @Comment("Enables instant kill for creepers.\nSet to true to set the creeper's HP to 0.") + public boolean instaKillCreeper = true; + + @Comment("Test No Sync Config") + public Permission.PermissionLevel permissionLevel = Permission.PermissionLevel.PLAYER; + + public String getFileName() { + return "manascore_test/skill_config"; + } +} diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/configs/TestConfig.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/configs/TestConfig.java new file mode 100644 index 00000000..d039e3e2 --- /dev/null +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/configs/TestConfig.java @@ -0,0 +1,57 @@ +package io.github.manasmods.manascore.testing.configs; + +import io.github.manasmods.manascore.command.api.Permission; +import io.github.manasmods.manascore.config.ConfigRegistry; +import io.github.manasmods.manascore.config.api.Comment; +import io.github.manasmods.manascore.config.api.ManasConfig; +import io.github.manasmods.manascore.config.api.ManasSubConfig; +import io.github.manasmods.manascore.config.api.SyncToClient; +import io.github.manasmods.manascore.testing.registry.RegistryTest; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; + +import java.util.List; + +import static io.github.manasmods.manascore.testing.ManasCoreTesting.LOG; + +@SyncToClient +public class TestConfig extends ManasConfig { + public String getFileName() { + return "manascore_test/test_folder/test_config"; + } + + public ResourceLocation testResourceLocation = RegistryTest.TEST_SKILL.getId(); + @Comment("Enum test!") + public Permission.PermissionLevel permissionLevel = Permission.PermissionLevel.GAMEMASTER; + + @Comment("Random Lists of Values") + public RandomLists random_lists = new RandomLists(); + public static class RandomLists extends ManasSubConfig { + public NumberLists numberLists = new NumberLists(); + public static class NumberLists extends ManasSubConfig { + public List doubleList = List.of(1.0, 2D, 3d); + public List intList = List.of(69, 420); + public List longList = List.of(1L, 2L, 3L); + } + @Comment("Who doesn't hate bugs?") + public List stringList = List.of("I", "Hate", "Bugs", "soooooo much!"); + } + + @Comment("Test Sub Config") + public TestSubConfig test_subConfig = new TestSubConfig(); + public static class TestSubConfig extends ManasSubConfig { + public String initialMessage = "Config working!"; + } + + public static void printTestConfig(Player player) { + Level level = player.level(); + logConfigValue(player, level, "Test Config Sync", ConfigRegistry.getConfig(TestConfig.class).testResourceLocation); + logConfigValue(player, level, "Test Config Non-Sync", ConfigRegistry.getConfig(SkillConfig.class).permissionLevel); + } + + private static void logConfigValue(Player player, Level level, String configType, Object value) { + LOG.info("{} for entity {} on {}:\n{}", configType, player.getName(), + level.isClientSide() ? "client" : "server", value); + } +} diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java index 630862ab..3736d431 100644 --- a/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/CommandModuleTest.java @@ -1,15 +1,39 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ package io.github.manasmods.manascore.testing.module; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import io.github.manasmods.manascore.command.api.*; -import io.github.manasmods.manascore.command.api.parameter.Enum; import io.github.manasmods.manascore.command.api.parameter.*; +import io.github.manasmods.manascore.command.api.parameter.coordinate.BlockPosArg; +import io.github.manasmods.manascore.command.api.parameter.coordinate.RotationArg; +import io.github.manasmods.manascore.command.api.parameter.coordinate.Vec3Arg; +import io.github.manasmods.manascore.command.api.parameter.primitive.*; +import io.github.manasmods.manascore.command.api.parameter.resource.EnchantmentArg; +import io.github.manasmods.manascore.race.api.SpawnPointHelper; +import io.github.manasmods.manascore.skill.api.ManasSkill; +import io.github.manasmods.manascore.skill.api.SkillAPI; +import io.github.manasmods.manascore.skill.api.Skills; +import io.github.manasmods.manascore.testing.ManasCoreTesting; +import io.github.manasmods.manascore.testing.registry.RegistryTest; import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.coordinates.Coordinates; +import net.minecraft.commands.arguments.item.ItemInput; +import net.minecraft.commands.arguments.selector.EntitySelector; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; import java.util.UUID; @@ -17,7 +41,7 @@ public class CommandModuleTest { private static final Component RESPONSE = Component.literal("Works!"); public static void init() { - CommandArgumentRegistrationEvent.EVENT.register(registry -> { + CommandArgumentRegistrationEvent.EVENT.register((registry, dispatcher, buildContext) -> { registry.registerEnum(TestEnum.class); }); @@ -27,7 +51,7 @@ public static void init() { @Command(value = "foo", subCommands = {TestSubCommand.class}) public static class TestCommand { @Execute - public boolean withPerms(@Sender CommandSourceStack sender) { + public boolean withPerms(@SenderArg CommandSourceStack sender) { sender.sendSystemMessage(RESPONSE); return true; } @@ -35,27 +59,95 @@ public boolean withPerms(@Sender CommandSourceStack sender) { @Command(value = "bar") public static class TestSubCommand { - @Permission("manascore.command.test") + @Permission(value = "manascore.command.test", permissionLevel = Permission.PermissionLevel.GAMEMASTER) @Execute - public boolean withPerms(@Sender CommandSourceStack sender, @Literal("perms") String l) { + public boolean withPerms(@SenderArg CommandSourceStack sender, @LiteralArg("perms") String l) { sender.sendSystemMessage(RESPONSE); return true; } @Execute - public boolean uuidArg(@Sender CommandSourceStack sender, @Literal("uuid") String l, @Uuid UUID uuid) { + public boolean entityArg(@SenderArg CommandSourceStack sender, @LiteralArg("entity") String l, + @EntityArg(name = "learner", value = EntityArg.Type.PLAYER) EntitySelector selector) throws CommandSyntaxException { sender.sendSystemMessage(RESPONSE); + Entity entity = selector.findSingleEntity(sender); + if (entity instanceof LivingEntity living) { + Skills storage = SkillAPI.getSkillsFrom(living); + if (storage.learnSkill(RegistryTest.TEST_SKILL.get())) + ManasCoreTesting.LOG.info("Added Test Skill to " + entity.getName()); + } return true; } @Execute - public boolean enumArg(@Sender CommandSourceStack sender, @Literal("enum") String l, @Enum(TestEnum.class) TestEnum _enum) { + public boolean blockPosArg(@SenderArg CommandSourceStack sender, @LiteralArg("pos") String l, + @BlockPosArg BlockPos pos, @RotationArg("x") Coordinates xRot, @RotationArg("y") Coordinates yRot) { sender.sendSystemMessage(RESPONSE); + if (sender.getPlayer() != null) + SpawnPointHelper.teleportToAcrossDimensions(sender.getPlayer(), Level.OVERWORLD, + pos.getX(), pos.getY(), pos.getZ(), xRot.getRotation(sender).x, yRot.getRotation(sender).y); return true; } @Execute - public boolean boolArg(@Sender CommandSourceStack sender, @Literal("bool") String l, @Bool Boolean bool) { + public boolean vec3Arg(@SenderArg CommandSourceStack sender, @LiteralArg("vec3") String l, + @Vec3Arg(value = Vec3Arg.Type.CENTER) Vec3 pos, @DimensionArg ServerLevel dimension) { + sender.sendSystemMessage(RESPONSE); + if (sender.getPlayer() != null) + SpawnPointHelper.teleportToAcrossDimensions(sender.getPlayer(), dimension, pos.x(), pos.y(), pos.z(), 0, 0); + return true; + } + + @Execute + public boolean resourceLocationArg(@SenderArg CommandSourceStack sender, @LiteralArg("resourceLocation") String l, + @ResourceLocationArg ResourceLocation location) { + sender.sendSystemMessage(RESPONSE); + ManasSkill skill = SkillAPI.getSkillRegistry().get(location); + if (skill != null && sender.getPlayer() != null) { + SkillAPI.getSkillsFrom(sender.getPlayer()).forgetSkill(skill); + ManasCoreTesting.LOG.info("Removed Test Skill from " + sender.getPlayer().getName()); + } + return true; + } + + @Execute + public boolean itemArg(@SenderArg CommandSourceStack sender, @LiteralArg("item") String l, + @ItemArg ItemInput itemInput, @ItemArg("item2") ItemInput itemInput2, @ItemArg("item3") ItemInput itemInput3, + @EnchantmentArg Holder.Reference location) throws CommandSyntaxException { + sender.sendSystemMessage(RESPONSE); + if (sender.getPlayer() != null) { + ItemStack stack = itemInput.createItemStack(5, false); + stack.enchant(location, 100); + sender.getPlayer().addItem(stack); + sender.getPlayer().addItem(itemInput2.createItemStack(4, false)); + sender.getPlayer().addItem(itemInput3.createItemStack(3, false)); + } + return true; + } + + @Execute + public boolean uuidArg(@SenderArg CommandSourceStack sender, @LiteralArg("uuid") String l, @UuidArg UUID uuid) { + sender.sendSystemMessage(RESPONSE); + return true; + } + + @Execute + public boolean enumArg(@SenderArg CommandSourceStack sender, @LiteralArg("enum") String l, + @EnumArg(TestEnum.class) TestEnum _enum, @EnumArg(TestEnum.class) TestEnum _enum2) { + sender.sendSystemMessage(RESPONSE.copy() + .append("\nLiteral: '") + .append(Component.literal(l)) + .append("'\nEnum: '") + .append(String.valueOf(_enum)) + .append("'\nEnum 2: '") + .append(String.valueOf(_enum2)) + .append("'") + ); + return true; + } + + @Execute + public boolean boolArg(@SenderArg CommandSourceStack sender, @LiteralArg("bool") String l, @BooleanArg Boolean bool) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -67,7 +159,7 @@ public boolean boolArg(@Sender CommandSourceStack sender, @Literal("bool") Strin } @Execute - public boolean primitiveBoolArg(@Sender CommandSourceStack sender, @Literal("primitive_bool") String l, @Bool boolean bool) { + public boolean primitiveBoolArg(@SenderArg CommandSourceStack sender, @LiteralArg("primitive_bool") String l, @BooleanArg boolean bool) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -79,7 +171,7 @@ public boolean primitiveBoolArg(@Sender CommandSourceStack sender, @Literal("pri } @Execute - public boolean wordArg(@Sender CommandSourceStack sender, @Literal("word") String l, @Text String word) { + public boolean wordArg(@SenderArg CommandSourceStack sender, @LiteralArg("word") String l, @TextArg String word) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -91,7 +183,7 @@ public boolean wordArg(@Sender CommandSourceStack sender, @Literal("word") Strin } @Execute - public boolean stringArg(@Sender CommandSourceStack sender, @Literal("string") String l, @Text(Text.Type.STRING) String string) { + public boolean stringArg(@SenderArg CommandSourceStack sender, @LiteralArg("string") String l, @TextArg(TextArg.Type.STRING) String string) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -103,7 +195,7 @@ public boolean stringArg(@Sender CommandSourceStack sender, @Literal("string") S } @Execute - public boolean greedyStringArg(@Sender CommandSourceStack sender, @Literal("greedy_string") String l, @Text(Text.Type.GREEDY_STRING) String greedyString) { + public boolean greedyStringArg(@SenderArg CommandSourceStack sender, @LiteralArg("greedy_string") String l, @TextArg(TextArg.Type.GREEDY_STRING) String greedyString) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -115,19 +207,21 @@ public boolean greedyStringArg(@Sender CommandSourceStack sender, @Literal("gree } @Execute - public boolean doubleArg(@Sender CommandSourceStack sender, @Literal("double") String l, @DoubleNumber Double d) { + public boolean doubleArg(@SenderArg CommandSourceStack sender, @LiteralArg("double") String l, @DoubleArg Double d, @DoubleArg Double d2) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) .append("'\nDouble: '") .append(String.valueOf(d)) + .append("'\nDouble 2: '") + .append(String.valueOf(d2)) .append("'") ); return true; } @Execute - public boolean primitiveDoubleArg(@Sender CommandSourceStack sender, @Literal("primitive_double") String l, @DoubleNumber double d) { + public boolean primitiveDoubleArg(@SenderArg CommandSourceStack sender, @LiteralArg("primitive_double") String l, @DoubleArg double d) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -139,7 +233,7 @@ public boolean primitiveDoubleArg(@Sender CommandSourceStack sender, @Literal("p } @Execute - public boolean floatArg(@Sender CommandSourceStack sender, @Literal("float") String l, @FloatNumber Float f) { + public boolean floatArg(@SenderArg CommandSourceStack sender, @LiteralArg("float") String l, @FloatArg Float f) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -151,7 +245,7 @@ public boolean floatArg(@Sender CommandSourceStack sender, @Literal("float") Str } @Execute - public boolean primitiveFloatArg(@Sender CommandSourceStack sender, @Literal("primitive_float") String l, @FloatNumber float f) { + public boolean primitiveFloatArg(@SenderArg CommandSourceStack sender, @LiteralArg("primitive_float") String l, @FloatArg float f) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -163,7 +257,7 @@ public boolean primitiveFloatArg(@Sender CommandSourceStack sender, @Literal("pr } @Execute - public boolean intArg(@Sender CommandSourceStack sender, @Literal("int") String l, @IntNumber Integer i) { + public boolean intArg(@SenderArg CommandSourceStack sender, @LiteralArg("int") String l, @IntegerArg Integer i) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -175,7 +269,7 @@ public boolean intArg(@Sender CommandSourceStack sender, @Literal("int") String } @Execute - public boolean primitiveIntArg(@Sender CommandSourceStack sender, @Literal("primitive_int") String l, @IntNumber int i) { + public boolean primitiveIntArg(@SenderArg CommandSourceStack sender, @LiteralArg("primitive_int") String l, @IntegerArg int i) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -187,7 +281,7 @@ public boolean primitiveIntArg(@Sender CommandSourceStack sender, @Literal("prim } @Execute - public boolean longArg(@Sender CommandSourceStack sender, @Literal("long") String l, @LongNumber Long l1) { + public boolean longArg(@SenderArg CommandSourceStack sender, @LiteralArg("long") String l, @LongArg Long l1) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -199,7 +293,7 @@ public boolean longArg(@Sender CommandSourceStack sender, @Literal("long") Strin } @Execute - public boolean primitiveLongArg(@Sender CommandSourceStack sender, @Literal("primitive_long") String l, @LongNumber long l1) { + public boolean primitiveLongArg(@SenderArg CommandSourceStack sender, @LiteralArg("primitive_long") String l, @LongArg long l1) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -211,7 +305,7 @@ public boolean primitiveLongArg(@Sender CommandSourceStack sender, @Literal("pri } @Execute - public boolean multiple(@Sender CommandSourceStack sender, @Literal("literal") String l, @Enum(TestEnum.class) TestEnum _enum) { + public boolean multiple(@SenderArg CommandSourceStack sender, @LiteralArg("literal") String l, @EnumArg(TestEnum.class) TestEnum _enum) { sender.sendSystemMessage(RESPONSE.copy() .append("\nLiteral: '") .append(Component.literal(l)) @@ -225,6 +319,7 @@ public boolean multiple(@Sender CommandSourceStack sender, @Literal("literal") S public enum TestEnum { TEST, - TEST2 + TEST2, + TEST3 } } diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/ConfigModuleTest.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/ConfigModuleTest.java new file mode 100644 index 00000000..f00d9c12 --- /dev/null +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/ConfigModuleTest.java @@ -0,0 +1,16 @@ +package io.github.manasmods.manascore.testing.module; + +import io.github.manasmods.manascore.config.ConfigRegistry; +import io.github.manasmods.manascore.testing.configs.SkillConfig; +import io.github.manasmods.manascore.testing.configs.TestConfig; + +public class ConfigModuleTest { + + public static void init() { + System.out.println("ConfigModuleTest initialized"); + ConfigRegistry.registerConfig(new SkillConfig()); + ConfigRegistry.registerConfig(new TestConfig()); + TestConfig testConfig = ConfigRegistry.getConfig(TestConfig.class); + System.out.println(testConfig.test_subConfig.initialMessage); + } +} diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/InventoryTabsTest.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/InventoryTabsTest.java index df86ea71..b36dcfa6 100644 --- a/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/InventoryTabsTest.java +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/InventoryTabsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/StorageModuleTest.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/StorageModuleTest.java index acb6fdd0..e952ab53 100644 --- a/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/StorageModuleTest.java +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/module/StorageModuleTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -9,15 +9,21 @@ import dev.architectury.event.events.common.ChatEvent; import dev.architectury.event.events.common.EntityEvent; import dev.architectury.event.events.common.PlayerEvent; +import io.github.manasmods.manascore.attribute.api.AttributeEvents; +import io.github.manasmods.manascore.skill.ManasCoreSkill; +import io.github.manasmods.manascore.skill.api.SkillEvents; import io.github.manasmods.manascore.storage.api.Storage; import io.github.manasmods.manascore.storage.api.StorageEvents; import io.github.manasmods.manascore.storage.api.StorageHolder; import io.github.manasmods.manascore.storage.api.StorageKey; import io.github.manasmods.manascore.testing.ModuleConstants; +import io.github.manasmods.manascore.testing.configs.TestConfig; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.LevelChunk; @@ -63,7 +69,29 @@ public static void init() { }); // Register event listeners that print the storage on client side and server side ChatEvent.RECEIVED.register((player, component) -> { - if (player != null) printTestStorage(player); + if (player != null) { + printTestStorage(player); + TestConfig.printTestConfig(player); + } + return EventResult.pass(); + }); + + SkillEvents.ACTIVATE_SKILL.register((skillInstance, owner, keyNumber, mode) -> { + ManasCoreSkill.LOG.info(String.valueOf(owner.position())); + return EventResult.pass(); + }); + SkillEvents.RELEASE_SKILL.register((skillInstance, owner, keyNumber, mode, heldTicks) -> { + ManasCoreSkill.LOG.info(String.valueOf(owner.position())); + return EventResult.pass(); + }); + + AttributeEvents.START_GLIDE_EVENT.register((entity, glide) -> { + if (entity.getItemBySlot(EquipmentSlot.HEAD).is(Items.PIGLIN_HEAD)) glide.set(true); + if (entity.getItemBySlot(EquipmentSlot.CHEST).is(Items.NETHERITE_CHESTPLATE)) glide.set(false); + return EventResult.pass(); + }); + AttributeEvents.CONTINUE_GLIDE_EVENT.register((entity, glide) -> { + if (entity.isShiftKeyDown()) glide.set(false); return EventResult.pass(); }); } @@ -74,7 +102,7 @@ public static void printTestStorage(Player player) { LevelChunk chunk = level.getChunkAt(player.blockPosition()); boolean isClientSide = level.isClientSide(); - LOG.info("Storage of entity {} on {}:\n{}", player.getId(), isClientSide ? "client" : "server", player.manasCore$getStorage(ENTITY_KEY)); + LOG.info("Storage of entity {} on {} at {}:\n{}", player.getId(), isClientSide ? "client" : "server", player.position(), player.manasCore$getStorage(ENTITY_KEY)); LOG.info("Storage at chunk {} on {}:\n{}", chunk.getPos(), isClientSide ? "client" : "server", chunk.manasCore$getStorage(CHUNK_KEY)); LOG.info("Storage of world {} on {}:\n{}", level.dimension().location(), isClientSide ? "client" : "server", level.manasCore$getStorage(WORLD_KEY)); } diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java new file mode 100644 index 00000000..be338e6b --- /dev/null +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/RegistryTest.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.testing.registry; + +import com.mojang.serialization.MapCodec; +import dev.architectury.event.EventResult; +import dev.architectury.event.events.common.PlayerEvent; +import dev.architectury.registry.CreativeTabRegistry; +import dev.architectury.registry.level.entity.EntityAttributeRegistry; +import dev.architectury.registry.registries.DeferredRegister; +import dev.architectury.registry.registries.Registrar; +import dev.architectury.registry.registries.RegistrySupplier; +import io.github.manasmods.manascore.attribute.api.ManasCoreAttributes; +import io.github.manasmods.manascore.config.ConfigRegistry; +import io.github.manasmods.manascore.race.api.ManasRace; +import io.github.manasmods.manascore.race.api.RaceAPI; +import io.github.manasmods.manascore.race.api.Races; +import io.github.manasmods.manascore.skill.api.ManasSkill; +import io.github.manasmods.manascore.skill.api.SkillAPI; +import io.github.manasmods.manascore.skill.api.Skills; +import io.github.manasmods.manascore.testing.ManasCoreTesting; +import io.github.manasmods.manascore.testing.ModuleConstants; +import io.github.manasmods.manascore.testing.configs.TestConfig; +import net.minecraft.ChatFormatting; +import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectCategory; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.npc.Villager; +import net.minecraft.world.entity.projectile.windcharge.AbstractWindCharge; +import net.minecraft.world.item.*; +import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.component.ItemAttributeModifiers; +import net.minecraft.world.level.Explosion; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class RegistryTest { + public static final DeferredRegister TABS = DeferredRegister.create(ModuleConstants.MOD_ID, Registries.CREATIVE_MODE_TAB); + public static final RegistrySupplier TESTING_TAB = TABS.register("test_tab", () -> + CreativeTabRegistry.create(Component.literal("Testing Creative Tab").withStyle(ChatFormatting.RED), + () -> new ItemStack(RegistryTest.TEST_ITEM.get()))); + + public static final DeferredRegister ITEMS = DeferredRegister.create(ModuleConstants.MOD_ID, Registries.ITEM); + public static final RegistrySupplier TEST_ITEM = ITEMS.register("test_item", + () -> new Item(new Item.Properties().arch$tab(TESTING_TAB) + .stacksTo(ConfigRegistry.getConfig(TestConfig.class).random_lists.numberLists.intList.getFirst()) + .attributes(ItemAttributeModifiers.builder() + .add(ManasCoreAttributes.CRITICAL_DAMAGE_MULTIPLIER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 10, + AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL), EquipmentSlotGroup.OFFHAND) + .add(ManasCoreAttributes.CRITICAL_ATTACK_CHANCE, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_multiplier"), 50, + AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL), EquipmentSlotGroup.OFFHAND).build()))); + + public static final DeferredRegister BLOCKS = DeferredRegister.create(ModuleConstants.MOD_ID, Registries.BLOCK); + public static final RegistrySupplier TEST_BLOCK = BLOCKS.register("test_block", + () -> new TestBlock(BlockBehaviour.Properties.of().lightLevel(value -> 15))); + public static final RegistrySupplier TEST_BLOCK_ITEM = ITEMS.register("test_block_item", + () -> new BlockItem(RegistryTest.TEST_BLOCK.get(), new Item.Properties().arch$tab(TESTING_TAB).stacksTo(42) + .attributes(ItemAttributeModifiers.builder() + .add(TestAttributeRegistry.TEST_ATTRIBUTE_PLAYER, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_chance"), + 69, AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND) + .add(TestAttributeRegistry.TEST_ATTRIBUTE_ALL, new AttributeModifier(ResourceLocation.withDefaultNamespace("test_critical_chance"), + 420, AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND).build()))); + + public static final DeferredRegister> BLOCK_ENTITIES = DeferredRegister.create(ModuleConstants.MOD_ID, Registries.BLOCK_ENTITY_TYPE); + public static final RegistrySupplier> TEST_BLOCK_ENTITY = BLOCK_ENTITIES.register("test_block_entity", + () -> BlockEntityType.Builder.of(TestBlockEntity::new, RegistryTest.TEST_BLOCK.get()).build(null)); + + public static final DeferredRegister> ENTITY_TYPES = DeferredRegister.create(ModuleConstants.MOD_ID, Registries.ENTITY_TYPE); + public static final RegistrySupplier> TEST_ENTITY_TYPE = ENTITY_TYPES.register("test_entity", + () -> EntityType.Builder.of(TestEntity::new, MobCategory.MONSTER).fireImmune() + .sized(1F, 1F).clientTrackingRange(4).updateInterval(10).build("test_entity")); + + public static final DeferredRegister MOB_EFFECTS = DeferredRegister.create(ModuleConstants.MOD_ID, Registries.MOB_EFFECT); + public static final RegistrySupplier TEST_MOB_EFFECT = MOB_EFFECTS.register("test_mob_effect", + () -> new TestMobEffect(MobEffectCategory.NEUTRAL, 4201604) + .withSoundOnAdded(SoundEvents.ALLAY_DEATH).setBlendDuration(60)); + public static final RegistrySupplier TEST_MOB_EFFECT_PARTICLE = MOB_EFFECTS.register("test_mob_effect_particle", + () -> new TestMobEffect(MobEffectCategory.HARMFUL, 6901604, ParticleTypes.ANGRY_VILLAGER) + .withSoundOnAdded(SoundEvents.BREWING_STAND_BREW) + .addAttributeModifier(Attributes.WATER_MOVEMENT_EFFICIENCY, ResourceLocation.withDefaultNamespace("test_swim_speed"), + 3, AttributeModifier.Operation.ADD_VALUE)); + + public static final DeferredRegister POTIONS = DeferredRegister.create(ModuleConstants.MOD_ID, Registries.POTION); + public static final RegistrySupplier TEST_POTION = POTIONS.register("test_potion", + () -> new Potion("lmao_potion", new MobEffectInstance(TEST_MOB_EFFECT, 100, 10), + new MobEffectInstance(TEST_MOB_EFFECT_PARTICLE, 200, 5, false, false, false))); + + public static final DeferredRegister SKILLS = DeferredRegister.create(ModuleConstants.MOD_ID, SkillAPI.getSkillRegistryKey()); + public static final RegistrySupplier TEST_SKILL = SKILLS.register("test_skill", TestSkill::new); + public static final RegistrySupplier TEST_SKILL_2 = SKILLS.register("test_skill_2", TestSkill::new); + + public static final DeferredRegister RACES = DeferredRegister.create(ModuleConstants.MOD_ID, RaceAPI.getRaceRegistryKey()); + public static final RegistrySupplier TEST_RACE = RACES.register("test_race", TestRace::new); + public static final RegistrySupplier TEST_RACE_EVOLVED = RACES.register("test_race_evolved", TestRaceEvolved::new); + + public static void init() { + ManasCoreTesting.LOG.info("Registered test content!"); + TABS.register(); + BLOCKS.register(); + ITEMS.register(); + BLOCK_ENTITIES.register(); + ENTITY_TYPES.register(); + MOB_EFFECTS.register(); + POTIONS.register(); + SKILLS.register(); + RACES.register(); + + EntityAttributeRegistry.register(TEST_ENTITY_TYPE, () -> Mob.createMobAttributes() + .add(Attributes.MOVEMENT_SPEED, 0.5).add(Attributes.FOLLOW_RANGE, 48.0) + .add(TestAttributeRegistry.TEST_ATTRIBUTE_ALL, 100F)); + + PlayerEvent.DROP_ITEM.register((player, entity) -> { + //Test giving Skills + if (entity.getItem().is(Items.DIAMOND)) { + Skills storage = SkillAPI.getSkillsFrom(player); + Registrar skills = SkillAPI.getSkillRegistry(); + RegistrySupplier testSkill = TEST_SKILL; + if (storage.learnSkill(RegistryTest.TEST_SKILL.get())) { + ManasCoreTesting.LOG.info("Added Tested Skill!"); + } + + } else if (entity.getItem().is(Items.EMERALD)) { + Skills storage = SkillAPI.getSkillsFrom(player); + storage.forgetSkill(RegistryTest.TEST_SKILL.get(), Component.literal("Forgot Tested Skill!")); + } else if (entity.getItem().is(Items.GOLD_INGOT)) { + Races storage = RaceAPI.getRaceFrom(player); + storage.setRace(RegistryTest.TEST_RACE.getId(), true, Component.literal("Set to Test Race!")); + } + + return EventResult.pass(); + }); + } + + private static class TestEntity extends Villager { + public TestEntity(EntityType entityType, Level level) { + super(TEST_ENTITY_TYPE.get(), level); + } + } + + private static class TestBlockEntity extends BlockEntity { + TestBlockEntity(BlockPos pos, BlockState blockState) { + super(TEST_BLOCK_ENTITY.get(), pos, blockState); + ManasCoreTesting.LOG.info("Created block entity!"); + } + } + + private static class TestMobEffect extends MobEffect { + protected TestMobEffect(MobEffectCategory mobEffectCategory, int i) { + super(mobEffectCategory, i); + } + + protected TestMobEffect(MobEffectCategory mobEffectCategory, int i, ParticleOptions particleOptions) { + super(mobEffectCategory, i, particleOptions); + } + + public void onMobRemoved(LivingEntity entity, int i, Entity.RemovalReason removalReason) { + if (removalReason == Entity.RemovalReason.KILLED) { + if (entity.level() instanceof ServerLevel level) { + double d = entity.getX(); + double e = entity.getY() + (double)(entity.getBbHeight() / 2.0F); + double f = entity.getZ(); + float g = 10.0F + entity.getRandom().nextFloat() * 2.0F; + if (this.equals(RegistryTest.TEST_MOB_EFFECT_PARTICLE.get())) + level.explode(entity, null, AbstractWindCharge.EXPLOSION_DAMAGE_CALCULATOR, d, e, f, g, + false, Level.ExplosionInteraction.TRIGGER, ParticleTypes.GUST_EMITTER_SMALL, + ParticleTypes.GUST_EMITTER_LARGE, SoundEvents.BREEZE_WIND_CHARGE_BURST); + else level.explode(entity, Explosion.getDefaultDamageSource(level, entity), null, d, e, f, g, + false, Level.ExplosionInteraction.MOB, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE); + } + } + } + } + + private static class TestBlock extends BaseEntityBlock { + private static final MapCodec TEST_BLOCK_MAP_CODEC = simpleCodec(TestBlock::new); + + TestBlock(Properties properties) { + super(properties); + } + + @Override + protected @NotNull MapCodec codec() { + return TEST_BLOCK_MAP_CODEC; + } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return TEST_BLOCK_ENTITY.get().create(pos, state); + } + } +} diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestAttributeRegistry.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestAttributeRegistry.java new file mode 100644 index 00000000..51e99f6c --- /dev/null +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestAttributeRegistry.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.testing.registry; + +import io.github.manasmods.manascore.attribute.ManasCoreAttributeRegister; +import io.github.manasmods.manascore.testing.ModuleConstants; +import net.minecraft.core.Holder; +import net.minecraft.world.entity.ai.attributes.Attribute; + +public class TestAttributeRegistry { + public static final Holder TEST_ATTRIBUTE_PLAYER = ManasCoreAttributeRegister.registerPlayerAttribute(ModuleConstants.MOD_ID, + "test_attribute_player", "manascore.attribute.test_attribute_player",69, 0, 420, true, Attribute.Sentiment.NEUTRAL); + public static final Holder TEST_ATTRIBUTE_ALL = ManasCoreAttributeRegister.registerGenericAttribute(ModuleConstants.MOD_ID, + "test_attribute_all", "manascore.attribute.test_attribute_all", 420, 69, 4200, true, Attribute.Sentiment.NEGATIVE); + + public static void init() { + } +} diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRace.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRace.java new file mode 100644 index 00000000..4cb196df --- /dev/null +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRace.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.testing.registry; + +import com.mojang.datafixers.util.Pair; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.race.api.ManasRace; +import io.github.manasmods.manascore.race.api.ManasRaceInstance; +import io.github.manasmods.manascore.race.api.SpawnPointHelper; +import io.github.manasmods.manascore.skill.api.ManasSkill; +import io.github.manasmods.manascore.skill.api.SkillAPI; +import io.github.manasmods.manascore.skill.api.Skills; +import io.github.manasmods.manascore.testing.ManasCoreTesting; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.tags.EntityTypeTags; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.monster.Pillager; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class TestRace extends ManasRace { + public TestRace() { + super(Difficulty.INTERMEDIATE); + this.addAttributeModifier(Attributes.STEP_HEIGHT, ResourceLocation.withDefaultNamespace("race.step"), + 1, AttributeModifier.Operation.ADD_VALUE); + this.addAttributeModifier(Attributes.ARMOR, ResourceLocation.withDefaultNamespace("race.armor"), + 10, AttributeModifier.Operation.ADD_VALUE); + } + + public boolean canTick(ManasRaceInstance instance, LivingEntity entity) { + return entity.isShiftKeyDown(); + } + + public void onActivateAbility(ManasRaceInstance instance, LivingEntity entity) { + entity.level().explode(entity, entity.getX(), entity.getY(), entity.getZ(), 1F, Level.ExplosionInteraction.BLOCK); + } + + public boolean onHeldAbility(ManasRaceInstance instance, LivingEntity entity, int heldTicks) { + entity.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SPEED, 10, 10)); + if (entity.isShiftKeyDown()) { + instance.setCooldown(5); + return false; + } + return true; + } + + public void onReleaseAbility(ManasRaceInstance instance, LivingEntity entity, int heldTicks) { + entity.level().explode(entity, entity.getX(), entity.getY(), entity.getZ(), 6F, Level.ExplosionInteraction.BLOCK); + } + + public void onTick(ManasRaceInstance instance, LivingEntity living) { + if (living.getItemBySlot(EquipmentSlot.MAINHAND).getItem().equals(Items.SALMON)) + ManasCoreTesting.LOG.info("Fishy fishy!"); + } + + public void onRaceSet(ManasRaceInstance instance, LivingEntity living) { + ManasCoreTesting.LOG.info("You are a Test Race!"); + living.playSound(SoundEvents.PLAYER_LEVELUP, 1, 1); + } + + public boolean onEffectAdded(ManasRaceInstance instance, LivingEntity entity, @Nullable Entity source, Changeable effect) { + MobEffectInstance effectInstance = effect.get(); + if (effectInstance == null) return false; + if (effectInstance.getEffect().equals(MobEffects.WEAKNESS)) return false; + + if (effectInstance.getEffect().equals(MobEffects.CONFUSION)) { + ManasCoreTesting.LOG.info("Poison is bad!"); + effect.set(new MobEffectInstance(MobEffects.DAMAGE_BOOST, effectInstance.getDuration(), effectInstance.getAmplifier())); + } + return true; + } + + public boolean onBeingTargeted(ManasRaceInstance instance, Changeable target, LivingEntity mob) { + if (mob.getType().is(EntityTypeTags.SENSITIVE_TO_SMITE)) target.set(null); + return true; + } + + public boolean onAttackEntity(ManasRaceInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable amount) { + if (owner.isShiftKeyDown() && target instanceof Pillager) { + BlockPos pos = ServerLevel.END_SPAWN_POINT; + SpawnPointHelper.teleportToAcrossDimensions(target, + this.getRespawnDimension(instance, owner).getFirst(), pos.getX(), pos.getY(), pos.getZ(), 0, 0); + } + return true; + } + + public boolean onHurt(ManasRaceInstance instance, LivingEntity owner, DamageSource source, Changeable amount) { + return !source.equals(owner.level().damageSources().fall()); + } + + public boolean onDeath(ManasRaceInstance instance, LivingEntity owner, DamageSource source) { + ManasCoreTesting.LOG.info("AWWWWW MANNNNN"); + return true; + } + + public void onRespawn(ManasRaceInstance instance, ServerPlayer owner, boolean conqueredEnd) { + ManasCoreTesting.LOG.info("CREEPER"); + } + + public Pair, BlockState> getRespawnDimension(ManasRaceInstance instance, LivingEntity owner) { + return Pair.of(Level.NETHER, Blocks.NETHERRACK.defaultBlockState()); + } + + public List getIntrinsicSkills(ManasRaceInstance instance, LivingEntity entity) { + List list = new ArrayList<>(); + list.add(RegistryTest.TEST_SKILL.get()); + return list; + } + + public void learnIntrinsicSkills(ManasRaceInstance instance, LivingEntity entity) { + Skills storage = SkillAPI.getSkillsFrom(entity); + for (ManasSkill skill : instance.getIntrinsicSkills(entity)) { + if (storage.learnSkill(skill)) ManasCoreTesting.LOG.info("LEARNT SKILL FROM RACE?!?!"); + } + } + + public float getEvolutionProgress(ManasRaceInstance instance, LivingEntity entity, ManasRace evolution) { + return entity.getMainHandItem().getCount() / 16F; + } + + public void onRaceEvolution(ManasRaceInstance instance, LivingEntity living, ManasRaceInstance evolution) { + ManasCoreTesting.LOG.info("YOU EVOLVED TO" + evolution.getRace().getName() + "!?!"); + } + + public List getNextEvolutions(ManasRaceInstance instance, LivingEntity entity) { + List list = new ArrayList<>(); + list.add(RegistryTest.TEST_RACE_EVOLVED.get()); + return list; + } + + public @Nullable ManasRace getDefaultEvolution(ManasRaceInstance instance, LivingEntity entity) { + return RegistryTest.TEST_RACE_EVOLVED.get(); + } +} diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRaceEvolved.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRaceEvolved.java new file mode 100644 index 00000000..aebdcaf2 --- /dev/null +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestRaceEvolved.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.testing.registry; + +import com.mojang.datafixers.util.Pair; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.race.api.ManasRace; +import io.github.manasmods.manascore.race.api.ManasRaceInstance; +import io.github.manasmods.manascore.race.api.SpawnPointHelper; +import io.github.manasmods.manascore.testing.ManasCoreTesting; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.tags.EntityTypeTags; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.ArrayList; +import java.util.List; + +public class TestRaceEvolved extends ManasRace { + public TestRaceEvolved() { + super(Difficulty.EASY); + this.addAttributeModifier(Attributes.JUMP_STRENGTH, ResourceLocation.withDefaultNamespace("race.speed"), + 2, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL); + this.addAttributeModifier(Attributes.MAX_HEALTH, ResourceLocation.withDefaultNamespace("race.health"), + 100, AttributeModifier.Operation.ADD_VALUE); + } + + public void onActivateAbility(ManasRaceInstance instance, LivingEntity entity) { + entity.level().explode(entity, entity.getX(), entity.getY(), entity.getZ(), 10F, Level.ExplosionInteraction.BLOCK); + } + + public void onRaceSet(ManasRaceInstance instance, LivingEntity living) { + ManasCoreTesting.LOG.info("You are a Test Race Evolved!"); + living.playSound(SoundEvents.ENCHANTMENT_TABLE_USE, 1, 1); + } + + public boolean onBeingTargeted(ManasRaceInstance instance, Changeable target, LivingEntity mob) { + if (mob.getType().is(EntityTypeTags.SENSITIVE_TO_BANE_OF_ARTHROPODS)) target.set(null); + return true; + } + + public boolean onAttackEntity(ManasRaceInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable amount) { + if (owner.isShiftKeyDown()) { + BlockPos pos = ServerLevel.END_SPAWN_POINT; + SpawnPointHelper.teleportToAcrossDimensions(target, + this.getRespawnDimension(instance, owner).getFirst(), pos.getX(), pos.getY(), pos.getZ(), 0, 0); + } + return true; + } + + public boolean onDeath(ManasRaceInstance instance, LivingEntity owner, DamageSource source) { + ManasCoreTesting.LOG.info("AWWWWW MANNNNN x2"); + return true; + } + + public void onRespawn(ManasRaceInstance instance, ServerPlayer owner, boolean conqueredEnd) { + ManasCoreTesting.LOG.info("CREEPER x2"); + } + + public List getPreviousEvolutions(ManasRaceInstance instance, LivingEntity entity) { + List list = new ArrayList<>(); + list.add(RegistryTest.TEST_RACE.get()); + return list; + } + + public Pair, BlockState> getRespawnDimension(ManasRaceInstance instance, LivingEntity owner) { + return Pair.of(Level.END, Blocks.END_STONE.defaultBlockState()); + } +} diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java new file mode 100644 index 00000000..487b245a --- /dev/null +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestSkill.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.testing.registry; + +import io.github.manasmods.manascore.config.ConfigRegistry; +import io.github.manasmods.manascore.network.api.util.Changeable; +import io.github.manasmods.manascore.skill.api.EntityEvents; +import io.github.manasmods.manascore.skill.api.ManasSkill; +import io.github.manasmods.manascore.skill.api.ManasSkillInstance; +import io.github.manasmods.manascore.skill.api.SkillAPI; +import io.github.manasmods.manascore.testing.ManasCoreTesting; +import io.github.manasmods.manascore.testing.configs.SkillConfig; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.animal.IronGolem; +import net.minecraft.world.entity.animal.Pig; +import net.minecraft.world.entity.animal.axolotl.Axolotl; +import net.minecraft.world.entity.monster.Creeper; +import net.minecraft.world.entity.monster.Slime; +import net.minecraft.world.entity.monster.Spider; +import net.minecraft.world.entity.npc.Villager; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.Arrow; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.entity.projectile.ProjectileDeflection; +import net.minecraft.world.entity.projectile.ThrownTrident; +import net.minecraft.world.phys.EntityHitResult; +import org.jetbrains.annotations.Nullable; + +public class TestSkill extends ManasSkill { + public TestSkill() { + super(); + ManasCoreTesting.LOG.info("Created skill!"); + this.addHeldAttributeModifier(Attributes.MOVEMENT_SPEED, "test_speed", -0.95, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL); + } + + public int getModes(ManasSkillInstance instance) { + return 2; + } + + public int getMaxHeldTime(ManasSkillInstance instance, LivingEntity entity) { + return 100; + } + + public boolean canBeToggled(ManasSkillInstance instance, LivingEntity entity) { + return entity.isShiftKeyDown(); + } + + public boolean canTick(ManasSkillInstance instance, LivingEntity entity) { + return instance.isToggled(); + } + + public boolean canScroll(ManasSkillInstance instance, LivingEntity entity) { + return entity.isShiftKeyDown(); + } + + public boolean canIgnoreCoolDown(ManasSkillInstance instance, LivingEntity entity, int mode) { + return mode == 1 && entity.isShiftKeyDown(); + } + + public void onToggleOn(ManasSkillInstance instance, LivingEntity entity) { + ManasCoreTesting.LOG.info("Toggled On"); + } + + public void onToggleOff(ManasSkillInstance instance, LivingEntity entity) { + ManasCoreTesting.LOG.info("Toggled Off"); + } + + public void onPressed(ManasSkillInstance instance, LivingEntity entity, int keyNumber, int mode) { + ManasCoreTesting.LOG.info("I'm pressed"); + if (mode == 1) ManasCoreTesting.LOG.info("In second mode"); + } + + public boolean onHeld(ManasSkillInstance instance, LivingEntity living, int heldTicks, int mode) { + ManasCoreTesting.LOG.info("Held for {} ticks", heldTicks); + if (mode == 1) ManasCoreTesting.LOG.info("In second mode"); + return true; + } + + public void onRelease(ManasSkillInstance instance, LivingEntity entity, int heldTicks, int keyNumber, int mode) { + ManasCoreTesting.LOG.info("I'm released after {} ticks", heldTicks); + if (mode == 1) { + ManasCoreTesting.LOG.info("In second mode"); + instance.setCoolDown(5, mode); + } + } + + public void addHeldAttributeModifiers(ManasSkillInstance instance, LivingEntity entity, int mode) { + if (mode == 1) return; + super.addHeldAttributeModifiers(instance, entity, mode); + } + + public void onTick(ManasSkillInstance instance, LivingEntity living) { + if (living.isShiftKeyDown()) ManasCoreTesting.LOG.info("You're sneaky"); + } + + public void onScroll(ManasSkillInstance instance, LivingEntity living, double delta, int mode) { + ManasCoreTesting.LOG.info("Scroll delta: {}", delta); + } + + public void onLearnSkill(ManasSkillInstance instance, LivingEntity entity) { + ManasCoreTesting.LOG.info("Learnt test skill"); + } + + public void onForgetSkill(ManasSkillInstance instance, LivingEntity entity) { + ManasCoreTesting.LOG.info("Forgot test skill"); + } + + public boolean onEffectAdded(ManasSkillInstance instance, LivingEntity entity, @Nullable Entity source, Changeable effect) { + MobEffectInstance effectInstance = effect.get(); + if (effectInstance == null) return false; + if (effectInstance.getEffect().equals(MobEffects.BLINDNESS)) return false; + + if (effectInstance.getEffect().equals(MobEffects.POISON)) { + ManasCoreTesting.LOG.info("Poison is bad!"); + effect.set(new MobEffectInstance(MobEffects.GLOWING, effectInstance.getDuration(), effectInstance.getAmplifier())); + } + return true; + } + + public boolean onBeingTargeted(ManasSkillInstance instance, Changeable target, LivingEntity mob) { + if (mob instanceof Spider) ManasCoreTesting.LOG.info("Targeted by {}", mob.getName()); + return true; + } + + public boolean onBeingDamaged(ManasSkillInstance instance, LivingEntity entity, DamageSource source, float amount) { + if (source.equals(entity.level().damageSources().cactus())) { + ManasCoreTesting.LOG.info("No cactus touchy"); + return false; + } + return true; + } + + public boolean onDamageEntity(ManasSkillInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable amount) { + SkillConfig skillConfig = ConfigRegistry.getConfig(SkillConfig.class); + if (target instanceof Creeper creeper && skillConfig.instaKillCreeper) { + creeper.hurt(owner.level().damageSources().dragonBreath(), 100F); + ManasCoreTesting.LOG.info("No creeper"); + } else if (target instanceof IronGolem) { + amount.set(amount.get() * skillConfig.ironGolemDamageMultiplier); + } else if (target instanceof Axolotl) { + amount.set(0F); + } else if (target instanceof Player) amount.set(amount.get() * 10F); + ManasCoreTesting.LOG.info("Dealt {} damage.", amount.get()); + return true; + } + + public boolean onTouchEntity(ManasSkillInstance instance, LivingEntity owner, LivingEntity target, DamageSource source, Changeable amount) { + if (owner.isShiftKeyDown() && target instanceof Villager) { + instance.setMastery(instance.getMastery() + 1); + ManasCoreTesting.LOG.info("My mastery is {}", instance.getMastery()); + amount.set(amount.get() * 100F); + } else if (target instanceof Pig) amount.set(0F); + ManasCoreTesting.LOG.info("Dealt {} damage.", amount.get()); + return true; + } + + public boolean onTakenDamage(ManasSkillInstance instance, LivingEntity owner, DamageSource source, Changeable amount) { + if (source.equals(owner.level().damageSources().lava())) amount.set(0F); + if (owner.isShiftKeyDown()) { + owner.heal(amount.get()); + ManasCoreTesting.LOG.info("Healed {} by {} health", owner.getName().getString(), amount.get()); + } + + if (source.getEntity() instanceof Slime) { + SkillAPI.getSkillsFrom(owner).learnSkill(RegistryTest.TEST_SKILL_2.get()); + } + return true; + } + + public void onProjectileHit(ManasSkillInstance instance, LivingEntity living, EntityHitResult hitResult, Projectile projectile, Changeable deflectionChangeable, Changeable result) { + if (projectile instanceof ThrownTrident) { + ManasCoreTesting.LOG.info("Dodged"); + result.set(EntityEvents.ProjectileHitResult.PASS); + } else if (projectile instanceof Arrow) { + if (living.isShiftKeyDown()) { + result.set(EntityEvents.ProjectileHitResult.PASS); + } else { + result.set(EntityEvents.ProjectileHitResult.DEFAULT); + deflectionChangeable.set(ProjectileDeflection.AIM_DEFLECT); + } + } + } + + public boolean onDeath(ManasSkillInstance instance, LivingEntity owner, DamageSource source) { + ManasCoreTesting.LOG.info("Welcome to the phantom realm"); + return true; + } + + public void onRespawn(ManasSkillInstance instance, ServerPlayer owner, boolean conqueredEnd) { + ManasCoreTesting.LOG.info("Welcome to the living realm"); + if (instance.is(TestTags.TEST_SKILL_TAG)) ManasCoreTesting.LOG.info("Im in the Tag!"); + } +} diff --git a/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestTags.java b/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestTags.java new file mode 100644 index 00000000..b62f1b95 --- /dev/null +++ b/testing-common/src/main/java/io/github/manasmods/manascore/testing/registry/TestTags.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025. ManasMods + * GNU General Public License 3 + */ + +package io.github.manasmods.manascore.testing.registry; + +import io.github.manasmods.manascore.skill.api.ManasSkill; +import io.github.manasmods.manascore.skill.api.SkillAPI; +import io.github.manasmods.manascore.testing.ModuleConstants; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; + +public class TestTags { + public static final TagKey TEST_SKILL_TAG = TagKey.create(SkillAPI.getSkillRegistryKey(), ResourceLocation.fromNamespaceAndPath(ModuleConstants.MOD_ID, "test_skill")); +} diff --git a/testing-common/src/main/resources/assets/manascore/lang/en_us.json b/testing-common/src/main/resources/assets/manascore/lang/en_us.json new file mode 100644 index 00000000..60ea309d --- /dev/null +++ b/testing-common/src/main/resources/assets/manascore/lang/en_us.json @@ -0,0 +1,16 @@ +{ + "item.manascore_testing.test_item": "Test Item", + "item.manascore_testing.test_block_item": "Test Block Item", + "block.manascore_testing.test_block": "Test Block", + "entity.manascore_testing.test_entity": "Test Entity", + "effect.manascore_testing.test_mob_effect": "Test Mob Effect", + "effect.manascore_testing.test_mob_effect_particle": "Test Mob Effect with custom Particle", + "item.minecraft.lingering_potion.effect.lmao_potion": "Test Lingering Potion", + "item.minecraft.splash_potion.effect.lmao_potion": "Test Splash Potion", + "item.minecraft.potion.effect.lmao_potion": "Test Potion", + "item.minecraft.tipped_arrow.effect.lmao_potion": "Test Tipped Arrow", + "manascore.attribute.test_attribute_player": "Test Player Attribute", + "manascore.attribute.test_attribute_all": "Test Generic Attribute", + "manascore_testing.skill.test_skill": "Test Skill", + "manascore_testing.skill.test_skill.description": "A skill for Testing." +} \ No newline at end of file diff --git a/testing-fabric/build.gradle b/testing-fabric/build.gradle index 125f3ade..7fb8e416 100644 --- a/testing-fabric/build.gradle +++ b/testing-fabric/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -27,6 +27,8 @@ dependencies { implementation(project(path: "$it-fabric", configuration: 'namedElements')) } modImplementation("com.terraformersmc:modmenu:11.0.2") + modImplementation("com.electronwill.night-config:toml:3.8.1") + modImplementation("com.electronwill.night-config:core:3.8.1") modImplementation("maven.modrinth:fabric-permissions-api:$fabric_permissions_api_version") modImplementation("maven.modrinth:luckperms:$fabric_luckperms_version") diff --git a/testing-fabric/src/main/java/io/github/manasmods/manascore/testing/fabric/ManasCoreTestingFabric.java b/testing-fabric/src/main/java/io/github/manasmods/manascore/testing/fabric/ManasCoreTestingFabric.java index f7a01956..ec1e5264 100644 --- a/testing-fabric/src/main/java/io/github/manasmods/manascore/testing/fabric/ManasCoreTestingFabric.java +++ b/testing-fabric/src/main/java/io/github/manasmods/manascore/testing/fabric/ManasCoreTestingFabric.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/testing-fabric/src/main/resources/assets/manascore/icon.png b/testing-fabric/src/main/resources/assets/manascore/icon.png index c9a7b039..a38ae1d1 100644 Binary files a/testing-fabric/src/main/resources/assets/manascore/icon.png and b/testing-fabric/src/main/resources/assets/manascore/icon.png differ diff --git a/testing-fabric/src/main/resources/assets/manascore/logo.png b/testing-fabric/src/main/resources/assets/manascore/logo.png new file mode 100644 index 00000000..c9a7b039 Binary files /dev/null and b/testing-fabric/src/main/resources/assets/manascore/logo.png differ diff --git a/testing-neoforge/build.gradle b/testing-neoforge/build.gradle index 5441812a..f2236fcd 100644 --- a/testing-neoforge/build.gradle +++ b/testing-neoforge/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ diff --git a/testing-neoforge/src/main/java/io/github/manasmods/manascore/testing/neoforge/ManasCoreTestingNeoForge.java b/testing-neoforge/src/main/java/io/github/manasmods/manascore/testing/neoforge/ManasCoreTestingNeoForge.java index 46098b9d..d6153c2a 100644 --- a/testing-neoforge/src/main/java/io/github/manasmods/manascore/testing/neoforge/ManasCoreTestingNeoForge.java +++ b/testing-neoforge/src/main/java/io/github/manasmods/manascore/testing/neoforge/ManasCoreTestingNeoForge.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. ManasMods + * Copyright (c) 2025. ManasMods * GNU General Public License 3 */ @@ -7,11 +7,12 @@ import io.github.manasmods.manascore.testing.ManasCoreTesting; import io.github.manasmods.manascore.testing.ModuleConstants; +import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; @Mod(ModuleConstants.MOD_ID) public final class ManasCoreTestingNeoForge { - public ManasCoreTestingNeoForge() { + public ManasCoreTestingNeoForge(IEventBus bus) { ManasCoreTesting.init(); } }