From bbcf6ead9abd43bd57843c5eb24a3aa953cf9ac3 Mon Sep 17 00:00:00 2001 From: My Name Date: Wed, 24 Sep 2025 18:21:17 +0200 Subject: [PATCH 1/7] gene (Phantom Immunity) --- .../palladium/powers/phantom_immunity.json | 17 ++++++++++ .../forge/events/PhantomSpawnHandler.java | 34 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 common/src/main/resources/data/bql/palladium/powers/phantom_immunity.json create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/events/PhantomSpawnHandler.java diff --git a/common/src/main/resources/data/bql/palladium/powers/phantom_immunity.json b/common/src/main/resources/data/bql/palladium/powers/phantom_immunity.json new file mode 100644 index 00000000..b4929451 --- /dev/null +++ b/common/src/main/resources/data/bql/palladium/powers/phantom_immunity.json @@ -0,0 +1,17 @@ +{ + "name": "Phantom Immunity", + "background": "minecraft:textures/block/white_wool.png", + "icon": "minecraft:phantom_membrane", + "gui_display_type": "tree", + "abilities": { + "Gene Ability": { + "title" : "Passive - Phantom Immunity", + "icon" : "minecraft:phantom_membrane", + "type": "palladium:dummy", + "description": "Phantoms cannot spawn on you", + "hidden": false, + "hidden_in_bar": true, + "gui_position": [0,0] + } + } +} \ No newline at end of file diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/events/PhantomSpawnHandler.java b/forge/src/main/java/com/github/b4ndithelps/forge/events/PhantomSpawnHandler.java new file mode 100644 index 00000000..74bd1d04 --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/events/PhantomSpawnHandler.java @@ -0,0 +1,34 @@ +package com.github.b4ndithelps.forge.events; + +import com.github.b4ndithelps.BanditsQuirkLib; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.monster.Phantom; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.event.entity.living.MobSpawnEvent; +import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.threetag.palladium.power.SuperpowerUtil; + +@Mod.EventBusSubscriber(modid = BanditsQuirkLib.MOD_ID) +public class PhantomSpawnHandler { + + @SubscribeEvent + public static void onCheckSpawn(MobSpawnEvent.FinalizeSpawn event) { + + if (event.getEntity() instanceof Phantom phantom) { + if (event.getLevel().isClientSide()) return; + + + var players = event.getLevel().players(); + for (var player : players) { + + if (SuperpowerUtil.hasSuperpower(player, ResourceLocation.parse("bql:phantom_immunity"))) { + event.setSpawnCancelled(true); + break; + } + } + } + } +} From e4dade0b29a0f15d75fe72f3a48af46b3f47107d Mon Sep 17 00:00:00 2001 From: My Name Date: Tue, 30 Sep 2025 20:03:57 +0200 Subject: [PATCH 2/7] gene (Adrenaline Gene && Dead Cells) --- .../bql/palladium/powers/adrenalinegene.json | 26 +++++ .../data/bql/palladium/powers/dead_cells.json | 17 ++++ .../forge/BanditsQuirkLibForge.java | 2 + .../forge/abilities/AbilityRegister.java | 2 + .../forge/abilities/AdrenalineAbility.java | 44 +++++++++ .../forge/client/ScreenFadeOverlay.java | 92 ++++++++++++++++++ .../forge/events/ZombieTargetingHandler.java | 29 ++++++ .../b4ndithelps/forge/network/BQLNetwork.java | 6 ++ .../forge/network/BlackScreenNetwork.java | 66 +++++++++++++ .../b4ndithelps/forge/sounds/ModSounds.java | 16 +++ .../assets/bandits_quirk_lib/sounds.json | 7 ++ .../bandits_quirk_lib/sounds/heartbeat.ogg | Bin 0 -> 44857 bytes 12 files changed, 307 insertions(+) create mode 100644 common/src/main/resources/data/bql/palladium/powers/adrenalinegene.json create mode 100644 common/src/main/resources/data/bql/palladium/powers/dead_cells.json create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/client/ScreenFadeOverlay.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/events/ZombieTargetingHandler.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/network/BlackScreenNetwork.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/sounds/ModSounds.java create mode 100644 forge/src/main/resources/assets/bandits_quirk_lib/sounds.json create mode 100644 forge/src/main/resources/assets/bandits_quirk_lib/sounds/heartbeat.ogg diff --git a/common/src/main/resources/data/bql/palladium/powers/adrenalinegene.json b/common/src/main/resources/data/bql/palladium/powers/adrenalinegene.json new file mode 100644 index 00000000..06186c2c --- /dev/null +++ b/common/src/main/resources/data/bql/palladium/powers/adrenalinegene.json @@ -0,0 +1,26 @@ +{ + "name": "Adrenaline Gene", + "background": "minecraft:textures/block/blue_wool.png", + "icon": "minecraft:rabbit_foot", + "gui_display_type": "tree", + "abilities": { + "Gene Ability": { + "title" : "Passive - Adrenaline", + "icon" : "minecraft:rabbit_foot", + "type": "bandits_quirk_lib:adrenaline", + "description": "When low on health your adrenaline kicks in", + "hidden": false, + "hidden_in_bar": true, + "gui_position": [0,0], + "conditions": { + "enabling": [ + { + "type": "palladium:health", + "min_health": 0, + "max_health": 6 + } + ] + } + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/data/bql/palladium/powers/dead_cells.json b/common/src/main/resources/data/bql/palladium/powers/dead_cells.json new file mode 100644 index 00000000..a0c5396e --- /dev/null +++ b/common/src/main/resources/data/bql/palladium/powers/dead_cells.json @@ -0,0 +1,17 @@ +{ + "name": "Dead Cells", + "background": "minecraft:textures/block/green_wool.png", + "icon": "minecraft:zombie_head", + "gui_display_type": "tree", + "abilities": { + "Gene Ability": { + "title" : "Passive - Dead Cells", + "icon" : "minecraft:zombie_head", + "type": "palladium:dummy", + "description": "Zombies are neutral towards you.", + "hidden": false, + "hidden_in_bar": true, + "gui_position": [0,0] + } + } +} \ No newline at end of file diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/BanditsQuirkLibForge.java b/forge/src/main/java/com/github/b4ndithelps/forge/BanditsQuirkLibForge.java index 6f54b403..59486d4d 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/BanditsQuirkLibForge.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/BanditsQuirkLibForge.java @@ -11,6 +11,7 @@ import com.github.b4ndithelps.forge.network.BQLNetwork; import com.github.b4ndithelps.forge.config.ConfigManager; import com.github.b4ndithelps.forge.config.ModGameRules; +import com.github.b4ndithelps.forge.sounds.ModSounds; import dev.architectury.platform.forge.EventBuses; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.fml.common.Mod; @@ -49,6 +50,7 @@ public BanditsQuirkLibForge() { ModBlocks.register(modEventBus); ModEntities.register(modEventBus); ModCreativeTabs.register(modEventBus); + ModSounds.SOUND_EVENTS.register(modEventBus); BQLNetwork.register(); diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AbilityRegister.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AbilityRegister.java index 573d5580..5a33663e 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AbilityRegister.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AbilityRegister.java @@ -24,6 +24,7 @@ public class AbilityRegister { public static final RegistrySupplier ENHANCED_PUNCH; public static final RegistrySupplier ENHANCED_KICK; public static final RegistrySupplier CHARGED_PUNCH; + public static final RegistrySupplier ADRENALINE; public AbilityRegister() { @@ -52,5 +53,6 @@ public static void init() { ENHANCED_PUNCH = ABILITIES.register("enhanced_punch", EnhancedPunchAbility::new); ENHANCED_KICK = ABILITIES.register("enhanced_kick", EnhancedKickAbility::new); CHARGED_PUNCH = ABILITIES.register("charged_punch", ChargedPunchAbility::new); + ADRENALINE = ABILITIES.register("adrenaline", AdrenalineAbility::new); } } diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java new file mode 100644 index 00000000..d07ca15a --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java @@ -0,0 +1,44 @@ +package com.github.b4ndithelps.forge.abilities; + +import com.github.b4ndithelps.forge.client.ScreenFadeOverlay; +import com.github.b4ndithelps.forge.network.BQLNetwork; +import com.github.b4ndithelps.forge.network.BlackScreenNetwork; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.LivingEntity; +import net.minecraftforge.network.PacketDistributor; +import net.threetag.palladium.power.IPowerHolder; +import net.threetag.palladium.power.ability.Ability; +import net.threetag.palladium.power.ability.AbilityInstance; +import net.threetag.palladium.util.property.IntegerProperty; +import net.threetag.palladium.util.property.PalladiumProperty; +import net.threetag.palladium.util.property.PropertyManager; +import net.threetag.palladium.util.property.SyncType; + +public class AdrenalineAbility extends Ability { + + public AdrenalineAbility() { + super(); + } + + @Override + public void firstTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { + if (!enabled) return; + if (!(entity instanceof ServerPlayer player)) return; + if (!(player.level() instanceof ServerLevel level)) return; + + + long ticks = level.getGameTime(); + long lastUseTick = player.getPersistentData().getLong("lastUseTimeAdrenaline"); + + player.getPersistentData().putLong("lastUseTimeAdrenaline", ticks); + BQLNetwork.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), new BlackScreenNetwork(player.getUUID())); + player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SPEED, 600, 1)); + + } +} diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/client/ScreenFadeOverlay.java b/forge/src/main/java/com/github/b4ndithelps/forge/client/ScreenFadeOverlay.java new file mode 100644 index 00000000..a9fd24c9 --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/client/ScreenFadeOverlay.java @@ -0,0 +1,92 @@ +package com.github.b4ndithelps.forge.client; + +import com.github.b4ndithelps.BanditsQuirkLib; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import com.mojang.blaze3d.vertex.VertexFormat.Mode; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RenderGuiOverlayEvent; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +/** + * A simple class that renders a black screen overlay and fades it out. + * Register this class to the Forge event bus from your client setup. + */ +@Mod.EventBusSubscriber(modid = BanditsQuirkLib.MOD_ID, value = Dist.CLIENT) +public class ScreenFadeOverlay { + + private static boolean blackoutActive = false; + private static float alpha = 0f; // current transparency (0 = invisible, 1 = black) + private static float fadeSpeed = 0.03f; // how fast to fade each tick + + /** + * Call this method when you want the fade-out effect to begin. + * For example, trigger this from a network packet sent from the server. + */ + public static void startFadeOut() { + blackoutActive = true; + alpha = 0.55f; // start fully black + } + + /** + * Ticks the fade every client tick. + */ + @SubscribeEvent + public static void onClientTick(TickEvent.ClientTickEvent event) { + if (event.phase != TickEvent.ClientTickEvent.Phase.END) return; + + if (blackoutActive) { + alpha -= fadeSpeed; + if (alpha <= 0f) { + alpha = 0f; + blackoutActive = false; // stop once finished + } + } + } + + /** + * Renders the black overlay each frame. + */ + @SubscribeEvent + public static void onRenderOverlay(RenderGuiOverlayEvent.Post event) { + // We want to render after everything else, so Post is fine. + if (!blackoutActive && alpha <= 0f) return; +// Player player = Minecraft.getInstance().player; + Minecraft mc = Minecraft.getInstance(); +// if (player == null || Minecraft.getInstance().level == null) return; +// if (mc.player != null) { +// mc.player.playSound( +// SoundEvents.EXPERIENCE_ORB_PICKUP, // example sound +// 1.0F, // volume +// 1.0F // pitch +// ); +// } + + int width = mc.getWindow().getGuiScaledWidth(); + int height = mc.getWindow().getGuiScaledHeight(); + + RenderSystem.disableDepthTest(); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + + Tesselator tessellator = Tesselator.getInstance(); + BufferBuilder buffer = tessellator.getBuilder(); + + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + buffer.vertex(0, height, 0).color(0f, 0f, 0f, alpha).endVertex(); + buffer.vertex(width, height, 0).color(0f, 0f, 0f, alpha).endVertex(); + buffer.vertex(width, 0, 0).color(0f, 0f, 0f, alpha).endVertex(); + buffer.vertex(0, 0, 0).color(0f, 0f, 0f, alpha).endVertex(); + tessellator.end(); + + RenderSystem.disableBlend(); + RenderSystem.enableDepthTest(); + } +} \ No newline at end of file diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/events/ZombieTargetingHandler.java b/forge/src/main/java/com/github/b4ndithelps/forge/events/ZombieTargetingHandler.java new file mode 100644 index 00000000..e46e6d5d --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/events/ZombieTargetingHandler.java @@ -0,0 +1,29 @@ +package com.github.b4ndithelps.forge.events; + +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.event.entity.living.LivingChangeTargetEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraft.world.entity.monster.Zombie; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.LivingEntity; +import net.threetag.palladium.power.SuperpowerUtil; + +@Mod.EventBusSubscriber +public class ZombieTargetingHandler { + + @SubscribeEvent + public static void onTargetSet(LivingChangeTargetEvent event) { + if (!(event.getEntity() instanceof Zombie zombie)) return; + LivingEntity target = event.getNewTarget(); + if (!(target instanceof Player player)) return; + + if (SuperpowerUtil.hasSuperpower(player, ResourceLocation.parse("bql:dead_cells"))) { + // Zombies attack back automatically if hurt, so we (me and chatgpt) only want to block "normal aggro" + if (zombie.getLastAttacker() != player) { + event.setCanceled(true); + } + } + } +} diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/network/BQLNetwork.java b/forge/src/main/java/com/github/b4ndithelps/forge/network/BQLNetwork.java index af4548d8..579837f2 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/network/BQLNetwork.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/network/BQLNetwork.java @@ -43,6 +43,12 @@ public static void register() { .decoder(MineHaSlotSyncPacket::decode) .consumerMainThread(MineHaSlotSyncPacket::handle) .add(); + + CHANNEL.messageBuilder(BlackScreenNetwork.class, index++, NetworkDirection.PLAY_TO_CLIENT) + .encoder(BlackScreenNetwork::encode) + .decoder(BlackScreenNetwork::decode) + .consumerMainThread(BlackScreenNetwork::handle) + .add(); } } diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/network/BlackScreenNetwork.java b/forge/src/main/java/com/github/b4ndithelps/forge/network/BlackScreenNetwork.java new file mode 100644 index 00000000..cdcaa464 --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/network/BlackScreenNetwork.java @@ -0,0 +1,66 @@ +package com.github.b4ndithelps.forge.network; + +import com.github.b4ndithelps.forge.capabilities.StaminaDataProvider; +import com.github.b4ndithelps.forge.client.ScreenFadeOverlay; +import com.github.b4ndithelps.forge.sounds.ModSounds; +import net.minecraft.client.Minecraft; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.network.NetworkEvent; + +import java.util.UUID; +import java.util.function.Supplier; + +/** + * Packet for synchronizing Stamina capability data (including upgrade points) from server to client. + */ +public class BlackScreenNetwork { + private final UUID playerUUID; + + public BlackScreenNetwork(UUID playerUUID) { + this.playerUUID = playerUUID; + } + + public void encode(FriendlyByteBuf buffer) { + buffer.writeUUID(playerUUID); + } + + public static BlackScreenNetwork decode(FriendlyByteBuf buffer) { + UUID id = buffer.readUUID(); + return new BlackScreenNetwork(id); + } + + public boolean handle(Supplier contextSupplier) { + NetworkEvent.Context context = contextSupplier.get(); + context.enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> handleClientSide())); + return true; + } + + private void handleClientSide() { + Player player = Minecraft.getInstance().level != null ? Minecraft.getInstance().level.getPlayerByUUID(playerUUID) : null; + if (player == null) return; + + Minecraft mc = Minecraft.getInstance(); + if (mc.player != null) { + mc.player.playSound( + ModSounds.HEARTBEAT.get(), // example sound + 1.0F, // volume + 1.0F // pitch + ); + } + + ScreenFadeOverlay.startFadeOut(); + } + + public static BlackScreenNetwork fullSync(Player player) { + CompoundTag tag = new CompoundTag(); + return new BlackScreenNetwork(player.getUUID()); + } +} + + diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/sounds/ModSounds.java b/forge/src/main/java/com/github/b4ndithelps/forge/sounds/ModSounds.java new file mode 100644 index 00000000..da18f38f --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/sounds/ModSounds.java @@ -0,0 +1,16 @@ +package com.github.b4ndithelps.forge.sounds; + +import com.github.b4ndithelps.BanditsQuirkLib; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvent; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +public class ModSounds { + public static final DeferredRegister SOUND_EVENTS = + DeferredRegister.create(ForgeRegistries.SOUND_EVENTS, BanditsQuirkLib.MOD_ID); + public static final RegistryObject HEARTBEAT = + SOUND_EVENTS.register("heartbeat", + () -> SoundEvent.createVariableRangeEvent(new ResourceLocation(BanditsQuirkLib.MOD_ID, "heartbeat"))); +} diff --git a/forge/src/main/resources/assets/bandits_quirk_lib/sounds.json b/forge/src/main/resources/assets/bandits_quirk_lib/sounds.json new file mode 100644 index 00000000..ab395022 --- /dev/null +++ b/forge/src/main/resources/assets/bandits_quirk_lib/sounds.json @@ -0,0 +1,7 @@ +{ + "heartbeat": { + "sounds": [ + { "name": "bandits_quirk_lib:heartbeat" } + ] + } +} \ No newline at end of file diff --git a/forge/src/main/resources/assets/bandits_quirk_lib/sounds/heartbeat.ogg b/forge/src/main/resources/assets/bandits_quirk_lib/sounds/heartbeat.ogg new file mode 100644 index 0000000000000000000000000000000000000000..feaf754fb4eda3bf488e9ffe12d8597226165645 GIT binary patch literal 44857 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 FOnK*tRV1ip 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 YEb=0Y_WjZpxxK~8ec>(gOwEg10H; Date: Thu, 2 Oct 2025 20:41:36 +0200 Subject: [PATCH 3/7] gene (Ender Gene) --- .../model_layers/ender_gene_geo.json | 59 +++++++++++++ .../render_layers/ender_gene_overlay.json | 21 +++++ .../bql/textures/models/ender_gene/mask_0.png | Bin 0 -> 166 bytes .../bql/textures/models/ender_gene/mask_1.png | Bin 0 -> 166 bytes .../textures/models/ender_gene/mask_10.png | Bin 0 -> 104 bytes .../bql/textures/models/ender_gene/mask_2.png | Bin 0 -> 440 bytes .../bql/textures/models/ender_gene/mask_3.png | Bin 0 -> 440 bytes .../bql/textures/models/ender_gene/mask_4.png | Bin 0 -> 478 bytes .../bql/textures/models/ender_gene/mask_5.png | Bin 0 -> 478 bytes .../bql/textures/models/ender_gene/mask_6.png | Bin 0 -> 481 bytes .../bql/textures/models/ender_gene/mask_7.png | Bin 0 -> 481 bytes .../bql/textures/models/ender_gene/mask_8.png | Bin 0 -> 104 bytes .../bql/textures/models/ender_gene/mask_9.png | Bin 0 -> 104 bytes .../bql/textures/models/ender_gene_tp.png | Bin 0 -> 168 bytes .../data/bql/palladium/powers/ender_gene.json | 78 ++++++++++++++++++ .../forge/abilities/AbilityRegister.java | 2 + .../forge/abilities/AdrenalineAbility.java | 4 + .../forge/abilities/EnderGeneTP.java | 51 ++++++++++++ 18 files changed, 215 insertions(+) create mode 100644 common/src/main/resources/assets/bql/palladium/model_layers/ender_gene_geo.json create mode 100644 common/src/main/resources/assets/bql/palladium/render_layers/ender_gene_overlay.json create mode 100644 common/src/main/resources/assets/bql/textures/models/ender_gene/mask_0.png create mode 100644 common/src/main/resources/assets/bql/textures/models/ender_gene/mask_1.png create mode 100644 common/src/main/resources/assets/bql/textures/models/ender_gene/mask_10.png create mode 100644 common/src/main/resources/assets/bql/textures/models/ender_gene/mask_2.png create mode 100644 common/src/main/resources/assets/bql/textures/models/ender_gene/mask_3.png create mode 100644 common/src/main/resources/assets/bql/textures/models/ender_gene/mask_4.png create mode 100644 common/src/main/resources/assets/bql/textures/models/ender_gene/mask_5.png create mode 100644 common/src/main/resources/assets/bql/textures/models/ender_gene/mask_6.png create mode 100644 common/src/main/resources/assets/bql/textures/models/ender_gene/mask_7.png create mode 100644 common/src/main/resources/assets/bql/textures/models/ender_gene/mask_8.png create mode 100644 common/src/main/resources/assets/bql/textures/models/ender_gene/mask_9.png create mode 100644 common/src/main/resources/assets/bql/textures/models/ender_gene_tp.png create mode 100644 common/src/main/resources/data/bql/palladium/powers/ender_gene.json create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/abilities/EnderGeneTP.java diff --git a/common/src/main/resources/assets/bql/palladium/model_layers/ender_gene_geo.json b/common/src/main/resources/assets/bql/palladium/model_layers/ender_gene_geo.json new file mode 100644 index 00000000..76795239 --- /dev/null +++ b/common/src/main/resources/assets/bql/palladium/model_layers/ender_gene_geo.json @@ -0,0 +1,59 @@ +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.unknown", + "texture_width": 64, + "texture_height": 64, + "visible_bounds_width": 3, + "visible_bounds_height": 3.5, + "visible_bounds_offset": [0, 1.25, 0] + }, + "bones": [ + { + "name": "head", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 24, -4], "size": [8, 8, 8], "inflate": 0.5, "uv": [32, 0]} + ] + }, + { + "name": "body", + "pivot": [0, 24, 0], + "cubes": [ + {"origin": [-4, 12, -2], "size": [8, 12, 4], "inflate": 0.25, "uv": [16, 32]} + ] + }, + { + "name": "right_arm", + "pivot": [-5, 22, 0], + "cubes": [ + {"origin": [-8, 12, -2], "size": [4, 12, 4], "inflate": 0.25, "uv": [40, 32]} + ] + }, + { + "name": "left_arm", + "pivot": [5, 22, 0], + "cubes": [ + {"origin": [4, 12, -2], "size": [4, 12, 4], "inflate": 0.25, "uv": [48, 48]} + ] + }, + { + "name": "right_leg", + "pivot": [-1.9, 12, 0], + "cubes": [ + {"origin": [-3.9, 0, -2], "size": [4, 12, 4], "inflate": 0.25, "uv": [0, 32]} + ] + }, + { + "name": "left_leg", + "pivot": [1.9, 12, 0], + "cubes": [ + {"origin": [-0.1, 0, -2], "size": [4, 12, 4], "inflate": 0.25, "uv": [0, 48]} + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/assets/bql/palladium/render_layers/ender_gene_overlay.json b/common/src/main/resources/assets/bql/palladium/render_layers/ender_gene_overlay.json new file mode 100644 index 00000000..99d1a0d1 --- /dev/null +++ b/common/src/main/resources/assets/bql/palladium/render_layers/ender_gene_overlay.json @@ -0,0 +1,21 @@ +{ + "model_layer": "bql:ender_gene_geo", + "texture": { + "base": "bql:textures/models/ender_gene_tp.png", + "transformers": [ + { + "type": "palladium:alpha_mask", + "mask": "bql:textures/models/ender_gene/mask_#MASK.png" + } + ], + "variables": { + "MASK": { + "type": "palladium:ability_integer_property", + "power": "bql:ender_gene", + "ability": "enablepls", + "property": "value" + } + }, + "output": "bql:textures/models/ender_gene_output/base_#MASK.png" + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_0.png b/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_0.png new file mode 100644 index 0000000000000000000000000000000000000000..dc5cbe3c3d82154d21edb21353195e011c96e41e GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGH$#^NA%Cx&(BWL^R}37#&FAsLNt z&pYxmCIxsd50E!_s`{L7XdP1;6t8)0|Vo?1rNXip00i_>zopr06)MNHUIzs literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_2.png b/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_2.png new file mode 100644 index 0000000000000000000000000000000000000000..811ac16b94e69e7214e6d7eb61124b5156e2a403 GIT binary patch literal 440 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGH$#^NA%Cx&(BWL^TB>AsLNt zXWkZSR^V|t`ttw(O|=W0m8Cos60_BxYo@JvbISKpiOlgfg{4!Pbr!^kt+H8OTz>85 z!V{~$6#mrqV0OBg%g<@rvW9_wVPyi_y3J&KdVj4u0^+BZO^UV znYnst!RxXk>8R&_ovkVh}|=9Ie6VSmg?OrnfE=7 z?*iY{5T@s^XD7d{dna=5x?9lqIcs{Y{<*73zFzfnLDG5trMwx*ek;Ds%d7F85(=^8 ztl#(34`&z^`zt@=k$7{YL~&Y8cwg?ZKI@GyY&G|Hi>%GQf8R@`isu~jx{|MdPE|H6 zUVZ$5%>&=){-;d_=MVg@`sbGRH8!uqi@$D`-zRbB7Z-tU?>C>YYSLtzvcSbx3hHmY d5VGE=n$KoilJ3FnA_0sF22WQ%mvv4FO#qis!nXhb literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_3.png b/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_3.png new file mode 100644 index 0000000000000000000000000000000000000000..811ac16b94e69e7214e6d7eb61124b5156e2a403 GIT binary patch literal 440 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGH$#^NA%Cx&(BWL^TB>AsLNt zXWkZSR^V|t`ttw(O|=W0m8Cos60_BxYo@JvbISKpiOlgfg{4!Pbr!^kt+H8OTz>85 z!V{~$6#mrqV0OBg%g<@rvW9_wVPyi_y3J&KdVj4u0^+BZO^UV znYnst!RxXk>8R&_ovkVh}|=9Ie6VSmg?OrnfE=7 z?*iY{5T@s^XD7d{dna=5x?9lqIcs{Y{<*73zFzfnLDG5trMwx*ek;Ds%d7F85(=^8 ztl#(34`&z^`zt@=k$7{YL~&Y8cwg?ZKI@GyY&G|Hi>%GQf8R@`isu~jx{|MdPE|H6 zUVZ$5%>&=){-;d_=MVg@`sbGRH8!uqi@$D`-zRbB7Z-tU?>C>YYSLtzvcSbx3hHmY d5VGE=n$KoilJ3FnA_0sF22WQ%mvv4FO#qis!nXhb literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_4.png b/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_4.png new file mode 100644 index 0000000000000000000000000000000000000000..ecfac8dfe505f6682c14e0a06e7f47258a53ee37 GIT binary patch literal 478 zcmV<40U`d0P)00001b5ch_0Itp) z=>Px$m`OxIRA_qZ*66Q?-NUH60B>>< zN0S1gNdeKMfM`-cG$|mO6cEk!0=^Emxa5P&8QSklDc9f6A8R>M57KUn!3c28;78<7 zFc3gQXZMFBcb0QNeL(xDQNOBf@x3EM?z6xdoKWQat$IgdKdCO2}BTe;&B??+TF%)?s-}-_zlw5xGGJ5 zkG&J|V8uAI`zUEe{9KCEE-52Utm8 UPVC#;VE_OC07*qoM6N<$g82;CL;wH) literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_5.png b/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_5.png new file mode 100644 index 0000000000000000000000000000000000000000..ecfac8dfe505f6682c14e0a06e7f47258a53ee37 GIT binary patch literal 478 zcmV<40U`d0P)00001b5ch_0Itp) z=>Px$m`OxIRA_qZ*66Q?-NUH60B>>< zN0S1gNdeKMfM`-cG$|mO6cEk!0=^Emxa5P&8QSklDc9f6A8R>M57KUn!3c28;78<7 zFc3gQXZMFBcb0QNeL(xDQNOBf@x3EM?z6xdoKWQat$IgdKdCO2}BTe;&B??+TF%)?s-}-_zlw5xGGJ5 zkG&J|V8uAI`zUEe{9KCEE-52Utm8 UPVC#;VE_OC07*qoM6N<$g82;CL;wH) literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_6.png b/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_6.png new file mode 100644 index 0000000000000000000000000000000000000000..ad1dc6afc81dda238c8113dec0f406fbe2c278b5 GIT binary patch literal 481 zcmV<70UrK|P)00001b5ch_0Itp) z=>Px$n@L1LRA_%eO7k!#*g#W{w6@`&diXx9aE>HV znWo~G&?fx)^X$Nw@2$Lk2JRWK=6hxPB?E(ezuA6o2Bdty*?#Xs%;x+3_RK=j9G`*H zynY7KKs0F}nluni8i*zhM3V-hNdwXRI+>HIC#h!8R9pS=p!GeMPNxUmC=Y0Na4^u0 z2_z>+i$0&vKpkc}*iySAXXn?EydxW;xzAGGd0FwyLgREajag&GKraF%N+h)XWMf?T zP(kD8rU@;Hez%fd(qDejUuk6U2Kw{f=`+oYtc@B^Pj*poKQ4VR`^;iaqjWv=-q^mW z=!Km@{2F3?5fUfOz+m04?Q6~k*td_BX=0{-PRX4(8k`o`pr`#$QNS(bInqEho+){s zg(Um>8Tfw&o^~z!CuYD@4;*Eo7jr#u2W4HgSPyt86p@MwsT6sl0bBe+AGwiv@_GY5 XUZ$Ge{vEml00000NkvXXu0mjfXF=Ju literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_7.png b/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_7.png new file mode 100644 index 0000000000000000000000000000000000000000..ad1dc6afc81dda238c8113dec0f406fbe2c278b5 GIT binary patch literal 481 zcmV<70UrK|P)00001b5ch_0Itp) z=>Px$n@L1LRA_%eO7k!#*g#W{w6@`&diXx9aE>HV znWo~G&?fx)^X$Nw@2$Lk2JRWK=6hxPB?E(ezuA6o2Bdty*?#Xs%;x+3_RK=j9G`*H zynY7KKs0F}nluni8i*zhM3V-hNdwXRI+>HIC#h!8R9pS=p!GeMPNxUmC=Y0Na4^u0 z2_z>+i$0&vKpkc}*iySAXXn?EydxW;xzAGGd0FwyLgREajag&GKraF%N+h)XWMf?T zP(kD8rU@;Hez%fd(qDejUuk6U2Kw{f=`+oYtc@B^Pj*poKQ4VR`^;iaqjWv=-q^mW z=!Km@{2F3?5fUfOz+m04?Q6~k*td_BX=0{-PRX4(8k`o`pr`#$QNS(bInqEho+){s zg(Um>8Tfw&o^~z!CuYD@4;*Eo7jr#u2W4HgSPyt86p@MwsT6sl0bBe+AGwiv@_GY5 XUZ$Ge{vEml00000NkvXXu0mjfXF=Ju literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_8.png b/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_8.png new file mode 100644 index 0000000000000000000000000000000000000000..c8ceaacbf2d2cff9cfea44d02ab93e07eee5a8db GIT binary patch literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGH$#^NA%Cx&(BWL^R}N}eu`AsLNt o4>Ixsd50E!_s`{L7XdP1;6t8)0|Vo?1rNXip00i_>zopr06)MNHUIzs literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_9.png b/common/src/main/resources/assets/bql/textures/models/ender_gene/mask_9.png new file mode 100644 index 0000000000000000000000000000000000000000..c8ceaacbf2d2cff9cfea44d02ab93e07eee5a8db GIT binary patch literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGH$#^NA%Cx&(BWL^R}N}eu`AsLNt o4>Ixsd50E!_s`{L7XdP1;6t8)0|Vo?1rNXip00i_>zopr06)MNHUIzs literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/ender_gene_tp.png b/common/src/main/resources/assets/bql/textures/models/ender_gene_tp.png new file mode 100644 index 0000000000000000000000000000000000000000..5fc5d7f26575438775cd7261aba040f64633f263 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGH$#^NA%Cx&(BWL^R}NuDl_AsLNt z&l~bGC@>r_II!Q%M1fIIhWF&EgxS?|K5kyh9A~{uKbLh* G2~7ax(Ki|Z literal 0 HcmV?d00001 diff --git a/common/src/main/resources/data/bql/palladium/powers/ender_gene.json b/common/src/main/resources/data/bql/palladium/powers/ender_gene.json new file mode 100644 index 00000000..0e06f205 --- /dev/null +++ b/common/src/main/resources/data/bql/palladium/powers/ender_gene.json @@ -0,0 +1,78 @@ +{ + "name": "Ender Gene", + "background": "minecraft:textures/block/blue_wool.png", + "icon": "minecraft:chorus_fruit", + "gui_display_type": "tree", + "abilities": { + "TP": { + "type": "bandits_quirk_lib:ender_gene_tp", + "hidden": true, + "hidden_in_bar": true, + "conditions": { + "enabling": [ + { + "type": "palladium:ability_integer_property", + "power": "null", + "ability": "enablepls", + "property": "value", + "min": 9, + "max": 10 + } + ] + } + }, + "Render Layer": { + "type": "palladium:render_layer", + "render_layer": "bql:ender_gene_overlay" + }, + "enablepls": { + "title" : "Ability - Ender Gene", + "type": "palladium:repeating_animation_timer", + "icon" : "minecraft:chorus_fruit", + "gui_position": [0,0], + "description": "You are able to use ender energy to teleport! But you cant control it.", + "hidden_in_bar": false, + "hidden": false, + "start_value": 0, + "max_value": 10, + "conditions": { + "enabling": [ + { + "type": "palladium:activation", + "cooldown": 60, + "ticks": 9, + "key_type": "key_bind", + "needs_empty_hand": false, + "allow_scrolling_when_crouching": true + } + ] + }, + "stamina_first_cost": 30 + }, + "hide overlay": { + "type": "palladium:hide_body_part", + "hidden": true, + "hidden_in_bar": true, + "list_index": -1, + "body_parts": [ + "head_overlay", + "chest_overlay", + "right_arm_overlay", + "left_arm_overlay", + "right_leg_overlay", + "left_leg_overlay" + ], + "affects_first_person": true, + "conditions": { + "enabling": [ + { + "type": "palladium:ability_enabled", + "power": "null", + "ability": "enablepls" + } + ] + } + } + + } +} \ No newline at end of file diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AbilityRegister.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AbilityRegister.java index 5a33663e..49529cbd 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AbilityRegister.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AbilityRegister.java @@ -25,6 +25,7 @@ public class AbilityRegister { public static final RegistrySupplier ENHANCED_KICK; public static final RegistrySupplier CHARGED_PUNCH; public static final RegistrySupplier ADRENALINE; + public static final RegistrySupplier ENDER_GENE_TP; public AbilityRegister() { @@ -54,5 +55,6 @@ public static void init() { ENHANCED_KICK = ABILITIES.register("enhanced_kick", EnhancedKickAbility::new); CHARGED_PUNCH = ABILITIES.register("charged_punch", ChargedPunchAbility::new); ADRENALINE = ABILITIES.register("adrenaline", AdrenalineAbility::new); + ENDER_GENE_TP = ABILITIES.register("ender_gene_tp", EnderGeneTP::new); } } diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java index d07ca15a..a2df605f 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java @@ -36,6 +36,10 @@ public void firstTick(LivingEntity entity, AbilityInstance entry, IPowerHolder h long ticks = level.getGameTime(); long lastUseTick = player.getPersistentData().getLong("lastUseTimeAdrenaline"); + if (ticks - lastUseTick < 1200) { // 60 seconds cooldown + return; + } +// player.sendSystemMessage(Component.literal(lastUseTick+"")); player.getPersistentData().putLong("lastUseTimeAdrenaline", ticks); BQLNetwork.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), new BlackScreenNetwork(player.getUUID())); player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SPEED, 600, 1)); diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/EnderGeneTP.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/EnderGeneTP.java new file mode 100644 index 00000000..35ed01f7 --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/EnderGeneTP.java @@ -0,0 +1,51 @@ +package com.github.b4ndithelps.forge.abilities; + +import com.github.b4ndithelps.forge.network.BQLNetwork; +import com.github.b4ndithelps.forge.network.BlackScreenNetwork; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.network.PacketDistributor; +import net.threetag.palladium.power.IPowerHolder; +import net.threetag.palladium.power.ability.Ability; +import net.threetag.palladium.power.ability.AbilityInstance; + +public class EnderGeneTP extends Ability { + + public EnderGeneTP() { + super(); + } + + @Override + public void firstTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { + if (!enabled) return; + if (!(entity.level() instanceof ServerLevel level)) return; + + double startX = entity.getX(); + double startY = entity.getY(); + double startZ = entity.getZ(); + + for (int i = 0; i < 16; ++i) { // 16 attempts, just like vanilla + double targetX = startX + (entity.getRandom().nextDouble() - 0.5D) * 16.0D; + double targetY = startY + (entity.getRandom().nextInt(16) - 8); + double targetZ = startZ + (entity.getRandom().nextDouble() - 0.5D) * 16.0D; + + if (entity.isPassenger()) entity.stopRiding(); + + Vec3 oldPos = entity.position(); + if (entity.randomTeleport(targetX, targetY, targetZ, true)) { + level.playSound(null, oldPos.x, oldPos.y, oldPos.z, + SoundEvents.CHORUS_FRUIT_TELEPORT, SoundSource.PLAYERS, 1.0F, 1.0F); + entity.playSound(SoundEvents.CHORUS_FRUIT_TELEPORT, 1.0F, 1.0F); + break; + } + } + + } +} \ No newline at end of file From 31b1fb42f1e487b0976af4281e94fa798cdde351 Mon Sep 17 00:00:00 2001 From: My Name Date: Wed, 5 Nov 2025 19:21:24 +0100 Subject: [PATCH 4/7] gene (Burst gene) --- .../bql/palladium/model_layers/explosion.json | 60 ++ .../render_layers/ender_gene_overlay.json | 2 +- .../render_layers/explosion_effects.json | 4 + .../render_layers/explosion_overlay.json | 15 + .../bql/textures/models/explosion_effects.png | Bin 0 -> 571 bytes .../models/explosion_overlay/texture1.png | Bin 0 -> 184 bytes .../models/explosion_overlay/texture10.png | Bin 0 -> 184 bytes .../models/explosion_overlay/texture11.png | Bin 0 -> 183 bytes .../models/explosion_overlay/texture12.png | Bin 0 -> 183 bytes .../models/explosion_overlay/texture13.png | Bin 0 -> 183 bytes .../models/explosion_overlay/texture14.png | Bin 0 -> 183 bytes .../models/explosion_overlay/texture15.png | Bin 0 -> 183 bytes .../models/explosion_overlay/texture16.png | Bin 0 -> 183 bytes .../models/explosion_overlay/texture17.png | Bin 0 -> 184 bytes .../models/explosion_overlay/texture18.png | Bin 0 -> 184 bytes .../models/explosion_overlay/texture19.png | Bin 0 -> 165 bytes .../models/explosion_overlay/texture2.png | Bin 0 -> 184 bytes .../models/explosion_overlay/texture20.png | Bin 0 -> 165 bytes .../models/explosion_overlay/texture21.png | Bin 0 -> 165 bytes .../models/explosion_overlay/texture22.png | Bin 0 -> 165 bytes .../models/explosion_overlay/texture23.png | Bin 0 -> 165 bytes .../models/explosion_overlay/texture24.png | Bin 0 -> 165 bytes .../models/explosion_overlay/texture25.png | Bin 0 -> 165 bytes .../models/explosion_overlay/texture26.png | Bin 0 -> 165 bytes .../models/explosion_overlay/texture27.png | Bin 0 -> 165 bytes .../models/explosion_overlay/texture28.png | Bin 0 -> 165 bytes .../models/explosion_overlay/texture29.png | Bin 0 -> 165 bytes .../models/explosion_overlay/texture3.png | Bin 0 -> 184 bytes .../models/explosion_overlay/texture30.png | Bin 0 -> 165 bytes .../models/explosion_overlay/texture4.png | Bin 0 -> 184 bytes .../models/explosion_overlay/texture5.png | Bin 0 -> 184 bytes .../models/explosion_overlay/texture6.png | Bin 0 -> 184 bytes .../models/explosion_overlay/texture7.png | Bin 0 -> 184 bytes .../models/explosion_overlay/texture8.png | Bin 0 -> 184 bytes .../models/explosion_overlay/texture9.png | Bin 0 -> 184 bytes .../data/bql/palladium/powers/burst_gene.json | 125 +++ forge/build.gradle | 8 + .../forge/BanditsQuirkLibForge.java | 3 +- .../forge/abilities/AbilityRegister.java | 7 +- .../forge/abilities/BurstGeneAbility.java | 216 ++++ .../forge/abilities/DetroitSmashAbility.java | 968 +++++++++--------- .../SuperBurstChargeGeneAbility.java | 134 +++ .../SuperBurstExplosionGeneAbility.java | 91 ++ .../forge/client/AnimationsInit.java | 28 + .../forge/client/ClientEventHandler.java | 1 + .../conditions/BurstRenderCondition.java | 61 ++ .../CustomConditionSerializers.java | 3 + .../forge/events/ModEventBusEvents.java | 25 + .../b4ndithelps/forge/network/BQLNetwork.java | 6 + .../forge/network/PlayerAnimationPacket.java | 78 ++ .../forge/particle/ModParticles.java | 25 + .../particle/custom/ChargingDustParticle.java | 85 ++ .../particle/custom/RisingDustParticle.java | 54 + .../b4ndithelps/forge/sounds/ModSounds.java | 3 + .../particles/charging_dust_particle.json | 5 + .../particles/rising_dust.json | 5 + .../core_explosion_burst.json | 141 +++ .../player_animation/explode_effects.json | 53 + .../assets/bandits_quirk_lib/sounds.json | 5 + .../sounds/explosion_charge.ogg | Bin 0 -> 22975 bytes .../textures/particle/charge_dust.png | Bin 0 -> 277 bytes .../textures/particle/particle.png | Bin 0 -> 356 bytes .../textures/particle/spark.png | Bin 0 -> 381 bytes 63 files changed, 1725 insertions(+), 486 deletions(-) create mode 100644 common/src/main/resources/assets/bql/palladium/model_layers/explosion.json create mode 100644 common/src/main/resources/assets/bql/palladium/render_layers/explosion_effects.json create mode 100644 common/src/main/resources/assets/bql/palladium/render_layers/explosion_overlay.json create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_effects.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture1.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture10.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture11.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture12.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture13.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture14.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture15.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture16.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture17.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture18.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture19.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture2.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture20.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture21.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture22.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture23.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture24.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture25.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture26.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture27.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture28.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture29.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture3.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture30.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture4.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture5.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture6.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture7.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture8.png create mode 100644 common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture9.png create mode 100644 common/src/main/resources/data/bql/palladium/powers/burst_gene.json create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/abilities/BurstGeneAbility.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstChargeGeneAbility.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstExplosionGeneAbility.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/client/AnimationsInit.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/conditions/BurstRenderCondition.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/events/ModEventBusEvents.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/network/PlayerAnimationPacket.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/particle/ModParticles.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/particle/custom/ChargingDustParticle.java create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/particle/custom/RisingDustParticle.java create mode 100644 forge/src/main/resources/assets/bandits_quirk_lib/particles/charging_dust_particle.json create mode 100644 forge/src/main/resources/assets/bandits_quirk_lib/particles/rising_dust.json create mode 100644 forge/src/main/resources/assets/bandits_quirk_lib/player_animation/core_explosion_burst.json create mode 100644 forge/src/main/resources/assets/bandits_quirk_lib/player_animation/explode_effects.json create mode 100644 forge/src/main/resources/assets/bandits_quirk_lib/sounds/explosion_charge.ogg create mode 100644 forge/src/main/resources/assets/bandits_quirk_lib/textures/particle/charge_dust.png create mode 100644 forge/src/main/resources/assets/bandits_quirk_lib/textures/particle/particle.png create mode 100644 forge/src/main/resources/assets/bandits_quirk_lib/textures/particle/spark.png diff --git a/common/src/main/resources/assets/bql/palladium/model_layers/explosion.json b/common/src/main/resources/assets/bql/palladium/model_layers/explosion.json new file mode 100644 index 00000000..08a841e7 --- /dev/null +++ b/common/src/main/resources/assets/bql/palladium/model_layers/explosion.json @@ -0,0 +1,60 @@ +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.unknown", + "texture_width": 64, + "texture_height": 64, + "visible_bounds_width": 22, + "visible_bounds_height": 9, + "visible_bounds_offset": [0, 1.5, 0] + }, + "bones": [ + { + "name": "body", + "pivot": [0, 24, 0] + }, + { + "name": "explosion_cube", + "parent": "body", + "pivot": [0, 12.5, 0], + "cubes": [ + {"origin": [-3, 12.5, -3], "size": [6, 6, 6], "uv": [28, 8]} + ] + }, + { + "name": "explosion", + "parent": "body", + "pivot": [0, 15, 8], + "cubes": [ + {"origin": [-9.5, 15, -9], "size": [19, 1, 4], "uv": [0, 0]}, + {"origin": [-9.5, 15, -5], "size": [4, 1, 11], "uv": [34, 22]}, + {"origin": [5.5, 15, -5], "size": [4, 1, 11], "uv": [10, 31]}, + {"origin": [-9.5, 15, 6], "size": [19, 1, 4], "uv": [0, 48]} + ] + }, + { + "name": "head", + "pivot": [0, 24, 0] + }, + { + "name": "right_arm", + "pivot": [-5, 22, 0] + }, + { + "name": "left_arm", + "pivot": [5, 22, 0] + }, + { + "name": "right_leg", + "pivot": [-1.9, 12, 0] + }, + { + "name": "left_leg", + "pivot": [1.9, 12, 0] + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/assets/bql/palladium/render_layers/ender_gene_overlay.json b/common/src/main/resources/assets/bql/palladium/render_layers/ender_gene_overlay.json index 99d1a0d1..2c669f8c 100644 --- a/common/src/main/resources/assets/bql/palladium/render_layers/ender_gene_overlay.json +++ b/common/src/main/resources/assets/bql/palladium/render_layers/ender_gene_overlay.json @@ -18,4 +18,4 @@ }, "output": "bql:textures/models/ender_gene_output/base_#MASK.png" } -} \ No newline at end of file +} diff --git a/common/src/main/resources/assets/bql/palladium/render_layers/explosion_effects.json b/common/src/main/resources/assets/bql/palladium/render_layers/explosion_effects.json new file mode 100644 index 00000000..4df57ec2 --- /dev/null +++ b/common/src/main/resources/assets/bql/palladium/render_layers/explosion_effects.json @@ -0,0 +1,4 @@ +{ + "model_layer": "bql:explosion", + "texture": "bql:textures/models/explosion_effects.png" +} diff --git a/common/src/main/resources/assets/bql/palladium/render_layers/explosion_overlay.json b/common/src/main/resources/assets/bql/palladium/render_layers/explosion_overlay.json new file mode 100644 index 00000000..e0eded7a --- /dev/null +++ b/common/src/main/resources/assets/bql/palladium/render_layers/explosion_overlay.json @@ -0,0 +1,15 @@ +{ + "model_layer": "bql:ender_gene_geo", + "texture": { + "base": "bql:textures/models/explosion_overlay/texture#MASK.png", + "variables": { + "MASK": { + "type": "palladium:ability_integer_property", + "power": "bql:burst_gene", + "ability": "ExplosionProg", + "property": "value" + } + }, + "output": "bql:textures/models/explosion_overlay/texture#MASK.png" + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/bql/textures/models/explosion_effects.png b/common/src/main/resources/assets/bql/textures/models/explosion_effects.png new file mode 100644 index 0000000000000000000000000000000000000000..45676506df1da357c8c716db18a2d45ad440a943 GIT binary patch literal 571 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEVEo|e;uumf=gn-}pu+|Nt@G1D z0ty(aqKuf`Ii44|&sS1qFPPb6e86%ClkpP)nFAs#U2<}iZx<>>YDb@WXFmDljlyf+ zPQOb1n#{}nne}pBja>Zu59zfvYpZ^9B(0RzaB+Bii8a7cvU@_`s<}KKJrR-%g7#(3 zKKy-K)&5;~Cvt6E!=V4JeU(iR+YM=E;W?+e6xu|!!+uSlV}5YuIa#S>oiLUye^;mf zW?El9IS%W!6vm#lUz@yk?U})MDR{ z0h6P=7HYJJ~g-!lzK2B65gy$2_!ZVG+gAKKHtJh%Qjv(TZvLOFRG zB$s`9aVkuGmA0#vu#D~bH);{wk8XJ`)Jj|_z<&riOkqsl1s# zBXLzV*CR#`mieBy>ewVp_E(>~{>#@+lF@0F>xm^jCX5E|EDXgi3B6y`mo|!r6timfo{hx26L{7(8A5T-G@y GGywpLXFCM| literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture16.png b/common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture16.png new file mode 100644 index 0000000000000000000000000000000000000000..68ecf5fa8ba0962ca7ec2595213300ec8cf1b2e8 GIT binary patch literal 183 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Ii4<#Ar*7pTrgy0Fc4@lX!{<2 zGF(xBb7wC9M(1@5Pq-YYM%V$Z`~N@wk+Bhw!3MIInc>timfo{hx26L{7(8A5T-G@y GGywpLXFCM| literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture17.png b/common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture17.png new file mode 100644 index 0000000000000000000000000000000000000000..47bd94b385d9a5d5e17782168c0636bd68a9b7bd GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=xt=bLAr*7pTrgy0Fc4@lX#1|e zAZ@*qf>6bP0 Hl+XkKXy`i* literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture18.png b/common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture18.png new file mode 100644 index 0000000000000000000000000000000000000000..47bd94b385d9a5d5e17782168c0636bd68a9b7bd GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=xt=bLAr*7pTrgy0Fc4@lX#1|e zAZ@*qf>6bP0 Hl+XkKXy`i* literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture19.png b/common/src/main/resources/assets/bql/textures/models/explosion_overlay/texture19.png new file mode 100644 index 0000000000000000000000000000000000000000..bee1b623419af9581dd4055023500fc0e7c700fe GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGJMC{Gv1kcv5P&O7ol7;rFbwEd7j z^W=^RTllPY|JIvbKF7g#fm(p@3eGDmSxlylvxw(zB6y`mo|!r66666 CHARGED_PUNCH; public static final RegistrySupplier ADRENALINE; public static final RegistrySupplier ENDER_GENE_TP; - + public static final RegistrySupplier BURST_GENE; + public static final RegistrySupplier SUPER_BURST_CHARGE_GENE; + public static final RegistrySupplier SUPER_BURST_EXPLOSION_GENE; public AbilityRegister() { @@ -56,5 +58,8 @@ public static void init() { CHARGED_PUNCH = ABILITIES.register("charged_punch", ChargedPunchAbility::new); ADRENALINE = ABILITIES.register("adrenaline", AdrenalineAbility::new); ENDER_GENE_TP = ABILITIES.register("ender_gene_tp", EnderGeneTP::new); + BURST_GENE = ABILITIES.register("burst_gene", BurstGeneAbility::new); + SUPER_BURST_CHARGE_GENE = ABILITIES.register("super_burst_charge_gene", SuperBurstChargeGeneAbility::new); + SUPER_BURST_EXPLOSION_GENE = ABILITIES.register("super_burst_explosion_gene", SuperBurstExplosionGeneAbility::new); } } diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/BurstGeneAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/BurstGeneAbility.java new file mode 100644 index 00000000..42732d3d --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/BurstGeneAbility.java @@ -0,0 +1,216 @@ +package com.github.b4ndithelps.forge.abilities; + + +import com.github.b4ndithelps.forge.particle.ModParticles; +import com.github.b4ndithelps.forge.sounds.ModSounds; +import com.github.b4ndithelps.forge.systems.BodyStatusHelper; +import com.github.b4ndithelps.forge.systems.PowerStockHelper; +import com.github.b4ndithelps.forge.systems.QuirkFactorHelper; +import net.minecraft.ChatFormatting; +import net.minecraft.client.renderer.EffectInstance; +import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.DustParticleOptions; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.damagesource.DamageType; +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.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import net.threetag.palladium.entity.PalladiumAttributes; +import net.threetag.palladium.power.IPowerHolder; +import net.threetag.palladium.power.ability.Ability; +import net.threetag.palladium.power.ability.AbilityInstance; +import net.threetag.palladium.util.property.*; +import org.joml.Vector3f; + +import java.util.List; +import java.util.UUID; + +import static com.github.b4ndithelps.forge.systems.PowerStockHelper.sendPlayerPercentageMessage; +import static com.github.b4ndithelps.forge.utils.ActionBarHelper.sendPercentageDisplay; + +public class BurstGeneAbility extends Ability { + private static final float FACTOR_SCALING = 2.5F; + // Unique properties for tracking state + public static final PalladiumProperty CHARGE_TICKS; + + private static final UUID JUMP_MODIFIER_UUID = UUID.fromString("12345678-1234-1234-1234-123456789abc"); + + public BurstGeneAbility() { + super(); + } + + public void registerUniqueProperties(PropertyManager manager) { + manager.register(CHARGE_TICKS, 0); + } + +// @Override +// public void firstTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { +// if (enabled && entity instanceof ServerPlayer player) { +// // Initialize charge ( not needed ) +//// entry.setUniqueProperty(CHARGE_TICKS, 0); +//// BodyStatusHelper.setCustomFloat(player, "chest", "burst_gene_charge", 0.0f); +// +// if (entity.level() instanceof ServerLevel serverLevel) { +// // Play charging start sound +// serverLevel.playSound(null, player.blockPosition(), +// SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.PLAYERS, 0.3f, 1.2f); +// } +// } +// } + + @Override + public void tick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { + if (!(entity instanceof ServerPlayer player)) return; + if (!(entity.level() instanceof ServerLevel serverLevel)) return; + + float charge = BodyStatusHelper.getCustomFloat(player, "chest", "burst_charge"); + + if (charge > 1) { + if (charge == 25) { + + player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 25, 5, true, false)); + AttributeInstance attribute = player.getAttribute(PalladiumAttributes.JUMP_POWER.get()); + + attribute.removeModifier(JUMP_MODIFIER_UUID); + + if (attribute != null) { + AttributeModifier modifier = new AttributeModifier( + JUMP_MODIFIER_UUID, + "temporary_jump_debuff", + -9999, // +50% movement speed + AttributeModifier.Operation.ADDITION + ); + + attribute.addPermanentModifier(modifier); + } + // Play charging start sound + serverLevel.playSound(null, player.blockPosition(), + ModSounds.EXPLOSION_CHARGE.get(), SoundSource.PLAYERS, 1f, 1.0f); + } + Vec3 playerPos = player.position(); + BodyStatusHelper.setCustomFloat(player, "chest", "burst_charge", --charge); + // Example: 2 blocks away + Vec3 direction = playerPos.subtract(playerPos).normalize().scale(1); // Velocity towards player + + float r = 0.5f, g = 0.5f, b = 0.5f, scale = 0.4f; + + DustParticleOptions dust = new DustParticleOptions(new Vector3f(r, g, b), scale); + + // Noticeable upward velocity + double motionX = 0.0D; + double motionY = 0.5D; // make this higher for faster movement + double motionZ = 0.0D; + + // Use overload that accepts motion values + serverLevel.sendParticles( + ModParticles.RISING_DUST.get(), + playerPos.x, playerPos.y + 1, playerPos.z, + 2, // spawn multiple for visibility + 0.8D, 0.5D, 0.8D, // spread vertically + 0.0D // speed + ); + } else if (charge == 1) { + BodyStatusHelper.setCustomFloat(player, "chest", "burst_charge", --charge); + executeBurst(player, serverLevel, entry); + AttributeInstance attribute = player.getAttribute(PalladiumAttributes.JUMP_POWER.get()); + if (attribute != null) { + attribute.removeModifier(JUMP_MODIFIER_UUID); + } + } + + + + + } + +// @Override +// public void lastTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { +// if (entity instanceof ServerPlayer player && entity.level() instanceof ServerLevel serverLevel) { +// int chargeTicks = entry.getProperty(CHARGE_TICKS); +// +// // Prevent bug where it forces a smash on reload +// if (chargeTicks == 0) return; +// +// // Trigger arm swing animation +//// player.swing(InteractionHand.MAIN_HAND, true); +// +// // Execute the Detroit Smash +// executeBurst(player, serverLevel, entry, chargeTicks); +// +// // Reset charge +// entry.setUniqueProperty(CHARGE_TICKS, 0); +// BodyStatusHelper.setCustomFloat(player, "chest", "burst_gene_charge", 0.0f); +// } +// } + +// private void handleChargingPhase(ServerPlayer player, ServerLevel level, AbilityInstance entry) { +// int maxChargeTicks = MAX_CHARGE_TICKS; +// int currentChargeTicks = entry.getProperty(CHARGE_TICKS); +// +// // Increment charge if not at max +// if (currentChargeTicks < maxChargeTicks) { +// currentChargeTicks++; +// entry.setUniqueProperty(CHARGE_TICKS, currentChargeTicks); +// BodyStatusHelper.setCustomFloat(player, "chest", "burst_gene_charge", currentChargeTicks); +// } +// +// float chargeAmount = (float) (CHARGE_SCALING * currentChargeTicks * (QuirkFactorHelper.getQuirkFactor(player)+1) / 20); +// float currentHealth = player.getHealth(); +// +// var color = ChatFormatting.GREEN; +// +// if (chargeAmount * 4 >= currentHealth) { +// color = ChatFormatting.BLACK; +// } else if (chargeAmount * 4 >= currentHealth * 0.75f) { +// color = ChatFormatting.DARK_RED; +// } else if (chargeAmount * 4 >= currentHealth * 0.4f) { +// color = ChatFormatting.YELLOW; +// } +// +// sendPercentageDisplay(player, "Burst Gene Charge", (currentChargeTicks / (float)maxChargeTicks) * 100.0f, +// ChatFormatting.GRAY, color, currentChargeTicks == maxChargeTicks ? "MAX" : "Charging");; +// } + + private void executeBurst(ServerPlayer player, ServerLevel level, AbilityInstance entry) { + float factor = (float) (QuirkFactorHelper.getQuirkFactor(player)+1); + player.hurt(level.damageSources().generic(), factor*FACTOR_SCALING*3); + level.explode( + (Entity) player, // entity that caused it (null = no source) + player.getX(), // x + player.getY() + 1, // y + player.getZ(), // z + factor * FACTOR_SCALING * 1.5F, // explosion power (TNT = 4) + Level.ExplosionInteraction.MOB // what kind of explosion (controls block damage) + ); + float playersHealthResistance = (float) Math.max(1, (player.getMaxHealth() - 20) * 0.5); + BodyStatusHelper.damageAll(player, factor * FACTOR_SCALING * 15 / playersHealthResistance); + } + + + @Override + public String getDocumentationDescription() { + return "Burst ability"; + } + + static { + CHARGE_TICKS = (new IntegerProperty("charge_ticks")).sync(SyncType.NONE).disablePersistence(); + } +} diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/DetroitSmashAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/DetroitSmashAbility.java index 84c6c908..da9ef2a7 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/DetroitSmashAbility.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/DetroitSmashAbility.java @@ -37,532 +37,534 @@ import static com.github.b4ndithelps.forge.systems.PowerStockHelper.sendPlayerPercentageMessage; public class DetroitSmashAbility extends Ability { - // Configurable properties - public static final PalladiumProperty MAX_CHARGE_TICKS = new IntegerProperty("max_charge_ticks").configurable("Maximum charge time in ticks"); - public static final PalladiumProperty MAX_DISTANCE = new FloatProperty("max_distance").configurable("Maximum ray distance"); - public static final PalladiumProperty BASE_DAMAGE = new FloatProperty("base_damage").configurable("Base damage for the attack"); - public static final PalladiumProperty BASE_KNOCKBACK = new FloatProperty("base_knockback").configurable("Base knockback for the attack"); - public static final PalladiumProperty BASE_RADIUS = new FloatProperty("base_radius").configurable("Base explosion radius"); - public static final PalladiumProperty MAX_POWER = new FloatProperty("max_power").configurable("Power value considered 100% scaling (e.g., 500000)"); - public static final PalladiumProperty MAX_DAMAGE = new FloatProperty("max_damage").configurable("Maximum damage when power used is 100%"); - public static final PalladiumProperty MAX_KNOCKBACK = new FloatProperty("max_knockback").configurable("Maximum knockback when power used is 100%"); - public static final PalladiumProperty MAX_RADIUS = new FloatProperty("max_radius").configurable("Maximum AoE radius when power used is 100%"); - public static final PalladiumProperty EXPLOSION_POWER_MAX = new FloatProperty("explosion_power_max").configurable("Max explosion strength at full power"); - public static final PalladiumProperty EXPLOSION_SPACING_MIN = new FloatProperty("explosion_spacing_min").configurable("Minimum spacing between explosions at high power"); - public static final PalladiumProperty EXPLOSION_COUNT_MAX = new IntegerProperty("explosion_count_max").configurable("Maximum number of explosions along the path"); - - // Unique properties for tracking state - public static final PalladiumProperty CHARGE_TICKS; - - public DetroitSmashAbility() { - super(); - this.withProperty(MAX_CHARGE_TICKS, 100) - .withProperty(MAX_DISTANCE, 30.0f) - .withProperty(BASE_DAMAGE, 5.0f) - .withProperty(BASE_KNOCKBACK, 1.0f) - .withProperty(BASE_RADIUS, 2.0f) - // Linear scaling maxima for damage/knockback/radius and the power cap - .withProperty(MAX_POWER, 500000.0f) - .withProperty(MAX_DAMAGE, 100.0f) - .withProperty(MAX_KNOCKBACK, 5.0f) - .withProperty(MAX_RADIUS, 10.0f) - // Explosion tuning properties - .withProperty(EXPLOSION_POWER_MAX, 8.0f) - .withProperty(EXPLOSION_SPACING_MIN, 2.5f) - .withProperty(EXPLOSION_COUNT_MAX, 36); - } + // Configurable properties + public static final PalladiumProperty MAX_CHARGE_TICKS = new IntegerProperty("max_charge_ticks").configurable("Maximum charge time in ticks"); + public static final PalladiumProperty MAX_DISTANCE = new FloatProperty("max_distance").configurable("Maximum ray distance"); + public static final PalladiumProperty BASE_DAMAGE = new FloatProperty("base_damage").configurable("Base damage for the attack"); + public static final PalladiumProperty BASE_KNOCKBACK = new FloatProperty("base_knockback").configurable("Base knockback for the attack"); + public static final PalladiumProperty BASE_RADIUS = new FloatProperty("base_radius").configurable("Base explosion radius"); + public static final PalladiumProperty MAX_POWER = new FloatProperty("max_power").configurable("Power value considered 100% scaling (e.g., 500000)"); + public static final PalladiumProperty MAX_DAMAGE = new FloatProperty("max_damage").configurable("Maximum damage when power used is 100%"); + public static final PalladiumProperty MAX_KNOCKBACK = new FloatProperty("max_knockback").configurable("Maximum knockback when power used is 100%"); + public static final PalladiumProperty MAX_RADIUS = new FloatProperty("max_radius").configurable("Maximum AoE radius when power used is 100%"); + public static final PalladiumProperty EXPLOSION_POWER_MAX = new FloatProperty("explosion_power_max").configurable("Max explosion strength at full power"); + public static final PalladiumProperty EXPLOSION_SPACING_MIN = new FloatProperty("explosion_spacing_min").configurable("Minimum spacing between explosions at high power"); + public static final PalladiumProperty EXPLOSION_COUNT_MAX = new IntegerProperty("explosion_count_max").configurable("Maximum number of explosions along the path"); + + // Unique properties for tracking state + public static final PalladiumProperty CHARGE_TICKS; + + public DetroitSmashAbility() { + super(); + this.withProperty(MAX_CHARGE_TICKS, 100) + .withProperty(MAX_DISTANCE, 30.0f) + .withProperty(BASE_DAMAGE, 5.0f) + .withProperty(BASE_KNOCKBACK, 1.0f) + .withProperty(BASE_RADIUS, 2.0f) + // Linear scaling maxima for damage/knockback/radius and the power cap + .withProperty(MAX_POWER, 500000.0f) + .withProperty(MAX_DAMAGE, 100.0f) + .withProperty(MAX_KNOCKBACK, 5.0f) + .withProperty(MAX_RADIUS, 10.0f) + // Explosion tuning properties + .withProperty(EXPLOSION_POWER_MAX, 8.0f) + .withProperty(EXPLOSION_SPACING_MIN, 2.5f) + .withProperty(EXPLOSION_COUNT_MAX, 36); + } - public void registerUniqueProperties(PropertyManager manager) { - manager.register(CHARGE_TICKS, 0); - } + public void registerUniqueProperties(PropertyManager manager) { + manager.register(CHARGE_TICKS, 0); + } - @Override - public void firstTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { - if (enabled && entity instanceof ServerPlayer player) { - // Initialize charge - entry.setUniqueProperty(CHARGE_TICKS, 0); - BodyStatusHelper.setCustomFloat(player, "left_arm", "pstock_smash_charge", 0.0f); - - if (entity.level() instanceof ServerLevel serverLevel) { - // Play charging start sound - serverLevel.playSound(null, player.blockPosition(), - SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.3f, 1.2f); + @Override + public void firstTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { + if (enabled && entity instanceof ServerPlayer player) { + // Initialize charge + entry.setUniqueProperty(CHARGE_TICKS, 0); + BodyStatusHelper.setCustomFloat(player, "left_arm", "pstock_smash_charge", 0.0f); + + if (entity.level() instanceof ServerLevel serverLevel) { + // Play charging start sound + serverLevel.playSound(null, player.blockPosition(), + SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.3f, 1.2f); + } } } - } - @Override - public void tick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { - if (!(entity instanceof ServerPlayer player)) return; - if (!(entity.level() instanceof ServerLevel serverLevel)) return; + @Override + public void tick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { + if (!(entity instanceof ServerPlayer player)) return; + if (!(entity.level() instanceof ServerLevel serverLevel)) return; - if (enabled) { - handleChargingPhase(player, serverLevel, entry); + if (enabled) { + handleChargingPhase(player, serverLevel, entry); + } } - } - @Override - public void lastTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { - if (entity instanceof ServerPlayer player && entity.level() instanceof ServerLevel serverLevel) { - int chargeTicks = entry.getProperty(CHARGE_TICKS); - - // Prevent bug where it forces a smash on reload - if (chargeTicks == 0) return; - - // Trigger arm swing animation - player.swing(InteractionHand.MAIN_HAND, true); - - // Execute the Detroit Smash - executeDetroitSmash(player, serverLevel, entry, chargeTicks); - - // Reset charge - entry.setUniqueProperty(CHARGE_TICKS, 0); - BodyStatusHelper.setCustomFloat(player, "left_arm", "pstock_smash_charge", 0.0f); - } - } + @Override + public void lastTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { + if (entity instanceof ServerPlayer player && entity.level() instanceof ServerLevel serverLevel) { + int chargeTicks = entry.getProperty(CHARGE_TICKS); - private void handleChargingPhase(ServerPlayer player, ServerLevel level, AbilityInstance entry) { - int maxChargeTicks = entry.getProperty(MAX_CHARGE_TICKS); - int currentChargeTicks = entry.getProperty(CHARGE_TICKS); - - // Increment charge if not at max - if (currentChargeTicks < maxChargeTicks) { - currentChargeTicks++; - entry.setUniqueProperty(CHARGE_TICKS, currentChargeTicks); - BodyStatusHelper.setCustomFloat(player, "left_arm", "pstock_smash_charge", currentChargeTicks); - } - - // Calculate charge percentage - float chargePercent = Math.min((currentChargeTicks / (float)maxChargeTicks) * 100.0f, 100.0f); - float storedPower = PowerStockHelper.getStoredPower(player); - float powerUsed = storedPower * chargePercent / 100.0f; - - // Show charge message every 5 ticks - if (currentChargeTicks % 5 == 0) { - sendPlayerPercentageMessage(player, powerUsed, chargePercent, "Charging Smash"); - } + // Prevent bug where it forces a smash on reload + if (chargeTicks == 0) return; - // Charge-based particle effects along player's look direction - if (currentChargeTicks % 5 == 0) { - addChargingParticles(player, level, chargePercent); - } - - // Sound effects at certain charge levels - if (currentChargeTicks == 20) { - level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.3f, 1.2f); - } else if (currentChargeTicks == 50) { - level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.5f, 1.5f); - } else if (currentChargeTicks == 80) { - level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.7f, 1.8f); - } else if (currentChargeTicks == 99) { - level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 1.0f, 2.0f); + // Trigger arm swing animation + player.swing(InteractionHand.MAIN_HAND, true); + + // Execute the Detroit Smash + executeDetroitSmash(player, serverLevel, entry, chargeTicks); + + // Reset charge + entry.setUniqueProperty(CHARGE_TICKS, 0); + BodyStatusHelper.setCustomFloat(player, "left_arm", "pstock_smash_charge", 0.0f); + } } - } - private void addChargingParticles(ServerPlayer player, ServerLevel level, float chargePercent) { - Vec3 lookDirection = player.getLookAngle(); - float storedPower = PowerStockHelper.getStoredPower(player); - - // Scale particle effects based on both charge and stored power (for 500k max) - float powerFactor = storedPower > 0 ? Math.min(1.0f, (float)Math.sqrt(storedPower) / 707.0f) : 0.0f; - - // Stronger particle scaling for a flashy feel - int baseParticleCount = (int) Math.ceil(chargePercent / 6.0); - int powerParticleCount = (int) Math.ceil(powerFactor * powerFactor * 40); - int particleCount = Math.max(0, baseParticleCount + powerParticleCount); - - // Add minimal "dud" particles for very low power levels - if (particleCount == 0 && chargePercent > 30 && storedPower >= 0) { - particleCount = 1; // At least one tiny particle to show something happened + private void handleChargingPhase(ServerPlayer player, ServerLevel level, AbilityInstance entry) { + int maxChargeTicks = entry.getProperty(MAX_CHARGE_TICKS); + int currentChargeTicks = entry.getProperty(CHARGE_TICKS); + + // Increment charge if not at max + if (currentChargeTicks < maxChargeTicks) { + currentChargeTicks++; + entry.setUniqueProperty(CHARGE_TICKS, currentChargeTicks); + BodyStatusHelper.setCustomFloat(player, "left_arm", "pstock_smash_charge", currentChargeTicks); + } + + // Calculate charge percentage + float chargePercent = Math.min((currentChargeTicks / (float) maxChargeTicks) * 100.0f, 100.0f); + float storedPower = PowerStockHelper.getStoredPower(player); + float powerUsed = storedPower * chargePercent / 100.0f; + + // Show charge message every 5 ticks + if (currentChargeTicks % 5 == 0) { + sendPlayerPercentageMessage(player, powerUsed, chargePercent, "Charging Smash"); + } + + // Charge-based particle effects along player's look direction + if (currentChargeTicks % 5 == 0) { + addChargingParticles(player, level, chargePercent); + } + + // Sound effects at certain charge levels + if (currentChargeTicks == 20) { + level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.3f, 1.2f); + } else if (currentChargeTicks == 50) { + level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.5f, 1.5f); + } else if (currentChargeTicks == 80) { + level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.7f, 1.8f); + } else if (currentChargeTicks == 99) { + level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 1.0f, 2.0f); + } } - - // Preview distance also scales with power - double baseDistance = 1 + (chargePercent / 20.0); - double previewDistance = Math.min(20, baseDistance * (0.2f + powerFactor * 1.2f)); - - // Show charging preview along look direction - for (double i = 1; i < previewDistance; i += 0.8) { - double px = player.getX() + (i * lookDirection.x); - double py = player.getY() + (i * lookDirection.y) + player.getEyeHeight(); - double pz = player.getZ() + (i * lookDirection.z); - - // Only show particles if we actually have particles to show - if (particleCount > 0) { - // Scale particle intensity based on power - int smokeParticles = Math.max(0, particleCount/2); - int critParticles = Math.max(0, particleCount/3); - - if (chargePercent < 30) { - if (smokeParticles > 0) { - // For very low power, show tiny "dud" particles - if (powerFactor < 0.1f) { - level.sendParticles(ParticleTypes.SMOKE, px, py, pz, 1, 0.02, 0.02, 0.02, 0.001); - } else { - level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.08, 0.08, 0.08, 0.01); + + private void addChargingParticles(ServerPlayer player, ServerLevel level, float chargePercent) { + Vec3 lookDirection = player.getLookAngle(); + float storedPower = PowerStockHelper.getStoredPower(player); + + // Scale particle effects based on both charge and stored power (for 500k max) + float powerFactor = storedPower > 0 ? Math.min(1.0f, (float) Math.sqrt(storedPower) / 707.0f) : 0.0f; + + // Stronger particle scaling for a flashy feel + int baseParticleCount = (int) Math.ceil(chargePercent / 6.0); + int powerParticleCount = (int) Math.ceil(powerFactor * powerFactor * 40); + int particleCount = Math.max(0, baseParticleCount + powerParticleCount); + + // Add minimal "dud" particles for very low power levels + if (particleCount == 0 && chargePercent > 30 && storedPower >= 0) { + particleCount = 1; // At least one tiny particle to show something happened + } + + // Preview distance also scales with power + double baseDistance = 1 + (chargePercent / 20.0); + double previewDistance = Math.min(20, baseDistance * (0.2f + powerFactor * 1.2f)); + + // Show charging preview along look direction + for (double i = 1; i < previewDistance; i += 0.8) { + double px = player.getX() + (i * lookDirection.x); + double py = player.getY() + (i * lookDirection.y) + player.getEyeHeight(); + double pz = player.getZ() + (i * lookDirection.z); + + // Only show particles if we actually have particles to show + if (particleCount > 0) { + // Scale particle intensity based on power + int smokeParticles = Math.max(0, particleCount / 2); + int critParticles = Math.max(0, particleCount / 3); + + if (chargePercent < 30) { + if (smokeParticles > 0) { + // For very low power, show tiny "dud" particles + if (powerFactor < 0.1f) { + level.sendParticles(ParticleTypes.SMOKE, px, py, pz, 1, 0.02, 0.02, 0.02, 0.001); + } else { + level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.08, 0.08, 0.08, 0.01); + } + } + } else if (chargePercent < 60) { + if (smokeParticles > 0) { + level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.15, 0.08, 0.15, 0.02); + } + if (critParticles > 0 && powerFactor > 0.15f) { + level.sendParticles(ParticleTypes.CRIT, px, py, pz, critParticles, 0.08, 0.08, 0.08, 0.008); + } + } else if (chargePercent < 80) { + if (smokeParticles > 0) { + level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.2, 0.15, 0.2, 0.03); + } + if (critParticles > 0 && powerFactor > 0.15f) { + level.sendParticles(ParticleTypes.CRIT, px, py, pz, critParticles, 0.12, 0.08, 0.12, 0.012); + } + // Only show explosion particles if we have significant power + if ((int) chargePercent % 10 == 0 && powerFactor > 0.35f) { + level.sendParticles(ParticleTypes.EXPLOSION, px, py, pz, 2, 0.08, 0.08, 0.08, 0.01); + } + } else { + if (smokeParticles > 0) { + level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.25, 0.2, 0.25, 0.04); + } + if (critParticles > 0 && powerFactor > 0.15f) { + level.sendParticles(ParticleTypes.CRIT, px, py, pz, critParticles, 0.18, 0.12, 0.18, 0.02); + } + // Enhanced effects only for higher power levels + if ((int) chargePercent % 10 == 0 && powerFactor > 0.45f) { + level.sendParticles(ParticleTypes.EXPLOSION, px, py, pz, 2, 0.12, 0.08, 0.12, 0.02); + if (powerFactor > 0.7f) + level.sendParticles(ParticleTypes.LARGE_SMOKE, px, py, pz, 3, 0.08, 0.08, 0.08, 0.008); } - } - } else if (chargePercent < 60) { - if (smokeParticles > 0) { - level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.15, 0.08, 0.15, 0.02); - } - if (critParticles > 0 && powerFactor > 0.15f) { - level.sendParticles(ParticleTypes.CRIT, px, py, pz, critParticles, 0.08, 0.08, 0.08, 0.008); - } - } else if (chargePercent < 80) { - if (smokeParticles > 0) { - level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.2, 0.15, 0.2, 0.03); - } - if (critParticles > 0 && powerFactor > 0.15f) { - level.sendParticles(ParticleTypes.CRIT, px, py, pz, critParticles, 0.12, 0.08, 0.12, 0.012); - } - // Only show explosion particles if we have significant power - if ((int)chargePercent % 10 == 0 && powerFactor > 0.35f) { - level.sendParticles(ParticleTypes.EXPLOSION, px, py, pz, 2, 0.08, 0.08, 0.08, 0.01); - } - } else { - if (smokeParticles > 0) { - level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.25, 0.2, 0.25, 0.04); - } - if (critParticles > 0 && powerFactor > 0.15f) { - level.sendParticles(ParticleTypes.CRIT, px, py, pz, critParticles, 0.18, 0.12, 0.18, 0.02); - } - // Enhanced effects only for higher power levels - if ((int)chargePercent % 10 == 0 && powerFactor > 0.45f) { - level.sendParticles(ParticleTypes.EXPLOSION, px, py, pz, 2, 0.12, 0.08, 0.12, 0.02); - if (powerFactor > 0.7f) level.sendParticles(ParticleTypes.LARGE_SMOKE, px, py, pz, 3, 0.08, 0.08, 0.08, 0.008); } } } } - } - private void executeDetroitSmash(ServerPlayer player, ServerLevel level, AbilityInstance entry, int chargeTicks) { - int maxChargeTicks = entry.getProperty(MAX_CHARGE_TICKS); - float maxDistance = entry.getProperty(MAX_DISTANCE); - float baseDamage = entry.getProperty(BASE_DAMAGE); - float baseKnockback = entry.getProperty(BASE_KNOCKBACK); - float baseRadius = entry.getProperty(BASE_RADIUS); - float maxPower = Math.max(1.0f, entry.getProperty(MAX_POWER)); - float maxDamage = Math.max(baseDamage, entry.getProperty(MAX_DAMAGE)); - float maxKnockback = Math.max(baseKnockback, entry.getProperty(MAX_KNOCKBACK)); - float maxRadius = Math.max(baseRadius, entry.getProperty(MAX_RADIUS)); - - float chargePercent = Math.min((chargeTicks / (float)maxChargeTicks) * 100.0f, 100.0f); - float storedPower = PowerStockHelper.getStoredPower(player); - float powerUsed = storedPower * chargePercent / 100.0f; - - // Linear power ratio based on configured max power - float powerRatio = Math.max(0.0f, Math.min(1.0f, powerUsed / maxPower)); - - // Final values map linearly to configured maxima using the power ratio - float finalDamage = Math.max(0.0f, maxDamage * powerRatio); - float finalKnockback = Math.max(0.0f, maxKnockback * powerRatio); - float finalRadius = Math.max(0.0f, maxRadius * powerRatio); - float finalDistance = Math.max(1.0f, entry.getProperty(MAX_DISTANCE) * powerRatio); - - // Execute the ray smash - sounds and initial particles (scale with power) - // Only play sounds if we have decent power, start very quiet - if (powerRatio > 0.1f) { - float soundVolume = Math.max(0.1f, Math.min(3.0f, powerRatio * 3.0f)); - level.playSound(null, player.blockPosition(), SoundEvents.GENERIC_EXPLODE, SoundSource.PLAYERS, soundVolume, 0.6f); - - if (powerRatio > 0.4f) { - level.playSound(null, player.blockPosition(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.PLAYERS, soundVolume * 0.8f, 1.0f); + private void executeDetroitSmash(ServerPlayer player, ServerLevel level, AbilityInstance entry, int chargeTicks) { + int maxChargeTicks = entry.getProperty(MAX_CHARGE_TICKS); + float maxDistance = entry.getProperty(MAX_DISTANCE); + float baseDamage = entry.getProperty(BASE_DAMAGE); + float baseKnockback = entry.getProperty(BASE_KNOCKBACK); + float baseRadius = entry.getProperty(BASE_RADIUS); + float maxPower = Math.max(1.0f, entry.getProperty(MAX_POWER)); + float maxDamage = Math.max(baseDamage, entry.getProperty(MAX_DAMAGE)); + float maxKnockback = Math.max(baseKnockback, entry.getProperty(MAX_KNOCKBACK)); + float maxRadius = Math.max(baseRadius, entry.getProperty(MAX_RADIUS)); + + float chargePercent = Math.min((chargeTicks / (float) maxChargeTicks) * 100.0f, 100.0f); + float storedPower = PowerStockHelper.getStoredPower(player); + float powerUsed = storedPower * chargePercent / 100.0f; + + // Linear power ratio based on configured max power + float powerRatio = Math.max(0.0f, Math.min(1.0f, powerUsed / maxPower)); + + // Final values map linearly to configured maxima using the power ratio + float finalDamage = Math.max(0.0f, maxDamage * powerRatio); + float finalKnockback = Math.max(0.0f, maxKnockback * powerRatio); + float finalRadius = Math.max(0.0f, maxRadius * powerRatio); + float finalDistance = Math.max(1.0f, entry.getProperty(MAX_DISTANCE) * powerRatio); + + // Execute the ray smash - sounds and initial particles (scale with power) + // Only play sounds if we have decent power, start very quiet + if (powerRatio > 0.1f) { + float soundVolume = Math.max(0.1f, Math.min(3.0f, powerRatio * 3.0f)); + level.playSound(null, player.blockPosition(), SoundEvents.GENERIC_EXPLODE, SoundSource.PLAYERS, soundVolume, 0.6f); + + if (powerRatio > 0.4f) { + level.playSound(null, player.blockPosition(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.PLAYERS, soundVolume * 0.8f, 1.0f); + } + if (powerRatio > 0.6f) { + level.playSound(null, player.blockPosition(), SoundEvents.FIREWORK_ROCKET_BLAST, SoundSource.PLAYERS, soundVolume * 0.6f, 0.8f); + } + } + + // Ray-based targeting system + Vec3 eyePosition = player.getEyePosition(); + Vec3 lookDirection = player.getLookAngle(); + Vec3 endPosition = eyePosition.add(lookDirection.scale(finalDistance)); + + // Perform ray trace + BlockHitResult hitResult = level.clip(new ClipContext(eyePosition, endPosition, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, player)); + + Vec3 impactPoint = hitResult.getType() == HitResult.Type.BLOCK ? hitResult.getLocation() : endPosition; + double actualDistance = eyePosition.distanceTo(impactPoint); + + // Give the user temporary resistance to prevent self-death from explosions + player.addEffect(new MobEffectInstance(MobEffects.DAMAGE_RESISTANCE, 200, 4, true, false)); + + // Spawn immediate swing visuals near the player + Vec3 handPos = eyePosition.add(lookDirection.scale(1.5)); + level.sendParticles(ParticleTypes.SWEEP_ATTACK, handPos.x, handPos.y - 0.2, handPos.z, 6, 0.2, 0.2, 0.2, 0.0); + level.sendParticles(ParticleTypes.CRIT, handPos.x, handPos.y, handPos.z, 12, 0.25, 0.25, 0.25, 0.05); + level.sendParticles(ParticleTypes.CLOUD, handPos.x, handPos.y, handPos.z, 8, 0.25, 0.25, 0.25, 0.05); + + // Direct entity hit check along the ray for a satisfying punch + LivingEntity directTarget = findTargetEntity(player, (float) finalDistance); + if (directTarget != null && directTarget != player && directTarget.isAlive()) { + impactPoint = directTarget.position(); + // Perform vanilla attack for animations/damage hooks + player.attack(directTarget); + // Add heavy extra damage scaling with power + float bonus = finalDamage * 0.75f; + directTarget.hurt(player.damageSources().playerAttack(player), bonus); + // Strong knockback on direct hit + Vec3 kbDir = directTarget.position().subtract(player.position()).normalize().add(0, 0.2, 0); + directTarget.setDeltaMovement(directTarget.getDeltaMovement().add(kbDir.scale(finalKnockback * 0.6))); + if (directTarget instanceof ServerPlayer sp) { + sp.connection.send(new ClientboundSetEntityMotionPacket(sp)); + } + } + + // Show particle trail along the ray path + addParticleTrail(level, eyePosition, impactPoint, chargePercent); + + // Create TNT explosions for mid to high power levels + boolean shouldCreateExplosions = storedPower >= 50000 && chargePercent >= 50 && powerRatio > 0.3f; + if (shouldCreateExplosions) { + float configuredMaxPower = Math.max(1.0f, entry.getProperty(MAX_POWER)); + float explosionPowerMax = Math.max(1.0f, entry.getProperty(EXPLOSION_POWER_MAX)); + float explosionSpacingMin = Math.max(0.5f, entry.getProperty(EXPLOSION_SPACING_MIN)); + int explosionCountMax = Math.max(1, entry.getProperty(EXPLOSION_COUNT_MAX)); + createExplosionChain(level, player, eyePosition, impactPoint, finalRadius, powerUsed, chargePercent, + configuredMaxPower, explosionPowerMax, explosionSpacingMin, explosionCountMax); + } + + // Enhanced visual effects at impact point + addImpactEffects(level, impactPoint, finalRadius, chargePercent, powerRatio); + + // Area of Effect Damage and Knockback + applyAreaDamage(level, player, impactPoint, finalRadius, finalDamage, finalKnockback); + + // Apply overuse damage to arms + PowerStockHelper.applyLimbDamage(player, powerUsed, "arm"); + + // Show completion message + player.sendSystemMessage(Component.literal(String.format("Detroit Smash executed! %.0f damage, %.0f radius, %.0f range", + finalDamage, finalRadius, actualDistance)).withStyle(ChatFormatting.GREEN)); + } + + private void addParticleTrail(ServerLevel level, Vec3 startPos, Vec3 endPos, float chargePercent) { + Vec3 direction = endPos.subtract(startPos).normalize(); + double distance = startPos.distanceTo(endPos); + + // Get power scaling for trail effects - denser at shorter ranges + float powerFactor = Math.max(0.0f, Math.min(1.0f, (float) (distance - 1.0) / 35.0f)); + + // Stronger trail particles + int trailParticleCount = Math.max(0, Math.round((chargePercent / 15.0f) * (powerFactor * powerFactor * 2.0f + 0.5f))); + double stepSize = Math.max(0.35, 1.2 - powerFactor * 1.0); + + // Add minimal "dud" trail particles for very low power but decent charge + if (trailParticleCount == 0 && chargePercent > 60 && distance > 3) { + trailParticleCount = 1; // At least show something traveled } - if (powerRatio > 0.6f) { - level.playSound(null, player.blockPosition(), SoundEvents.FIREWORK_ROCKET_BLAST, SoundSource.PLAYERS, soundVolume * 0.6f, 0.8f); + + for (double i = 1; i < distance; i += stepSize) { + Vec3 particlePos = startPos.add(direction.scale(i)); + + if (trailParticleCount > 0) { + // Scale particle spread and intensity with power + double spread = Math.max(0.04, 0.25 * powerFactor); + double speed = Math.max(0.01, 0.12 * powerFactor); + + // For very low power (dud particles), use minimal effects + if (powerFactor < 0.1f) { + level.sendParticles(ParticleTypes.SMOKE, particlePos.x, particlePos.y, particlePos.z, + 1, 0.03, 0.03, 0.03, 0.006); // Tiny puff + } else { + level.sendParticles(ParticleTypes.CRIT, particlePos.x, particlePos.y, particlePos.z, + trailParticleCount, spread, spread, spread, speed); + level.sendParticles(ParticleTypes.SMOKE, particlePos.x, particlePos.y, particlePos.z, + Math.max(1, trailParticleCount * 2), spread * 0.9, spread * 0.9, spread * 0.9, speed * 0.6); + + // Only add explosion particles if we have significant power + if (powerFactor > 0.45f && trailParticleCount > 1) { + level.sendParticles(ParticleTypes.EXPLOSION, particlePos.x, particlePos.y, particlePos.z, + Math.max(0, trailParticleCount / 2), spread * 0.6, spread * 0.6, spread * 0.6, speed * 0.35); + } + } + } } } - // Ray-based targeting system - Vec3 eyePosition = player.getEyePosition(); - Vec3 lookDirection = player.getLookAngle(); - Vec3 endPosition = eyePosition.add(lookDirection.scale(finalDistance)); - - // Perform ray trace - BlockHitResult hitResult = level.clip(new ClipContext(eyePosition, endPosition, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, player)); - - Vec3 impactPoint = hitResult.getType() == HitResult.Type.BLOCK ? hitResult.getLocation() : endPosition; - double actualDistance = eyePosition.distanceTo(impactPoint); - - // Give the user temporary resistance to prevent self-death from explosions - player.addEffect(new MobEffectInstance(MobEffects.DAMAGE_RESISTANCE, 200, 4, true, false)); - - // Spawn immediate swing visuals near the player - Vec3 handPos = eyePosition.add(lookDirection.scale(1.5)); - level.sendParticles(ParticleTypes.SWEEP_ATTACK, handPos.x, handPos.y - 0.2, handPos.z, 6, 0.2, 0.2, 0.2, 0.0); - level.sendParticles(ParticleTypes.CRIT, handPos.x, handPos.y, handPos.z, 12, 0.25, 0.25, 0.25, 0.05); - level.sendParticles(ParticleTypes.CLOUD, handPos.x, handPos.y, handPos.z, 8, 0.25, 0.25, 0.25, 0.05); - - // Direct entity hit check along the ray for a satisfying punch - LivingEntity directTarget = findTargetEntity(player, (float)finalDistance); - if (directTarget != null && directTarget != player && directTarget.isAlive()) { - impactPoint = directTarget.position(); - // Perform vanilla attack for animations/damage hooks - player.attack(directTarget); - // Add heavy extra damage scaling with power - float bonus = finalDamage * 0.75f; - directTarget.hurt(player.damageSources().playerAttack(player), bonus); - // Strong knockback on direct hit - Vec3 kbDir = directTarget.position().subtract(player.position()).normalize().add(0, 0.2, 0); - directTarget.setDeltaMovement(directTarget.getDeltaMovement().add(kbDir.scale(finalKnockback * 0.6))); - if (directTarget instanceof ServerPlayer sp) { - sp.connection.send(new ClientboundSetEntityMotionPacket(sp)); + private void createExplosionChain(ServerLevel level, ServerPlayer player, Vec3 startPos, Vec3 endPos, + float finalRadius, float powerUsed, float chargePercent, + float configuredMaxPower, float explosionPowerMax, float explosionSpacingMin, int explosionCountMax) { + Vec3 direction = endPos.subtract(startPos).normalize(); + double distance = startPos.distanceTo(endPos); + + // Scale explosions using the same linear power ratio configuration + float powerRatio = Math.max(0.0f, Math.min(1.0f, powerUsed / configuredMaxPower)); + float explosionPower = Math.min(explosionPowerMax, 1.5f + (finalRadius * 0.4f * powerRatio)); + float explosionSpacing = Math.max(explosionSpacingMin, 10.0f - (chargePercent * 0.08f) - (powerRatio * 4.0f)); + int maxExplosions = Math.min(explosionCountMax, Math.round(10 + powerRatio * (explosionCountMax - 10))); + int numExplosions = Math.min(maxExplosions, (int) Math.ceil(distance / explosionSpacing)); + + // Create explosions along the ray path + for (int i = 0; i <= numExplosions; i++) { + double explosionDistance = Math.min(i * explosionSpacing, distance); + Vec3 explosionPos = startPos.add(direction.scale(explosionDistance)); + + // Vary explosion power slightly along the path (stronger in middle) + double distanceRatio = explosionDistance / distance; + double powerMultiplier = 0.6 + (0.4 * Math.sin(distanceRatio * Math.PI)); + float currentExplosionPower = Math.max(0.5f, explosionPower * (float) powerMultiplier); + + // Create explosion that damages entities + level.explode(player, explosionPos.x, explosionPos.y, explosionPos.z, + currentExplosionPower, Level.ExplosionInteraction.BLOCK); } + + player.sendSystemMessage(Component.literal(String.format("High-power Detroit Smash with %d explosions!", numExplosions)) + .withStyle(ChatFormatting.RED)); } - // Show particle trail along the ray path - addParticleTrail(level, eyePosition, impactPoint, chargePercent); - - // Create TNT explosions for mid to high power levels - boolean shouldCreateExplosions = storedPower >= 50000 && chargePercent >= 50 && powerRatio > 0.3f; - if (shouldCreateExplosions) { - float configuredMaxPower = Math.max(1.0f, entry.getProperty(MAX_POWER)); - float explosionPowerMax = Math.max(1.0f, entry.getProperty(EXPLOSION_POWER_MAX)); - float explosionSpacingMin = Math.max(0.5f, entry.getProperty(EXPLOSION_SPACING_MIN)); - int explosionCountMax = Math.max(1, entry.getProperty(EXPLOSION_COUNT_MAX)); - createExplosionChain(level, player, eyePosition, impactPoint, finalRadius, powerUsed, chargePercent, - configuredMaxPower, explosionPowerMax, explosionSpacingMin, explosionCountMax); + // Simple entity raycast to find the closest living entity along the player's look within range + private LivingEntity findTargetEntity(ServerPlayer player, float range) { + Vec3 eyePos = player.getEyePosition(); + Vec3 lookVec = player.getLookAngle(); + Vec3 endPos = eyePos.add(lookVec.scale(range)); + + LivingEntity best = null; + double closest = range; + + List entities = player.level().getEntitiesOfClass(LivingEntity.class, + new AABB(eyePos, endPos).inflate(1.0), + e -> e != player && e.isAlive()); + + for (LivingEntity e : entities) { + if (e.getBoundingBox().inflate(0.3).clip(eyePos, endPos).isPresent()) { + double dist = eyePos.distanceTo(e.position()); + if (dist < closest) { + closest = dist; + best = e; + } + } + } + + return best; } - // Enhanced visual effects at impact point - addImpactEffects(level, impactPoint, finalRadius, chargePercent, powerRatio); + private void addImpactEffects(ServerLevel level, Vec3 impactPoint, float finalRadius, float chargePercent, float powerScalingFactor) { + // Scale impact effects based on power and charge + float effectIntensity = Math.max(0.0f, powerScalingFactor * powerScalingFactor * (chargePercent / 90.0f)); - // Area of Effect Damage and Knockback - applyAreaDamage(level, player, impactPoint, finalRadius, finalDamage, finalKnockback); + // Only create major effects if we have very significant power + if (powerScalingFactor > 0.6f) { + level.sendParticles(ParticleTypes.EXPLOSION_EMITTER, + impactPoint.x, impactPoint.y, impactPoint.z, 1, 0, 0, 0, 0); + } - // Apply overuse damage to arms - PowerStockHelper.applyLimbDamage(player, powerUsed, "arm"); + // Stronger particle counts + int critCount = Math.max(0, Math.round((10 + chargePercent) * effectIntensity)); + int smokeCount = Math.max(0, Math.round((18 + chargePercent) * effectIntensity)); + int explosionCount = Math.max(0, Math.round((4 + chargePercent / 5) * effectIntensity)); - // Show completion message - player.sendSystemMessage(Component.literal(String.format("Detroit Smash executed! %.0f damage, %.0f radius, %.0f range", - finalDamage, finalRadius, actualDistance)).withStyle(ChatFormatting.GREEN)); - } + // Add minimal "dud" impact particles for very low power levels + if (critCount == 0 && smokeCount == 0 && chargePercent > 20) { + smokeCount = 1; // At least a tiny puff of smoke for the impact + if (chargePercent > 50) { + critCount = 1; // Maybe one crit particle if they charged a bit + } + } - private void addParticleTrail(ServerLevel level, Vec3 startPos, Vec3 endPos, float chargePercent) { - Vec3 direction = endPos.subtract(startPos).normalize(); - double distance = startPos.distanceTo(endPos); - - // Get power scaling for trail effects - denser at shorter ranges - float powerFactor = Math.max(0.0f, Math.min(1.0f, (float)(distance - 1.0) / 35.0f)); - - // Stronger trail particles - int trailParticleCount = Math.max(0, Math.round((chargePercent / 15.0f) * (powerFactor * powerFactor * 2.0f + 0.5f))); - double stepSize = Math.max(0.35, 1.2 - powerFactor * 1.0); - - // Add minimal "dud" trail particles for very low power but decent charge - if (trailParticleCount == 0 && chargePercent > 60 && distance > 3) { - trailParticleCount = 1; // At least show something traveled - } - - for (double i = 1; i < distance; i += stepSize) { - Vec3 particlePos = startPos.add(direction.scale(i)); - - if (trailParticleCount > 0) { - // Scale particle spread and intensity with power - double spread = Math.max(0.04, 0.25 * powerFactor); - double speed = Math.max(0.01, 0.12 * powerFactor); - - // For very low power (dud particles), use minimal effects - if (powerFactor < 0.1f) { - level.sendParticles(ParticleTypes.SMOKE, particlePos.x, particlePos.y, particlePos.z, - 1, 0.03, 0.03, 0.03, 0.006); // Tiny puff - } else { - level.sendParticles(ParticleTypes.CRIT, particlePos.x, particlePos.y, particlePos.z, - trailParticleCount, spread, spread, spread, speed); - level.sendParticles(ParticleTypes.SMOKE, particlePos.x, particlePos.y, particlePos.z, - Math.max(1, trailParticleCount * 2), spread * 0.9, spread * 0.9, spread * 0.9, speed * 0.6); - - // Only add explosion particles if we have significant power - if (powerFactor > 0.45f && trailParticleCount > 1) { - level.sendParticles(ParticleTypes.EXPLOSION, particlePos.x, particlePos.y, particlePos.z, - Math.max(0, trailParticleCount/2), spread * 0.6, spread * 0.6, spread * 0.6, speed * 0.35); - } - } - } - } - } + // Send particles - even minimal ones for dud effects + if (critCount > 0) { + // For dud effects, use very small radius and speed + float effectiveRadius = powerScalingFactor < 0.1f ? 0.1f : finalRadius; + level.sendParticles(ParticleTypes.CRIT, + impactPoint.x, impactPoint.y, impactPoint.z, + critCount, effectiveRadius, effectiveRadius, effectiveRadius, Math.max(0.005, 0.15 * powerScalingFactor)); + } + if (smokeCount > 0) { + // For dud effects, use very small radius and speed + float effectiveRadius = powerScalingFactor < 0.1f ? 0.15f : finalRadius * 1.5f; + level.sendParticles(ParticleTypes.SMOKE, + impactPoint.x, impactPoint.y, impactPoint.z, + smokeCount, effectiveRadius, effectiveRadius, effectiveRadius, Math.max(0.01, 0.2 * powerScalingFactor)); + } + if (explosionCount > 0) { + level.sendParticles(ParticleTypes.EXPLOSION, + impactPoint.x, impactPoint.y, impactPoint.z, + explosionCount, finalRadius * 0.8f, finalRadius * 0.8f, finalRadius * 0.8f, Math.max(0.005, 0.1 * powerScalingFactor)); + } - private void createExplosionChain(ServerLevel level, ServerPlayer player, Vec3 startPos, Vec3 endPos, - float finalRadius, float powerUsed, float chargePercent, - float configuredMaxPower, float explosionPowerMax, float explosionSpacingMin, int explosionCountMax) { - Vec3 direction = endPos.subtract(startPos).normalize(); - double distance = startPos.distanceTo(endPos); - - // Scale explosions using the same linear power ratio configuration - float powerRatio = Math.max(0.0f, Math.min(1.0f, powerUsed / configuredMaxPower)); - float explosionPower = Math.min(explosionPowerMax, 1.5f + (finalRadius * 0.4f * powerRatio)); - float explosionSpacing = Math.max(explosionSpacingMin, 10.0f - (chargePercent * 0.08f) - (powerRatio * 4.0f)); - int maxExplosions = Math.min(explosionCountMax, Math.round(10 + powerRatio * (explosionCountMax - 10))); - int numExplosions = Math.min(maxExplosions, (int)Math.ceil(distance / explosionSpacing)); - - // Create explosions along the ray path - for (int i = 0; i <= numExplosions; i++) { - double explosionDistance = Math.min(i * explosionSpacing, distance); - Vec3 explosionPos = startPos.add(direction.scale(explosionDistance)); - - // Vary explosion power slightly along the path (stronger in middle) - double distanceRatio = explosionDistance / distance; - double powerMultiplier = 0.6 + (0.4 * Math.sin(distanceRatio * Math.PI)); - float currentExplosionPower = Math.max(0.5f, explosionPower * (float)powerMultiplier); - - // Create explosion that damages entities - level.explode(player, explosionPos.x, explosionPos.y, explosionPos.z, - currentExplosionPower, Level.ExplosionInteraction.BLOCK); - } + // Shockwave ring around impact for flair + if (powerScalingFactor > 0.3f) { + int rings = Math.max(1, (int) (powerScalingFactor * 3)); + for (int r = 1; r <= rings; r++) { + double ringRadius = finalRadius * (0.5 + 0.3 * r); + for (int a = 0; a < 24; a++) { + double theta = (Math.PI * 2 * a) / 24.0; + double rx = impactPoint.x + ringRadius * Math.cos(theta); + double rz = impactPoint.z + ringRadius * Math.sin(theta); + level.sendParticles(ParticleTypes.CLOUD, rx, impactPoint.y + 0.2, rz, 1, 0.02, 0.02, 0.02, 0.02); + } + } + } - player.sendSystemMessage(Component.literal(String.format("High-power Detroit Smash with %d explosions!", numExplosions)) - .withStyle(ChatFormatting.RED)); - } + // Scale sound effects with power - much quieter at low levels + if (powerScalingFactor > 0.1f) { + float impactSoundVolume = Math.max(0.1f, Math.min(4.0f, powerScalingFactor * 4.0f)); + level.playSound(null, BlockPos.containing(impactPoint), SoundEvents.GENERIC_EXPLODE, SoundSource.PLAYERS, impactSoundVolume, 0.4f); - // Simple entity raycast to find the closest living entity along the player's look within range - private LivingEntity findTargetEntity(ServerPlayer player, float range) { - Vec3 eyePos = player.getEyePosition(); - Vec3 lookVec = player.getLookAngle(); - Vec3 endPos = eyePos.add(lookVec.scale(range)); - - LivingEntity best = null; - double closest = range; - - List entities = player.level().getEntitiesOfClass(LivingEntity.class, - new AABB(eyePos, endPos).inflate(1.0), - e -> e != player && e.isAlive()); - - for (LivingEntity e : entities) { - if (e.getBoundingBox().inflate(0.3).clip(eyePos, endPos).isPresent()) { - double dist = eyePos.distanceTo(e.position()); - if (dist < closest) { - closest = dist; - best = e; + if (powerScalingFactor > 0.5f) { + level.playSound(null, BlockPos.containing(impactPoint), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.PLAYERS, impactSoundVolume * 0.7f, 0.8f); } } } - return best; - } - - private void addImpactEffects(ServerLevel level, Vec3 impactPoint, float finalRadius, float chargePercent, float powerScalingFactor) { - // Scale impact effects based on power and charge - float effectIntensity = Math.max(0.0f, powerScalingFactor * powerScalingFactor * (chargePercent / 90.0f)); - - // Only create major effects if we have very significant power - if (powerScalingFactor > 0.6f) { - level.sendParticles(ParticleTypes.EXPLOSION_EMITTER, - impactPoint.x, impactPoint.y, impactPoint.z, 1, 0, 0, 0, 0); - } - - // Stronger particle counts - int critCount = Math.max(0, Math.round((10 + chargePercent) * effectIntensity)); - int smokeCount = Math.max(0, Math.round((18 + chargePercent) * effectIntensity)); - int explosionCount = Math.max(0, Math.round((4 + chargePercent/5) * effectIntensity)); - - // Add minimal "dud" impact particles for very low power levels - if (critCount == 0 && smokeCount == 0 && chargePercent > 20) { - smokeCount = 1; // At least a tiny puff of smoke for the impact - if (chargePercent > 50) { - critCount = 1; // Maybe one crit particle if they charged a bit - } - } - - // Send particles - even minimal ones for dud effects - if (critCount > 0) { - // For dud effects, use very small radius and speed - float effectiveRadius = powerScalingFactor < 0.1f ? 0.1f : finalRadius; - level.sendParticles(ParticleTypes.CRIT, - impactPoint.x, impactPoint.y, impactPoint.z, - critCount, effectiveRadius, effectiveRadius, effectiveRadius, Math.max(0.005, 0.15 * powerScalingFactor)); - } - if (smokeCount > 0) { - // For dud effects, use very small radius and speed - float effectiveRadius = powerScalingFactor < 0.1f ? 0.15f : finalRadius * 1.5f; - level.sendParticles(ParticleTypes.SMOKE, - impactPoint.x, impactPoint.y, impactPoint.z, - smokeCount, effectiveRadius, effectiveRadius, effectiveRadius, Math.max(0.01, 0.2 * powerScalingFactor)); - } - if (explosionCount > 0) { - level.sendParticles(ParticleTypes.EXPLOSION, - impactPoint.x, impactPoint.y, impactPoint.z, - explosionCount, finalRadius * 0.8f, finalRadius * 0.8f, finalRadius * 0.8f, Math.max(0.005, 0.1 * powerScalingFactor)); - } - - // Shockwave ring around impact for flair - if (powerScalingFactor > 0.3f) { - int rings = Math.max(1, (int)(powerScalingFactor * 3)); - for (int r = 1; r <= rings; r++) { - double ringRadius = finalRadius * (0.5 + 0.3 * r); - for (int a = 0; a < 24; a++) { - double theta = (Math.PI * 2 * a) / 24.0; - double rx = impactPoint.x + ringRadius * Math.cos(theta); - double rz = impactPoint.z + ringRadius * Math.sin(theta); - level.sendParticles(ParticleTypes.CLOUD, rx, impactPoint.y + 0.2, rz, 1, 0.02, 0.02, 0.02, 0.02); - } - } - } - - // Scale sound effects with power - much quieter at low levels - if (powerScalingFactor > 0.1f) { - float impactSoundVolume = Math.max(0.1f, Math.min(4.0f, powerScalingFactor * 4.0f)); - level.playSound(null, BlockPos.containing(impactPoint), SoundEvents.GENERIC_EXPLODE, SoundSource.PLAYERS, impactSoundVolume, 0.4f); - - if (powerScalingFactor > 0.5f) { - level.playSound(null, BlockPos.containing(impactPoint), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.PLAYERS, impactSoundVolume * 0.7f, 0.8f); - } - } - } - - private void applyAreaDamage(ServerLevel level, ServerPlayer caster, Vec3 impactPoint, - float finalRadius, float finalDamage, float finalKnockback) { - // Find entities in radius - AABB searchArea = new AABB( - impactPoint.x - finalRadius, impactPoint.y - finalRadius, impactPoint.z - finalRadius, - impactPoint.x + finalRadius, impactPoint.y + finalRadius, impactPoint.z + finalRadius - ); - - List nearbyEntities = level.getEntitiesOfClass(LivingEntity.class, searchArea); - - for (LivingEntity entity : nearbyEntities) { - if (entity == caster) continue; // Don't damage the caster - - double distance = entity.position().distanceTo(impactPoint); - if (distance <= finalRadius) { - // Calculate damage and knockback based on distance (closer = more damage) - double damageMultiplier = Math.max(0.3, 1.0 - (distance / finalRadius)); - double knockbackMultiplier = Math.max(0.2, 1.0 - (distance / finalRadius)); - - float actualDamage = (float)(finalDamage * damageMultiplier); - float actualKnockback = (float)(finalKnockback * knockbackMultiplier); - - // Apply damage using explosion damage source - DamageSource explosionDamage = level.damageSources().explosion(caster, caster); - entity.hurt(explosionDamage, actualDamage); - - // Apply knockback effect - if (distance > 0.1) { // Avoid division by zero - Vec3 knockbackDirection = entity.position().subtract(impactPoint).normalize(); - - // Add upward component for more realistic knockback - knockbackDirection = new Vec3( - knockbackDirection.x, - Math.max(0.2, knockbackDirection.y + 0.3), - knockbackDirection.z - ); - - Vec3 knockbackVelocity = knockbackDirection.scale(actualKnockback * 0.25); - entity.setDeltaMovement(entity.getDeltaMovement().add(knockbackVelocity)); - if (entity instanceof ServerPlayer sp) { - sp.connection.send(new ClientboundSetEntityMotionPacket(sp)); + private void applyAreaDamage(ServerLevel level, ServerPlayer caster, Vec3 impactPoint, + float finalRadius, float finalDamage, float finalKnockback) { + // Find entities in radius + AABB searchArea = new AABB( + impactPoint.x - finalRadius, impactPoint.y - finalRadius, impactPoint.z - finalRadius, + impactPoint.x + finalRadius, impactPoint.y + finalRadius, impactPoint.z + finalRadius + ); + + List nearbyEntities = level.getEntitiesOfClass(LivingEntity.class, searchArea); + + for (LivingEntity entity : nearbyEntities) { + if (entity == caster) continue; // Don't damage the caster + + double distance = entity.position().distanceTo(impactPoint); + if (distance <= finalRadius) { + // Calculate damage and knockback based on distance (closer = more damage) + double damageMultiplier = Math.max(0.3, 1.0 - (distance / finalRadius)); + double knockbackMultiplier = Math.max(0.2, 1.0 - (distance / finalRadius)); + + float actualDamage = (float) (finalDamage * damageMultiplier); + float actualKnockback = (float) (finalKnockback * knockbackMultiplier); + + // Apply damage using explosion damage source + DamageSource explosionDamage = level.damageSources().explosion(caster, caster); + entity.hurt(explosionDamage, actualDamage); + + // Apply knockback effect + if (distance > 0.1) { // Avoid division by zero + Vec3 knockbackDirection = entity.position().subtract(impactPoint).normalize(); + + // Add upward component for more realistic knockback + knockbackDirection = new Vec3( + knockbackDirection.x, + Math.max(0.2, knockbackDirection.y + 0.3), + knockbackDirection.z + ); + + Vec3 knockbackVelocity = knockbackDirection.scale(actualKnockback * 0.25); + entity.setDeltaMovement(entity.getDeltaMovement().add(knockbackVelocity)); + if (entity instanceof ServerPlayer sp) { + sp.connection.send(new ClientboundSetEntityMotionPacket(sp)); + } } } } } - } - @Override - public String getDocumentationDescription() { - return "A charging punch ability based on My Hero Academia's Detroit Smash. Scales damage, range, and effects based on stored power in the user's chest. Creates ray-cast explosions along the punch trajectory with proper damage and knockback for all entities including players."; - } + @Override + public String getDocumentationDescription() { + return "A charging punch ability based on My Hero Academia's Detroit Smash. Scales damage, range, and effects based on stored power in the user's chest. Creates ray-cast explosions along the punch trajectory with proper damage and knockback for all entities including players."; + } - static { - CHARGE_TICKS = (new IntegerProperty("charge_ticks")).sync(SyncType.NONE).disablePersistence(); + static { + CHARGE_TICKS = (new IntegerProperty("charge_ticks")).sync(SyncType.NONE).disablePersistence(); + } } -} + diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstChargeGeneAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstChargeGeneAbility.java new file mode 100644 index 00000000..c0edb25c --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstChargeGeneAbility.java @@ -0,0 +1,134 @@ +package com.github.b4ndithelps.forge.abilities; + + +import com.github.b4ndithelps.forge.effects.ModEffects; +import com.github.b4ndithelps.forge.network.BQLNetwork; +import com.github.b4ndithelps.forge.network.PlayerAnimationPacket; +import com.github.b4ndithelps.forge.particle.ModParticles; +import com.github.b4ndithelps.forge.systems.BodyStatusHelper; +import com.github.b4ndithelps.forge.systems.PowerStockHelper; +import com.github.b4ndithelps.forge.systems.QuirkFactorHelper; +import com.github.b4ndithelps.forge.utils.ActionBarHelper; +import net.minecraft.ChatFormatting; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.RandomSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.LivingEntity; +import net.minecraftforge.network.PacketDistributor; +import net.threetag.palladium.power.IPowerHolder; +import net.threetag.palladium.power.ability.Ability; +import net.threetag.palladium.power.ability.AbilityInstance; +import net.threetag.palladium.util.property.*; + +public class SuperBurstChargeGeneAbility extends Ability { + private static final float FACTOR_SCALING = 10F; + // Unique properties for tracking state + public static final PalladiumProperty CHARGE_TICKS; + + public SuperBurstChargeGeneAbility() { + super(); + } + + public void registerUniqueProperties(PropertyManager manager) { + manager.register(CHARGE_TICKS, 0); + } + +// @Override +// public void firstTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { +// if (enabled && entity instanceof ServerPlayer player) { +// // Initialize charge ( not needed ) +//// entry.setUniqueProperty(CHARGE_TICKS, 0); +//// BodyStatusHelper.setCustomFloat(player, "chest", "burst_gene_charge", 0.0f); +// +// if (entity.level() instanceof ServerLevel serverLevel) { +// // Play charging start sound +// serverLevel.playSound(null, player.blockPosition(), +// SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.PLAYERS, 0.3f, 1.2f); +// } +// } +// } + + @Override + public void tick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { + if (!(entity instanceof ServerPlayer player)) return; + if (!(entity.level() instanceof ServerLevel serverLevel)) return; + + if (!enabled) { + return; + } + + player.addEffect(new MobEffectInstance(ModEffects.STUN_EFFECT.get(), 80, 244)); + + float charge = BodyStatusHelper.getCustomFloat(player, "chest", "super_burst_charge"); + float factor = (float) (QuirkFactorHelper.getQuirkFactor(player)+1) * charge/20; + + + RandomSource random = serverLevel.getRandom(); + + + double x = player.getX(); + double y = player.getY() +1; + double z = player.getZ(); + + serverLevel.sendParticles(ModParticles.CHARGE_DUST_PARTICLE.get(), x, y, z, 1, 1.5, 1.5, 1.5, 0); + + if (charge == 0) { + serverLevel.playSound(null, player.blockPosition(), SoundEvents.BEACON_ACTIVATE, SoundSource.PLAYERS, 1, 1); + BQLNetwork.CHANNEL.send( + PacketDistributor.PLAYER.with(() -> player), + new PlayerAnimationPacket("core_explosion_burst") + ); + } + + sendInfoMessage(player, charge, factor); + if (player.getHealth() - factor*FACTOR_SCALING <= 0) { + BodyStatusHelper.setCustomFloat(player, "chest", "super_burst_charge", charge-0.5f); + } + BodyStatusHelper.setCustomFloat(player, "chest", "super_burst_charge", ++charge); + + } + + @Override + public void lastTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { + if (entity instanceof ServerPlayer player && entity.level() instanceof ServerLevel serverLevel) { + serverLevel.playSound(null, player.blockPosition(), SoundEvents.TNT_PRIMED, SoundSource.PLAYERS, 2, 1); + + BodyStatusHelper.setCustomFloat(player, "chest", "super_burst_render", 1); + + } + } + + private void sendInfoMessage(ServerPlayer player, float chargePercent, float factor) { + ChatFormatting color = ChatFormatting.GREEN; + + if (player.getHealth() - factor*FACTOR_SCALING <= 0) { + color = ChatFormatting.BLACK; + } else if (player.getHealth() - factor*FACTOR_SCALING <= player.getHealth() * 0.25f) { + color = ChatFormatting.DARK_RED; + } else if (player.getHealth() - factor*FACTOR_SCALING <= player.getHealth() * 0.6f) { + color = ChatFormatting.YELLOW; + } + ActionBarHelper.sendPercentageDisplay( + player, + "Super Burst Charge", + chargePercent, + ChatFormatting.GRAY, + color, + chargePercent >= 400.0f ? "MAX" : "Charging" + ); + } + + + + @Override + public String getDocumentationDescription() { + return "Burst ability"; + } + + static { + CHARGE_TICKS = (new IntegerProperty("charge_ticks")).sync(SyncType.NONE).disablePersistence(); + } +} diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstExplosionGeneAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstExplosionGeneAbility.java new file mode 100644 index 00000000..9693c9c3 --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstExplosionGeneAbility.java @@ -0,0 +1,91 @@ +package com.github.b4ndithelps.forge.abilities; + + +import com.github.b4ndithelps.forge.network.BQLNetwork; +import com.github.b4ndithelps.forge.network.PlayerAnimationPacket; +import com.github.b4ndithelps.forge.systems.BodyStatusHelper; +import com.github.b4ndithelps.forge.systems.QuirkFactorHelper; +import net.minecraft.core.particles.DustParticleOptions; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; +import net.minecraftforge.network.PacketDistributor; +import net.threetag.palladium.power.IPowerHolder; +import net.threetag.palladium.power.ability.Ability; +import net.threetag.palladium.power.ability.AbilityInstance; +import org.joml.Vector3f; + +public class SuperBurstExplosionGeneAbility extends Ability { + private static final float FACTOR_SCALING = 10F; + + public SuperBurstExplosionGeneAbility() { + super(); + } + + + @Override + public void tick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { + if (!(entity instanceof ServerPlayer player)) return; + if (!(entity.level() instanceof ServerLevel serverLevel)) return; + + if (!enabled) { + return; + } + + float charge = BodyStatusHelper.getCustomFloat(player, "chest", "super_burst_charge"); + if (charge > 0) { + BQLNetwork.CHANNEL.send( + PacketDistributor.PLAYER.with(() -> player), + new PlayerAnimationPacket("x") + ); + executeBurst(player, serverLevel); + } + + } + + + private void executeBurst(ServerPlayer player, ServerLevel level) { + float charge = BodyStatusHelper.getCustomFloat(player, "chest", "super_burst_charge"); + float factor = (float) (QuirkFactorHelper.getQuirkFactor(player)+1) * charge/20; + player.hurt(level.damageSources().generic(), factor*FACTOR_SCALING); + level.explode( + player, // entity that caused it (null = no source) + player.getX(), // x + player.getY() + 1, // y + player.getZ(), // z + factor * FACTOR_SCALING * 1.5F, // factor * FACTOR_SCALING * 1.5F + Level.ExplosionInteraction.TNT // what kind of explosion (controls block damage) + ); + float playersHealthResistance = (float) Math.max(0.01 ,1 - ((player.getMaxHealth() - factor*FACTOR_SCALING) / player.getMaxHealth())); + BodyStatusHelper.damageAll(player, playersHealthResistance*50); + BodyStatusHelper.setCustomFloat(player, "chest", "super_burst_charge", 0); + + double x = player.getX(); + double y = player.getY() + 1.0; // slightly above feet + double z = player.getZ(); + + RandomSource random = player.level().random; + + for (int i = 0; i < (charge/20) * 3; i++) { // number of particles + double randomN = random.nextDouble() * (charge/10) * 3; // random angle + + // Random speed multiplier + double speed = 0.2 + random.nextDouble() * 0.5; // 0.2–0.7 + + + level.sendParticles( + new DustParticleOptions(new Vector3f(1.0F, 0.5F, 0.0F), 1.0F), // orange color, size=1 + x, y, z, + 10, randomN, randomN, randomN, 1 + ); + } + } + + + @Override + public String getDocumentationDescription() { + return "Burst end ability"; + } +} diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/client/AnimationsInit.java b/forge/src/main/java/com/github/b4ndithelps/forge/client/AnimationsInit.java new file mode 100644 index 00000000..a1f77e06 --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/client/AnimationsInit.java @@ -0,0 +1,28 @@ +package com.github.b4ndithelps.forge.client; + +import com.github.b4ndithelps.BanditsQuirkLib; +import dev.kosmx.playerAnim.api.layered.IAnimation; +import dev.kosmx.playerAnim.api.layered.ModifierLayer; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationFactory; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; + +@Mod.EventBusSubscriber(modid = BanditsQuirkLib.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) +public class AnimationsInit { + @SubscribeEvent + public static void clientSetup(FMLClientSetupEvent event) { + PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory( + ResourceLocation.fromNamespaceAndPath(BanditsQuirkLib.MOD_ID, "animation"), + 2, + AnimationsInit::registerPlayerAnimation); + } + + private static IAnimation registerPlayerAnimation(AbstractClientPlayer player) { + //This will be invoked for every new player + return new ModifierLayer<>(); + } +} \ No newline at end of file diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/client/ClientEventHandler.java b/forge/src/main/java/com/github/b4ndithelps/forge/client/ClientEventHandler.java index a378ba5b..64095770 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/client/ClientEventHandler.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/client/ClientEventHandler.java @@ -7,6 +7,7 @@ import com.github.b4ndithelps.util.FileManager; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.renderer.entity.ArrowRenderer; import net.minecraft.client.renderer.entity.EntityRenderer; import net.minecraft.client.renderer.entity.EntityRendererProvider; diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/conditions/BurstRenderCondition.java b/forge/src/main/java/com/github/b4ndithelps/forge/conditions/BurstRenderCondition.java new file mode 100644 index 00000000..20e8d773 --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/conditions/BurstRenderCondition.java @@ -0,0 +1,61 @@ +package com.github.b4ndithelps.forge.conditions; + +import com.github.b4ndithelps.forge.capabilities.Body.BodyPart; +import com.github.b4ndithelps.forge.capabilities.Body.DamageStage; +import com.github.b4ndithelps.forge.systems.BodyStatusHelper; +import com.google.gson.JsonObject; +import net.minecraft.world.entity.LivingEntity; +import net.threetag.palladium.condition.Condition; +import net.threetag.palladium.condition.ConditionEnvironment; +import net.threetag.palladium.condition.ConditionSerializer; +import net.threetag.palladium.util.context.DataContext; +import net.threetag.palladium.util.property.EnumPalladiumProperty; +import net.threetag.palladium.util.property.PalladiumProperty; +import net.threetag.palladium.util.property.PropertyManager; +import net.threetag.palladium.util.property.StringProperty; + +import java.util.Map; +import java.util.Objects; + +public class BurstRenderCondition extends Condition { + + @Override + public boolean active(DataContext dataContext) { + LivingEntity entity = dataContext.getLivingEntity(); + + // Accept both ServerPlayer and LocalPlayer (client-side) + if (!(entity instanceof net.minecraft.world.entity.player.Player player)) { + return false; + } + + float charge = BodyStatusHelper.getCustomFloat(player, "chest", "super_burst_render"); + + if (charge <= 30) { + BodyStatusHelper.setCustomFloat(player, "chest", "super_burst_render", ++charge); + return true; + } + return false; + } + + @Override + public ConditionSerializer getSerializer() { + return CustomConditionSerializers.BODY_CHECK.get(); + } + + public static class Serializer extends ConditionSerializer { + + public ConditionEnvironment getContextEnvironment() { + return ConditionEnvironment.ALL; + } + + + @Override + public Condition make(JsonObject jsonObject) { + return new BurstRenderCondition(); + } + + public String getDocumentationDescription() { + return "A condition that checks if a specific render property is more than 0 and reduces it."; + } + } +} diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/conditions/CustomConditionSerializers.java b/forge/src/main/java/com/github/b4ndithelps/forge/conditions/CustomConditionSerializers.java index d17d230c..af3e3263 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/conditions/CustomConditionSerializers.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/conditions/CustomConditionSerializers.java @@ -14,6 +14,7 @@ public class CustomConditionSerializers { public static final RegistrySupplier HELD_WITH_COOLDOWN; public static final RegistrySupplier BODY_FLOAT_CHECK; public static final RegistrySupplier RANDOM_CHANCE; + public static final RegistrySupplier BURST_RENDER; public static final DeferredRegister CUSTOM_SERIALIZERS; @@ -30,5 +31,7 @@ public CustomConditionSerializers() { HELD_WITH_COOLDOWN = CUSTOM_SERIALIZERS.register("held_with_cooldown", HeldWithCooldownCondition.Serializer::new); BODY_FLOAT_CHECK = CUSTOM_SERIALIZERS.register("body_float_check", BodyFloatCondition.Serializer::new); RANDOM_CHANCE = CUSTOM_SERIALIZERS.register("random_chance", RandomChanceCondition.Serializer::new); + BURST_RENDER = CUSTOM_SERIALIZERS.register("burst_render", BurstRenderCondition.Serializer::new); + } } diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/events/ModEventBusEvents.java b/forge/src/main/java/com/github/b4ndithelps/forge/events/ModEventBusEvents.java new file mode 100644 index 00000000..f121e9c4 --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/events/ModEventBusEvents.java @@ -0,0 +1,25 @@ +package com.github.b4ndithelps.forge.events; + +import com.github.b4ndithelps.BanditsQuirkLib; +import com.github.b4ndithelps.forge.particle.ModParticles; +import com.github.b4ndithelps.forge.particle.custom.ChargingDustParticle; +import com.github.b4ndithelps.forge.particle.custom.RisingDustParticle; +import net.minecraft.client.Minecraft; +import net.minecraft.world.entity.vehicle.Minecart; +import net.minecraftforge.client.event.RegisterParticleProvidersEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = BanditsQuirkLib.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) +public class ModEventBusEvents { + + @SubscribeEvent + public static void registerParticle(RegisterParticleProvidersEvent event) { + Minecraft.getInstance().particleEngine.register( + ModParticles.RISING_DUST.get(), RisingDustParticle.Provider::new + ); + Minecraft.getInstance().particleEngine.register( + ModParticles.CHARGE_DUST_PARTICLE.get(), ChargingDustParticle.Provider::new + ); + } +} diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/network/BQLNetwork.java b/forge/src/main/java/com/github/b4ndithelps/forge/network/BQLNetwork.java index 579837f2..177dd589 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/network/BQLNetwork.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/network/BQLNetwork.java @@ -49,6 +49,12 @@ public static void register() { .decoder(BlackScreenNetwork::decode) .consumerMainThread(BlackScreenNetwork::handle) .add(); + + CHANNEL.messageBuilder(PlayerAnimationPacket.class, index++, NetworkDirection.PLAY_TO_CLIENT) + .encoder(PlayerAnimationPacket::encode) + .decoder(PlayerAnimationPacket::decode) + .consumerMainThread(PlayerAnimationPacket::handle) + .add(); } } diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/network/PlayerAnimationPacket.java b/forge/src/main/java/com/github/b4ndithelps/forge/network/PlayerAnimationPacket.java new file mode 100644 index 00000000..8e33c123 --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/network/PlayerAnimationPacket.java @@ -0,0 +1,78 @@ +package com.github.b4ndithelps.forge.network; + + +import com.github.b4ndithelps.BanditsQuirkLib; +import dev.kosmx.playerAnim.api.firstPerson.FirstPersonConfiguration; +import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; +import dev.kosmx.playerAnim.api.layered.IAnimation; +import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer; +import dev.kosmx.playerAnim.api.layered.ModifierLayer; +import dev.kosmx.playerAnim.api.layered.modifier.AbstractFadeModifier; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.network.NetworkEvent; +import net.threetag.palladium.util.Easing; +import org.checkerframework.checker.units.qual.C; + +import java.util.function.Supplier; + +import static dev.kosmx.playerAnim.core.util.Ease.INOUTSINE; + + +// USE THIS TO PLAY PLAYER !MAIN! ANIMATION +public class PlayerAnimationPacket { + private final String animation; + + public PlayerAnimationPacket(String anim) { + this.animation = anim; + } + + public static void encode(PlayerAnimationPacket msg, FriendlyByteBuf buf) { + buf.writeUtf(msg.animation); + } + + public static PlayerAnimationPacket decode(FriendlyByteBuf buf) { + return new PlayerAnimationPacket(buf.readUtf()); + } + + public static void handle(PlayerAnimationPacket msg, Supplier ctx) { + ctx.get().enqueueWork(() -> { + Minecraft mc = Minecraft.getInstance(); + LocalPlayer player = mc.player; + + if (player != null) { + var animation = (ModifierLayer)PlayerAnimationAccess.getPlayerAssociatedData(player).get(ResourceLocation.fromNamespaceAndPath(BanditsQuirkLib.MOD_ID, "animation")); + if (animation != null) { + + if (msg.animation == "") { + animation.replaceAnimationWithFade( + AbstractFadeModifier.standardFadeIn(10, INOUTSINE), + null + ); + } else if (msg.animation.equals("x")) { + animation.setAnimation(null); + } else { + KeyframeAnimationPlayer freshAnim = new KeyframeAnimationPlayer( + PlayerAnimationRegistry.getAnimation( + ResourceLocation.fromNamespaceAndPath(BanditsQuirkLib.MOD_ID, msg.animation) + ) + ).setFirstPersonMode(FirstPersonMode.THIRD_PERSON_MODEL) + .setFirstPersonConfiguration(new FirstPersonConfiguration(true, false, true, false)); + + animation.replaceAnimationWithFade( + AbstractFadeModifier.standardFadeIn(10, INOUTSINE), + freshAnim + ); + } + } + } + }); + ctx.get().setPacketHandled(true); + } +} \ No newline at end of file diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/particle/ModParticles.java b/forge/src/main/java/com/github/b4ndithelps/forge/particle/ModParticles.java new file mode 100644 index 00000000..4ef0ee42 --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/particle/ModParticles.java @@ -0,0 +1,25 @@ +package com.github.b4ndithelps.forge.particle; + +import com.github.b4ndithelps.BanditsQuirkLib; +import com.github.b4ndithelps.forge.particle.custom.RisingDustParticle; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.core.particles.SimpleParticleType; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +public class ModParticles { + public static final DeferredRegister> PARTICLES = + DeferredRegister.create(ForgeRegistries.PARTICLE_TYPES, BanditsQuirkLib.MOD_ID); + + public static final RegistryObject RISING_DUST = PARTICLES.register("rising_dust", () -> new SimpleParticleType(true)); + + public static final RegistryObject CHARGE_DUST_PARTICLE = + PARTICLES.register("charging_dust_particle", () -> new SimpleParticleType(true)); + + public static void register(IEventBus bus) { + PARTICLES.register(bus); + } +} \ No newline at end of file diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/particle/custom/ChargingDustParticle.java b/forge/src/main/java/com/github/b4ndithelps/forge/particle/custom/ChargingDustParticle.java new file mode 100644 index 00000000..0c076c9d --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/particle/custom/ChargingDustParticle.java @@ -0,0 +1,85 @@ +package com.github.b4ndithelps.forge.particle.custom; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.*; +import net.minecraft.core.particles.SimpleParticleType; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +public class ChargingDustParticle extends TextureSheetParticle { + private final Player player; + + protected ChargingDustParticle(ClientLevel world, double x, double y, double z, Player player, double vx, double vy, double vz, SpriteSet sprites) { + super(world, x, y, z, vx, vy, vz); + this.player = player; + this.lifetime = 50; // ticks before auto-despawn + this.gravity = 0.0F; + this.xd = vx; + this.yd = vy; + this.zd = vz; + this.alpha = 0; + this.pickSprite(sprites); +// this.setSpriteFromAge(sprites); +// this.setColor(0.9f, 0.8f, 0.5f); +// this.quadSize = 0.1f; + } + + @Override + public void tick() { + super.tick(); + + if (player == null || !player.isAlive()) { + this.remove(); + return; + } + + if (this.age < 16) { + fadeIn(); + return; + } + + // Move toward player + Vec3 toPlayer = player.position().subtract(this.x, this.y-1, this.z); + double distance = toPlayer.length(); + + if (distance < 0.3) { + this.remove(); // reached player + return; + } + + Vec3 direction = toPlayer.normalize().scale(0.2); // adjust speed + this.xd = direction.x; + this.yd = direction.y; + this.zd = direction.z; + } + + @Override + public ParticleRenderType getRenderType() { + return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT; + } + + public void fadeIn() { + this.alpha = Math.min(1, age / 10); + } + + @OnlyIn(Dist.CLIENT) + public static class Provider implements ParticleProvider { + private final SpriteSet spriteSet; + + public Provider(SpriteSet spriteSet) { + this.spriteSet = spriteSet; + } + + @Override + public Particle createParticle(SimpleParticleType type, ClientLevel world, double x, double y, double z, + double dx, double dy, double dz) { + Player player = Minecraft.getInstance().player; + ChargingDustParticle particle = new ChargingDustParticle(world, x, y, z, player, dx, dy, dz, spriteSet); + particle.pickSprite(spriteSet); + return particle; + } + } +} diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/particle/custom/RisingDustParticle.java b/forge/src/main/java/com/github/b4ndithelps/forge/particle/custom/RisingDustParticle.java new file mode 100644 index 00000000..45d40bb6 --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/particle/custom/RisingDustParticle.java @@ -0,0 +1,54 @@ +package com.github.b4ndithelps.forge.particle.custom; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.*; +import net.minecraft.core.particles.DustParticleOptions; +import net.minecraft.core.particles.SimpleParticleType; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.jetbrains.annotations.Nullable; + +public class RisingDustParticle extends TextureSheetParticle { + protected RisingDustParticle(ClientLevel level, double x, double y, double z, + double vx, double vy, double vz, SpriteSet sprites) { + super(level, x, y, z, vx, vy, vz); + this.gravity = 0.0F; + this.xd = vx; + this.yd = vy; + this.zd = vz; + this.alpha = 0; + this.lifetime = 30; // ticks + this.setSpriteFromAge(sprites); + } + + @Override + public void tick() { + super.tick(); + fadeIn(); // keep moving up + } + + public void fadeIn() { + this.alpha = Math.min(1, (1/(float)lifetime) * age * 10); + } + + @Override + public ParticleRenderType getRenderType() { + return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT; + } + + @OnlyIn(Dist.CLIENT) + public static class Provider implements ParticleProvider { + private final SpriteSet sprites; + + public Provider(SpriteSet sprites) { + this.sprites = sprites; + } + + + public Particle createParticle(SimpleParticleType data, ClientLevel level, + double x, double y, double z, + double vx, double vy, double vz) { + return new RisingDustParticle(level, x, y, z, vx, vy, vz, sprites); + } + } +} \ No newline at end of file diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/sounds/ModSounds.java b/forge/src/main/java/com/github/b4ndithelps/forge/sounds/ModSounds.java index da18f38f..17c83cb3 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/sounds/ModSounds.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/sounds/ModSounds.java @@ -13,4 +13,7 @@ public class ModSounds { public static final RegistryObject HEARTBEAT = SOUND_EVENTS.register("heartbeat", () -> SoundEvent.createVariableRangeEvent(new ResourceLocation(BanditsQuirkLib.MOD_ID, "heartbeat"))); + public static final RegistryObject EXPLOSION_CHARGE = + SOUND_EVENTS.register("explosion_charge", + () -> SoundEvent.createVariableRangeEvent(new ResourceLocation(BanditsQuirkLib.MOD_ID, "explosion_charge"))); } diff --git a/forge/src/main/resources/assets/bandits_quirk_lib/particles/charging_dust_particle.json b/forge/src/main/resources/assets/bandits_quirk_lib/particles/charging_dust_particle.json new file mode 100644 index 00000000..29d06f1a --- /dev/null +++ b/forge/src/main/resources/assets/bandits_quirk_lib/particles/charging_dust_particle.json @@ -0,0 +1,5 @@ +{ + "textures": [ + "bandits_quirk_lib:charge_dust" + ] +} \ No newline at end of file diff --git a/forge/src/main/resources/assets/bandits_quirk_lib/particles/rising_dust.json b/forge/src/main/resources/assets/bandits_quirk_lib/particles/rising_dust.json new file mode 100644 index 00000000..4a750c65 --- /dev/null +++ b/forge/src/main/resources/assets/bandits_quirk_lib/particles/rising_dust.json @@ -0,0 +1,5 @@ +{ + "textures": [ + "bandits_quirk_lib:particle" + ] +} \ No newline at end of file diff --git a/forge/src/main/resources/assets/bandits_quirk_lib/player_animation/core_explosion_burst.json b/forge/src/main/resources/assets/bandits_quirk_lib/player_animation/core_explosion_burst.json new file mode 100644 index 00000000..05553208 --- /dev/null +++ b/forge/src/main/resources/assets/bandits_quirk_lib/player_animation/core_explosion_burst.json @@ -0,0 +1,141 @@ +{ + "format_version": "1.8.0", + "animations": { + "core_explosion_burst": { + "animation_length": 35, + "bones": { + "head": { + "rotation": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.375": { + "vector": [25, 0, 0] + }, + "0.5833": { + "vector": [42.5, 0, 0] + } + } + }, + "body": { + "rotation": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.375": { + "vector": [-10, 0, 0] + }, + "0.5833": { + "vector": [-15, 0, 0] + } + }, + "position": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.375": { + "vector": [0, 0, 0.5] + }, + "0.5833": { + "vector": [0, 0, 0.75] + } + } + }, + "right_arm": { + "rotation": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.375": { + "vector": [-66.0146, -17.66046, -9.54013] + }, + "0.5833": { + "vector": [-103.5146, -17.66046, -9.54013] + } + }, + "position": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.375": { + "vector": [0.25, 0, 1] + }, + "0.5833": { + "vector": [0.25, -2.5, 1] + } + } + }, + "left_arm": { + "rotation": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.375": { + "vector": [-61.29527, 19.51252, 22.89676] + }, + "0.5833": { + "vector": [-94.45638, 18.05368, 3.78271] + } + }, + "position": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.375": { + "vector": [1, 0, 0.5] + }, + "0.5833": { + "vector": [-0.5, -1.25, 0.5] + } + } + }, + "right_leg": { + "rotation": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.375": { + "vector": [32.5, 0, 0] + } + }, + "position": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.375": { + "vector": [0, 2, -4.75] + }, + "0.5833": { + "vector": [-0.25, 4.5, -7] + } + } + }, + "left_leg": { + "rotation": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.375": { + "vector": [27.5, 0, 0] + }, + "0.5833": { + "vector": [35, 0, 0] + } + }, + "position": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.375": { + "vector": [0, 2, -5.5] + }, + "0.5833": { + "vector": [0.25, 4.5, -7] + } + } + } + } + } + }, + "geckolib_format_version": 2 +} \ No newline at end of file diff --git a/forge/src/main/resources/assets/bandits_quirk_lib/player_animation/explode_effects.json b/forge/src/main/resources/assets/bandits_quirk_lib/player_animation/explode_effects.json new file mode 100644 index 00000000..70d7f710 --- /dev/null +++ b/forge/src/main/resources/assets/bandits_quirk_lib/player_animation/explode_effects.json @@ -0,0 +1,53 @@ +{ + "format_version": "1.8.0", + "animations": { + "explode_effects": { + "animation_length": 10.2917, + "bones": { + "body/explosion_cube": { + "rotation": { + "vector": [0, 0, 0] + }, + "position": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.5": { + "vector": [0, -60, 0] + } + }, + "scale": { + "0.0": { + "vector": [1, 1, 1] + }, + "0.5": { + "vector": [20, 20, 20] + } + } + }, + "body/explosion": { + "rotation": { + "vector": [0, 0, 0] + }, + "position": { + "0.0": { + "vector": [0, 0, 0] + }, + "0.5": { + "vector": [0, 0, 122] + } + }, + "scale": { + "0.0": { + "vector": [1, 1, 1] + }, + "0.5": { + "vector": [17, 1, 17] + } + } + } + } + } + }, + "geckolib_format_version": 2 +} \ No newline at end of file diff --git a/forge/src/main/resources/assets/bandits_quirk_lib/sounds.json b/forge/src/main/resources/assets/bandits_quirk_lib/sounds.json index ab395022..761d041b 100644 --- a/forge/src/main/resources/assets/bandits_quirk_lib/sounds.json +++ b/forge/src/main/resources/assets/bandits_quirk_lib/sounds.json @@ -3,5 +3,10 @@ "sounds": [ { "name": "bandits_quirk_lib:heartbeat" } ] + }, + "explosion_charge": { + "sounds": [ + { "name": "bandits_quirk_lib:explosion_charge" } + ] } } \ No newline at end of file diff --git a/forge/src/main/resources/assets/bandits_quirk_lib/sounds/explosion_charge.ogg b/forge/src/main/resources/assets/bandits_quirk_lib/sounds/explosion_charge.ogg new file mode 100644 index 0000000000000000000000000000000000000000..e469f23f3c4decf58a9611026c618fa3e340cbea GIT binary patch literal 22975 zcmeFYWmsIzwm-OW*WeJe(cl&!IE3KdxNFcL!GgO>aCaxTLy&}^L4rF32sAFig7!3d z&%Ni|`SPFVx$|kBsjgkUYL)H!t&&w$yHu>LH2`?vUk3hLu;&vgI4zV4M+xWt&c)2e z^@#y5{psls00Nrf{{FYYsXV3pZ}XJ$DFkKOR|QWL`acZ{!aq4-!XhY|x?AycvBQ3x zoKFcTVUMMw`8x|s7ZF&PIV_ClDU90Q#_XR|a7h3B(@RQe!2#j`0CFW^ktuN{3c)3l zR3+V_kQ}!=B$ZUe3!>mw)c;QJnm|*WFBzfF6!;?n3_K+Wm+&MAiIPC2385F!NepHb zfl(2HFrLj54_ETPoY#VHVWASJ&qKC21u%oR1^!TkycX2Kh4FT&LvSS9GJ`(}!XktC zWL1Na1$QYzzOph!1%2aeBML#}V~Pp-#W^$+j3F7387#;5hb+We!jtHqGC&ft3Bkw` zLnI+G60kQw7HlI3`7TjT7Q!NkN)Ze+g{!I?U z5O>PPsscHJ6M+=@uN+{n5r7xP`7PkTKn@_0$1X&x7F^HX%Gbrtl&=?jo-Nz#m6#RP zE(pnX9UtDw<{F1lh1t58nXq_}>~HzH=>JfR^#<9jCAcmptsq1r1UGqxE< zAywPNWh&IeDA{%|^(=)ap6gRqf1UmQJ&NpSPu}vb_SCE&{V+;pU~erO_rIh3jrSL1 zSbfED!(vTWm{F%7Z>eSQ%*eR+B(8Tk(a3_cCHE(H-xeneq0*-~tIIR~12{Y&j!F|s zpf3Agj#&gv7>UaWb6I|nJWEA+VGQ?o*2dAlj?m$!M(kIZT;Fh|UQ~Zm*KpvQ)3G}i zgk%OcY3bQjH>=Atmn5sB|HY3QhP@;*sYQa?Q_f})%w^dw=q%;`z)Afi8URrFO)-i0 z{NMJBVgip|4IgINrn&j1HC}_=Eay|*bX$G(7eWoMeOF=jkDIAZs-^D#u>VvY*2}43 zjs2gBFb|NpjtcIAl5zj5nlOO^P6TiMwl}#3kirybY>HLooLA+Uh#JA`X@enYU9gLbNeev8|3n&tAf@A`jOLzKT�LPOL^|d~TIR$pfjE$y_}d{C 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 W7l4e4dQi7nWRa!zR9*(Ir)>bP`}F|; literal 0 HcmV?d00001 diff --git a/forge/src/main/resources/assets/bandits_quirk_lib/textures/particle/charge_dust.png b/forge/src/main/resources/assets/bandits_quirk_lib/textures/particle/charge_dust.png new file mode 100644 index 0000000000000000000000000000000000000000..8fdb132f69e956b879cf62ef5ffd0629e1308389 GIT binary patch literal 277 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?e4;Gf?**8cfQ}ZT|g1e0*}aI1_nK45N51cYF`EvWH0gbb!C6aCCn=#9XX-*0#GQ@ z)5S5w;`G~z2YC+|a4^d*`)C$9HRrI|JMH~q6F#j7`MU4K4Ymi%Rg|WjeS77A*7pvN z;0V=u#Y)je^Q`hermbb2#K16Vaj8xDbd{aEZ*jiMn($`lzE8*a3m9j2**}u8QZ@%# O!rl>l literal 0 HcmV?d00001 diff --git a/forge/src/main/resources/assets/bandits_quirk_lib/textures/particle/particle.png b/forge/src/main/resources/assets/bandits_quirk_lib/textures/particle/particle.png new file mode 100644 index 0000000000000000000000000000000000000000..44f69d747e6490d9dd78e4b504d4422d0ea834fb GIT binary patch literal 356 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?e4eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?e4QI)9^5O6Bi**G=KU|LtpZqqnb-xyPFD Date: Wed, 5 Nov 2025 20:13:38 +0100 Subject: [PATCH 5/7] gene (Skulk Core) - Raw) --- .../palladium/render_layers/skulk_core.json | 5 ++ .../render_layers/skulk_core_deactivated.json | 5 ++ .../textures/models/skulkcore/skulkcore.png | Bin 0 -> 277 bytes .../skulkcore/skulkcore_deactivated.png | Bin 0 -> 345 bytes .../data/bql/palladium/powers/burst_gene.json | 8 ++- .../data/bql/palladium/powers/skulk_core.json | 47 ++++++++++++++ .../SuperBurstChargeGeneAbility.java | 4 +- .../CustomConditionSerializers.java | 2 + .../forge/conditions/SkulkCoreCondition.java | 60 ++++++++++++++++++ 9 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 common/src/main/resources/assets/bql/palladium/render_layers/skulk_core.json create mode 100644 common/src/main/resources/assets/bql/palladium/render_layers/skulk_core_deactivated.json create mode 100644 common/src/main/resources/assets/bql/textures/models/skulkcore/skulkcore.png create mode 100644 common/src/main/resources/assets/bql/textures/models/skulkcore/skulkcore_deactivated.png create mode 100644 common/src/main/resources/data/bql/palladium/powers/skulk_core.json create mode 100644 forge/src/main/java/com/github/b4ndithelps/forge/conditions/SkulkCoreCondition.java diff --git a/common/src/main/resources/assets/bql/palladium/render_layers/skulk_core.json b/common/src/main/resources/assets/bql/palladium/render_layers/skulk_core.json new file mode 100644 index 00000000..be025048 --- /dev/null +++ b/common/src/main/resources/assets/bql/palladium/render_layers/skulk_core.json @@ -0,0 +1,5 @@ +{ + "type": "palladium:skin_overlay", + "render_type": "solid", + "texture": "bql:textures/models/skulkcore/skulkcore.png" +} diff --git a/common/src/main/resources/assets/bql/palladium/render_layers/skulk_core_deactivated.json b/common/src/main/resources/assets/bql/palladium/render_layers/skulk_core_deactivated.json new file mode 100644 index 00000000..cd010274 --- /dev/null +++ b/common/src/main/resources/assets/bql/palladium/render_layers/skulk_core_deactivated.json @@ -0,0 +1,5 @@ +{ + "type": "palladium:skin_overlay", + "render_type": "solid", + "texture": "bql:textures/models/skulkcore/skulkcore_deactivated.png" +} diff --git a/common/src/main/resources/assets/bql/textures/models/skulkcore/skulkcore.png b/common/src/main/resources/assets/bql/textures/models/skulkcore/skulkcore.png new file mode 100644 index 0000000000000000000000000000000000000000..68eeb1b0a8395ffa723d4f54cefde46fce7ec337 GIT binary patch literal 277 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|&U?BzhE&XX zd)1KZkb^+$!*s5|jtDJB7oYURKl7uv?x1r>sC;t;} z&%Jy6%sLjJ5g?#(?CYOBuZ%;^ef{%i>zBg|EvC(0-tABo_pj=fxlVTNX_JP!<%>%M zSL}?rwwgKS{CU&48h%2h?9N-$io1=LZg}1B*6;1<$Vi!&+ahOA)jXxraD9)pt^d!A zH9CDdr=~3W!f-GBuiUyT7IG#0;lHlBU$|ml)0~!7{?K0l25Ji)%(KChOFc5`|aoOrSARl{$j_PLuv`u_ph8^f3V?im2VhOQ_ruT zWju9Xe#hL~mGboiXNG*m?xStjzw51j*|VlS;l=!{xYn8R%?rObtl51}tc-uh_8&dw zN4{8#oQ-eB<49Du``H~3WnlRK|Mbl5`M>~T1BDDT!+t?G^|$*L@&ZK|JYD@<);T3K F0RSuok?sHh literal 0 HcmV?d00001 diff --git a/common/src/main/resources/data/bql/palladium/powers/burst_gene.json b/common/src/main/resources/data/bql/palladium/powers/burst_gene.json index c5bbbf18..17236f30 100644 --- a/common/src/main/resources/data/bql/palladium/powers/burst_gene.json +++ b/common/src/main/resources/data/bql/palladium/powers/burst_gene.json @@ -53,12 +53,12 @@ "description": "Big boo boom", "hidden": false, "hidden_in_bar": false, - "gui_position": [0,0], + "gui_position": [0,-1], "list_index": 1, "conditions": { "enabling": [ { - "type": "palladium:held", + "type": "bandits_quirk_lib:held_with_cooldown", "cooldown": 100, "key_type": "key_bind", "needs_empty_hand": false @@ -75,6 +75,8 @@ "ExplosionProg": { "type": "palladium:repeating_animation_timer", "start_value": 1, + "hidden": true, + "hidden_in_bar": true, "max_value": 30, "conditions": { "enabling": [ @@ -109,6 +111,8 @@ }, "CoreExplosion End": { "type": "bandits_quirk_lib:super_burst_explosion_gene", + "hidden": true, + "hidden_in_bar": true, "conditions": { "enabling": [ { diff --git a/common/src/main/resources/data/bql/palladium/powers/skulk_core.json b/common/src/main/resources/data/bql/palladium/powers/skulk_core.json new file mode 100644 index 00000000..4c40970a --- /dev/null +++ b/common/src/main/resources/data/bql/palladium/powers/skulk_core.json @@ -0,0 +1,47 @@ +{ + "name": "Sculk Core", + "background": "minecraft:textures/block/blue_wool.png", + "icon": "minecraft:echo_shard", + "gui_display_type": "tree", + "abilities": { + "Gene Ability": { + "title": "Passive - Sculk Core", + "icon": "minecraft:echo_shard", + "type": "palladium:render_layer", + "description": "An ancient energy source, alone its useless. Maybe it can be utilized by other powers?", + "hidden": false, + "hidden_in_bar": true, + "gui_position": [ + 0, + 0 + ], + "render_layer": "bql:skulk_core", + "conditions": { + "enabling": [ + { + "type": "bandits_quirk_lib:skulk_core" + } + ] + } + }, + "Deactivated": { + "icon": "minecraft:echo_shard", + "type": "palladium:render_layer", + "hidden": true, + "hidden_in_bar": true, + "render_layer": "bql:skulk_core_deactivated", + "conditions": { + "enabling": [ + { + "type": "palladium:not", + "conditions": [{ + "type": "palladium:ability_enabled", + "power": "null", + "ability": "Gene Ability" + }] + } + ] + } + } + } +} \ No newline at end of file diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstChargeGeneAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstChargeGeneAbility.java index c0edb25c..3bcf9f01 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstChargeGeneAbility.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstChargeGeneAbility.java @@ -60,7 +60,7 @@ public void tick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder return; } - player.addEffect(new MobEffectInstance(ModEffects.STUN_EFFECT.get(), 80, 244)); + player.addEffect(new MobEffectInstance(ModEffects.STUN_EFFECT.get(), 80, 244, true, false)); float charge = BodyStatusHelper.getCustomFloat(player, "chest", "super_burst_charge"); float factor = (float) (QuirkFactorHelper.getQuirkFactor(player)+1) * charge/20; @@ -114,7 +114,7 @@ private void sendInfoMessage(ServerPlayer player, float chargePercent, float fac ActionBarHelper.sendPercentageDisplay( player, "Super Burst Charge", - chargePercent, + chargePercent/4.0f, ChatFormatting.GRAY, color, chargePercent >= 400.0f ? "MAX" : "Charging" diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/conditions/CustomConditionSerializers.java b/forge/src/main/java/com/github/b4ndithelps/forge/conditions/CustomConditionSerializers.java index af3e3263..98af6292 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/conditions/CustomConditionSerializers.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/conditions/CustomConditionSerializers.java @@ -15,6 +15,7 @@ public class CustomConditionSerializers { public static final RegistrySupplier BODY_FLOAT_CHECK; public static final RegistrySupplier RANDOM_CHANCE; public static final RegistrySupplier BURST_RENDER; + public static final RegistrySupplier SKULK_CORE_CHECK; public static final DeferredRegister CUSTOM_SERIALIZERS; @@ -32,6 +33,7 @@ public CustomConditionSerializers() { BODY_FLOAT_CHECK = CUSTOM_SERIALIZERS.register("body_float_check", BodyFloatCondition.Serializer::new); RANDOM_CHANCE = CUSTOM_SERIALIZERS.register("random_chance", RandomChanceCondition.Serializer::new); BURST_RENDER = CUSTOM_SERIALIZERS.register("burst_render", BurstRenderCondition.Serializer::new); + SKULK_CORE_CHECK = CUSTOM_SERIALIZERS.register("skulk_core", SkulkCoreCondition.Serializer::new); } } diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/conditions/SkulkCoreCondition.java b/forge/src/main/java/com/github/b4ndithelps/forge/conditions/SkulkCoreCondition.java new file mode 100644 index 00000000..ec42686b --- /dev/null +++ b/forge/src/main/java/com/github/b4ndithelps/forge/conditions/SkulkCoreCondition.java @@ -0,0 +1,60 @@ +package com.github.b4ndithelps.forge.conditions; + +import com.github.b4ndithelps.forge.systems.BodyStatusHelper; +import com.google.gson.JsonObject; +import net.minecraft.world.entity.LivingEntity; +import net.threetag.palladium.condition.Condition; +import net.threetag.palladium.condition.ConditionEnvironment; +import net.threetag.palladium.condition.ConditionSerializer; +import net.threetag.palladium.util.context.DataContext; + +public class SkulkCoreCondition extends Condition { + + @Override + public boolean active(DataContext dataContext) { + LivingEntity entity = dataContext.getLivingEntity(); + + // Accept both ServerPlayer and LocalPlayer (client-side) + if (!(entity instanceof net.minecraft.world.entity.player.Player player)) { + return false; + } + + float charge = BodyStatusHelper.getCustomFloat(player, "chest", "skulk_core"); + + if (charge == 0) { + float recharge = BodyStatusHelper.getCustomFloat(player, "chest", "skulk_core_recharge"); + + if (recharge == 400) { + BodyStatusHelper.setCustomFloat(player, "chest", "skulk_core", 1); + BodyStatusHelper.setCustomFloat(player, "chest", "skulk_core_recharge", 0); + return true; + } + + BodyStatusHelper.setCustomFloat(player, "chest", "skulk_core_recharge", ++recharge); + return false; + } + return true; + } + + @Override + public ConditionSerializer getSerializer() { + return CustomConditionSerializers.BODY_CHECK.get(); + } + + public static class Serializer extends ConditionSerializer { + + public ConditionEnvironment getContextEnvironment() { + return ConditionEnvironment.ALL; + } + + + @Override + public Condition make(JsonObject jsonObject) { + return new SkulkCoreCondition(); + } + + public String getDocumentationDescription() { + return "A condition that checks for skulk core."; + } + } +} From 27fe084a9899520f07290b99dfaef69266563f01 Mon Sep 17 00:00:00 2001 From: KeeperOfHearts Date: Mon, 10 Nov 2025 20:02:55 +0100 Subject: [PATCH 6/7] Genes optimization Changing genes (from separate powers to abilities in main gene quirk) --- .../render_layers/ender_gene_overlay.json | 4 +- .../bql/palladium/powers/adrenalinegene.json | 26 - .../data/bql/palladium/powers/dead_cells.json | 17 - .../data/bql/palladium/powers/ender_gene.json | 78 -- .../data/bql/palladium/powers/gene_quirk.json | 128 +++ .../palladium/powers/phantom_immunity.json | 17 - .../forge/abilities/AdrenalineAbility.java | 1 - .../forge/abilities/DetroitSmashAbility.java | 825 +++++++----------- .../conditions/BurstRenderCondition.java | 2 - .../forge/events/PhantomSpawnHandler.java | 19 +- .../forge/events/ZombieTargetingHandler.java | 16 +- .../forge/network/BlackScreenNetwork.java | 1 - .../assets/bandits_quirk_lib/lang/en_us.json | 7 +- .../bandits_quirk_lib/genes/adrenaline.json | 8 + .../bandits_quirk_lib/genes/burst_gene.json | 14 + .../bandits_quirk_lib/genes/dead_cells.json | 8 + .../bandits_quirk_lib/genes/ender_gene.json | 8 + .../genes/phantom_immunity.json | 8 + 18 files changed, 510 insertions(+), 677 deletions(-) delete mode 100644 common/src/main/resources/data/bql/palladium/powers/adrenalinegene.json delete mode 100644 common/src/main/resources/data/bql/palladium/powers/dead_cells.json delete mode 100644 common/src/main/resources/data/bql/palladium/powers/ender_gene.json delete mode 100644 common/src/main/resources/data/bql/palladium/powers/phantom_immunity.json create mode 100644 forge/src/main/resources/data/bandits_quirk_lib/genes/adrenaline.json create mode 100644 forge/src/main/resources/data/bandits_quirk_lib/genes/burst_gene.json create mode 100644 forge/src/main/resources/data/bandits_quirk_lib/genes/dead_cells.json create mode 100644 forge/src/main/resources/data/bandits_quirk_lib/genes/ender_gene.json create mode 100644 forge/src/main/resources/data/bandits_quirk_lib/genes/phantom_immunity.json diff --git a/common/src/main/resources/assets/bql/palladium/render_layers/ender_gene_overlay.json b/common/src/main/resources/assets/bql/palladium/render_layers/ender_gene_overlay.json index 2c669f8c..906aeaaa 100644 --- a/common/src/main/resources/assets/bql/palladium/render_layers/ender_gene_overlay.json +++ b/common/src/main/resources/assets/bql/palladium/render_layers/ender_gene_overlay.json @@ -11,8 +11,8 @@ "variables": { "MASK": { "type": "palladium:ability_integer_property", - "power": "bql:ender_gene", - "ability": "enablepls", + "power": "bql:gene_quirk", + "ability": "ender_gene", "property": "value" } }, diff --git a/common/src/main/resources/data/bql/palladium/powers/adrenalinegene.json b/common/src/main/resources/data/bql/palladium/powers/adrenalinegene.json deleted file mode 100644 index 06186c2c..00000000 --- a/common/src/main/resources/data/bql/palladium/powers/adrenalinegene.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "Adrenaline Gene", - "background": "minecraft:textures/block/blue_wool.png", - "icon": "minecraft:rabbit_foot", - "gui_display_type": "tree", - "abilities": { - "Gene Ability": { - "title" : "Passive - Adrenaline", - "icon" : "minecraft:rabbit_foot", - "type": "bandits_quirk_lib:adrenaline", - "description": "When low on health your adrenaline kicks in", - "hidden": false, - "hidden_in_bar": true, - "gui_position": [0,0], - "conditions": { - "enabling": [ - { - "type": "palladium:health", - "min_health": 0, - "max_health": 6 - } - ] - } - } - } -} \ No newline at end of file diff --git a/common/src/main/resources/data/bql/palladium/powers/dead_cells.json b/common/src/main/resources/data/bql/palladium/powers/dead_cells.json deleted file mode 100644 index a0c5396e..00000000 --- a/common/src/main/resources/data/bql/palladium/powers/dead_cells.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "Dead Cells", - "background": "minecraft:textures/block/green_wool.png", - "icon": "minecraft:zombie_head", - "gui_display_type": "tree", - "abilities": { - "Gene Ability": { - "title" : "Passive - Dead Cells", - "icon" : "minecraft:zombie_head", - "type": "palladium:dummy", - "description": "Zombies are neutral towards you.", - "hidden": false, - "hidden_in_bar": true, - "gui_position": [0,0] - } - } -} \ No newline at end of file diff --git a/common/src/main/resources/data/bql/palladium/powers/ender_gene.json b/common/src/main/resources/data/bql/palladium/powers/ender_gene.json deleted file mode 100644 index 0e06f205..00000000 --- a/common/src/main/resources/data/bql/palladium/powers/ender_gene.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "name": "Ender Gene", - "background": "minecraft:textures/block/blue_wool.png", - "icon": "minecraft:chorus_fruit", - "gui_display_type": "tree", - "abilities": { - "TP": { - "type": "bandits_quirk_lib:ender_gene_tp", - "hidden": true, - "hidden_in_bar": true, - "conditions": { - "enabling": [ - { - "type": "palladium:ability_integer_property", - "power": "null", - "ability": "enablepls", - "property": "value", - "min": 9, - "max": 10 - } - ] - } - }, - "Render Layer": { - "type": "palladium:render_layer", - "render_layer": "bql:ender_gene_overlay" - }, - "enablepls": { - "title" : "Ability - Ender Gene", - "type": "palladium:repeating_animation_timer", - "icon" : "minecraft:chorus_fruit", - "gui_position": [0,0], - "description": "You are able to use ender energy to teleport! But you cant control it.", - "hidden_in_bar": false, - "hidden": false, - "start_value": 0, - "max_value": 10, - "conditions": { - "enabling": [ - { - "type": "palladium:activation", - "cooldown": 60, - "ticks": 9, - "key_type": "key_bind", - "needs_empty_hand": false, - "allow_scrolling_when_crouching": true - } - ] - }, - "stamina_first_cost": 30 - }, - "hide overlay": { - "type": "palladium:hide_body_part", - "hidden": true, - "hidden_in_bar": true, - "list_index": -1, - "body_parts": [ - "head_overlay", - "chest_overlay", - "right_arm_overlay", - "left_arm_overlay", - "right_leg_overlay", - "left_leg_overlay" - ], - "affects_first_person": true, - "conditions": { - "enabling": [ - { - "type": "palladium:ability_enabled", - "power": "null", - "ability": "enablepls" - } - ] - } - } - - } -} \ No newline at end of file diff --git a/common/src/main/resources/data/bql/palladium/powers/gene_quirk.json b/common/src/main/resources/data/bql/palladium/powers/gene_quirk.json index a3c331d5..9c761e26 100644 --- a/common/src/main/resources/data/bql/palladium/powers/gene_quirk.json +++ b/common/src/main/resources/data/bql/palladium/powers/gene_quirk.json @@ -283,6 +283,134 @@ } ] } + }, + "adrenaline": { + "type": "bandits_quirk_lib:adrenaline", + "icon": "minecraft:rabbit_foot", + "title": "Adrenaline", + "hidden_in_bar": true, + "description": "When in danger your adrenaline kicks in.", + "conditions": { + "enabling": [ + { + "type": "palladium:health", + "min_health": 0, + "max_health": 6 + } + ], + "unlocking": [ + { + "type": "bandits_quirk_lib:genome_has_gene", + "gene_id": "bandits_quirk_lib:gene.adrenaline", + "min_quality": 0 + } + ] + } + }, + "phantom_immunity": { + "type": "palladium:dummy", + "icon": "minecraft:phantom_membrane", + "title": "Phantom Immunity", + "hidden_in_bar": true, + "description": "Phantoms cannot spawn on you.", + "conditions": { + "unlocking": [ + { + "type": "bandits_quirk_lib:genome_has_gene", + "gene_id": "bandits_quirk_lib:gene.phantom_immunity", + "min_quality": 0 + } + ] + } + }, + "dead_cells": { + "type": "palladium:dummy", + "icon": "minecraft:zombie_head", + "title": "Dead Cells", + "hidden_in_bar": true, + "description": "Zombies are neutral towards you.", + "conditions": { + "unlocking": [ + { + "type": "bandits_quirk_lib:genome_has_gene", + "gene_id": "bandits_quirk_lib:gene.dead_cells", + "min_quality": 0 + } + ] + } + }, + "ender_gene": { + "type": "palladium:repeating_animation_timer", + "icon": "minecraft:chorus_fruit", + "title": "Ender Gene", + "hidden_in_bar": false, + "description": "You are able to use ender energy to teleport! But you cant control it.", + "start_value": 0, + "max_value": 10, + "conditions": { + "enabling": [ + { + "type": "palladium:activation", + "cooldown": 60, + "ticks": 9, + "key_type": "key_bind", + "needs_empty_hand": false, + "allow_scrolling_when_crouching": true + } + ], + "unlocking": [ + { + "type": "bandits_quirk_lib:genome_has_gene", + "gene_id": "bandits_quirk_lib:gene.ender_gene", + "min_quality": 0 + } + ] + } + }, + "ender gene render layer": { + "type": "palladium:render_layer", + "render_layer": "bql:ender_gene_overlay" + }, + "ender gene hide overlay": { + "type": "palladium:hide_body_part", + "hidden": true, + "hidden_in_bar": true, + "list_index": -1, + "body_parts": [ + "head_overlay", + "chest_overlay", + "right_arm_overlay", + "left_arm_overlay", + "right_leg_overlay", + "left_leg_overlay" + ], + "affects_first_person": true, + "conditions": { + "enabling": [ + { + "type": "palladium:ability_enabled", + "power": "null", + "ability": "ender_gene" + } + ] + } + }, + "ender gene tp backend": { + "type": "bandits_quirk_lib:ender_gene_tp", + "hidden": true, + "hidden_in_bar": true, + "conditions": { + "enabling": [ + { + "type": "palladium:ability_integer_property", + "power": "null", + "ability": "ender_gene", + "property": "value", + "min": 9, + "max": 10 + } + ] + } } } } \ No newline at end of file diff --git a/common/src/main/resources/data/bql/palladium/powers/phantom_immunity.json b/common/src/main/resources/data/bql/palladium/powers/phantom_immunity.json deleted file mode 100644 index b4929451..00000000 --- a/common/src/main/resources/data/bql/palladium/powers/phantom_immunity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "Phantom Immunity", - "background": "minecraft:textures/block/white_wool.png", - "icon": "minecraft:phantom_membrane", - "gui_display_type": "tree", - "abilities": { - "Gene Ability": { - "title" : "Passive - Phantom Immunity", - "icon" : "minecraft:phantom_membrane", - "type": "palladium:dummy", - "description": "Phantoms cannot spawn on you", - "hidden": false, - "hidden_in_bar": true, - "gui_position": [0,0] - } - } -} \ No newline at end of file diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java index a2df605f..8cd7d90b 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java @@ -32,7 +32,6 @@ public void firstTick(LivingEntity entity, AbilityInstance entry, IPowerHolder h if (!(entity instanceof ServerPlayer player)) return; if (!(player.level() instanceof ServerLevel level)) return; - long ticks = level.getGameTime(); long lastUseTick = player.getPersistentData().getLong("lastUseTimeAdrenaline"); diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/DetroitSmashAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/DetroitSmashAbility.java index 8b0f7cc2..2ec619a5 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/DetroitSmashAbility.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/DetroitSmashAbility.java @@ -37,200 +37,168 @@ import static com.github.b4ndithelps.forge.systems.PowerStockHelper.sendPlayerPercentageMessage; public class DetroitSmashAbility extends Ability { - // Configurable properties - public static final PalladiumProperty MAX_CHARGE_TICKS = new IntegerProperty("max_charge_ticks").configurable("Maximum charge time in ticks"); - public static final PalladiumProperty MAX_DISTANCE = new FloatProperty("max_distance").configurable("Maximum ray distance"); - public static final PalladiumProperty BASE_DAMAGE = new FloatProperty("base_damage").configurable("Base damage for the attack"); - public static final PalladiumProperty BASE_KNOCKBACK = new FloatProperty("base_knockback").configurable("Base knockback for the attack"); - public static final PalladiumProperty BASE_RADIUS = new FloatProperty("base_radius").configurable("Base explosion radius"); - public static final PalladiumProperty MAX_POWER = new FloatProperty("max_power").configurable("Power value considered 100% scaling (e.g., 500000)"); - public static final PalladiumProperty MAX_DAMAGE = new FloatProperty("max_damage").configurable("Maximum damage when power used is 100%"); - public static final PalladiumProperty MAX_KNOCKBACK = new FloatProperty("max_knockback").configurable("Maximum knockback when power used is 100%"); - public static final PalladiumProperty MAX_RADIUS = new FloatProperty("max_radius").configurable("Maximum AoE radius when power used is 100%"); - public static final PalladiumProperty EXPLOSION_POWER_MAX = new FloatProperty("explosion_power_max").configurable("Max explosion strength at full power"); - public static final PalladiumProperty EXPLOSION_SPACING_MIN = new FloatProperty("explosion_spacing_min").configurable("Minimum spacing between explosions at high power"); - public static final PalladiumProperty EXPLOSION_COUNT_MAX = new IntegerProperty("explosion_count_max").configurable("Maximum number of explosions along the path"); - - // Unique properties for tracking state - public static final PalladiumProperty CHARGE_TICKS; - - public DetroitSmashAbility() { - super(); - this.withProperty(MAX_CHARGE_TICKS, 100) - .withProperty(MAX_DISTANCE, 30.0f) - .withProperty(BASE_DAMAGE, 5.0f) - .withProperty(BASE_KNOCKBACK, 1.0f) - .withProperty(BASE_RADIUS, 2.0f) - // Linear scaling maxima for damage/knockback/radius and the power cap - .withProperty(MAX_POWER, 500000.0f) - .withProperty(MAX_DAMAGE, 100.0f) - .withProperty(MAX_KNOCKBACK, 5.0f) - .withProperty(MAX_RADIUS, 10.0f) - // Explosion tuning properties - .withProperty(EXPLOSION_POWER_MAX, 8.0f) - .withProperty(EXPLOSION_SPACING_MIN, 2.5f) - .withProperty(EXPLOSION_COUNT_MAX, 36); - } + // Configurable properties + public static final PalladiumProperty MAX_CHARGE_TICKS = new IntegerProperty("max_charge_ticks").configurable("Maximum charge time in ticks"); + public static final PalladiumProperty MAX_DISTANCE = new FloatProperty("max_distance").configurable("Maximum ray distance"); + public static final PalladiumProperty BASE_DAMAGE = new FloatProperty("base_damage").configurable("Base damage for the attack"); + public static final PalladiumProperty BASE_KNOCKBACK = new FloatProperty("base_knockback").configurable("Base knockback for the attack"); + public static final PalladiumProperty BASE_RADIUS = new FloatProperty("base_radius").configurable("Base explosion radius"); + public static final PalladiumProperty MAX_POWER = new FloatProperty("max_power").configurable("Power value considered 100% scaling (e.g., 500000)"); + public static final PalladiumProperty MAX_DAMAGE = new FloatProperty("max_damage").configurable("Maximum damage when power used is 100%"); + public static final PalladiumProperty MAX_KNOCKBACK = new FloatProperty("max_knockback").configurable("Maximum knockback when power used is 100%"); + public static final PalladiumProperty MAX_RADIUS = new FloatProperty("max_radius").configurable("Maximum AoE radius when power used is 100%"); + public static final PalladiumProperty EXPLOSION_POWER_MAX = new FloatProperty("explosion_power_max").configurable("Max explosion strength at full power"); + public static final PalladiumProperty EXPLOSION_SPACING_MIN = new FloatProperty("explosion_spacing_min").configurable("Minimum spacing between explosions at high power"); + public static final PalladiumProperty EXPLOSION_COUNT_MAX = new IntegerProperty("explosion_count_max").configurable("Maximum number of explosions along the path"); + + // Unique properties for tracking state + public static final PalladiumProperty CHARGE_TICKS; + + public DetroitSmashAbility() { + super(); + this.withProperty(MAX_CHARGE_TICKS, 100) + .withProperty(MAX_DISTANCE, 30.0f) + .withProperty(BASE_DAMAGE, 5.0f) + .withProperty(BASE_KNOCKBACK, 1.0f) + .withProperty(BASE_RADIUS, 2.0f) + // Linear scaling maxima for damage/knockback/radius and the power cap + .withProperty(MAX_POWER, 500000.0f) + .withProperty(MAX_DAMAGE, 100.0f) + .withProperty(MAX_KNOCKBACK, 5.0f) + .withProperty(MAX_RADIUS, 10.0f) + // Explosion tuning properties + .withProperty(EXPLOSION_POWER_MAX, 8.0f) + .withProperty(EXPLOSION_SPACING_MIN, 2.5f) + .withProperty(EXPLOSION_COUNT_MAX, 36); + } - public void registerUniqueProperties(PropertyManager manager) { - manager.register(CHARGE_TICKS, 0); - } + public void registerUniqueProperties(PropertyManager manager) { + manager.register(CHARGE_TICKS, 0); + } - @Override - public void firstTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { - if (enabled && entity instanceof ServerPlayer player) { - // Initialize charge - entry.setUniqueProperty(CHARGE_TICKS, 0); - BodyStatusHelper.setCustomFloat(player, "left_arm", "pstock_smash_charge", 0.0f); - - if (entity.level() instanceof ServerLevel serverLevel) { - // Play charging start sound - serverLevel.playSound(null, player.blockPosition(), - SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.3f, 1.2f); - } + @Override + public void firstTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { + if (enabled && entity instanceof ServerPlayer player) { + // Initialize charge + entry.setUniqueProperty(CHARGE_TICKS, 0); + BodyStatusHelper.setCustomFloat(player, "left_arm", "pstock_smash_charge", 0.0f); + + if (entity.level() instanceof ServerLevel serverLevel) { + // Play charging start sound + serverLevel.playSound(null, player.blockPosition(), + SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.3f, 1.2f); } } + } - @Override - public void tick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { - if (!(entity instanceof ServerPlayer player)) return; - if (!(entity.level() instanceof ServerLevel serverLevel)) return; + @Override + public void tick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { + if (!(entity instanceof ServerPlayer player)) return; + if (!(entity.level() instanceof ServerLevel serverLevel)) return; - if (enabled) { - handleChargingPhase(player, serverLevel, entry); - } + if (enabled) { + handleChargingPhase(player, serverLevel, entry); } + } - @Override - public void lastTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { - if (entity instanceof ServerPlayer player && entity.level() instanceof ServerLevel serverLevel) { - int chargeTicks = entry.getProperty(CHARGE_TICKS); + @Override + public void lastTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { + if (entity instanceof ServerPlayer player && entity.level() instanceof ServerLevel serverLevel) { + int chargeTicks = entry.getProperty(CHARGE_TICKS); - // Prevent bug where it forces a smash on reload - if (chargeTicks == 0) return; + // Prevent bug where it forces a smash on reload + if (chargeTicks == 0) return; - // Trigger arm swing animation - player.swing(InteractionHand.MAIN_HAND, true); + // Trigger arm swing animation + player.swing(InteractionHand.MAIN_HAND, true); - // Execute the Detroit Smash - executeDetroitSmash(player, serverLevel, entry, chargeTicks); + // Execute the Detroit Smash + executeDetroitSmash(player, serverLevel, entry, chargeTicks); - // Reset charge - entry.setUniqueProperty(CHARGE_TICKS, 0); - BodyStatusHelper.setCustomFloat(player, "left_arm", "pstock_smash_charge", 0.0f); - } + // Reset charge + entry.setUniqueProperty(CHARGE_TICKS, 0); + BodyStatusHelper.setCustomFloat(player, "left_arm", "pstock_smash_charge", 0.0f); } + } - private void handleChargingPhase(ServerPlayer player, ServerLevel level, AbilityInstance entry) { - int maxChargeTicks = entry.getProperty(MAX_CHARGE_TICKS); - int currentChargeTicks = entry.getProperty(CHARGE_TICKS); + private void handleChargingPhase(ServerPlayer player, ServerLevel level, AbilityInstance entry) { + int maxChargeTicks = entry.getProperty(MAX_CHARGE_TICKS); + int currentChargeTicks = entry.getProperty(CHARGE_TICKS); - // Increment charge if not at max - if (currentChargeTicks < maxChargeTicks) { - currentChargeTicks++; - entry.setUniqueProperty(CHARGE_TICKS, currentChargeTicks); - BodyStatusHelper.setCustomFloat(player, "left_arm", "pstock_smash_charge", currentChargeTicks); - } + // Increment charge if not at max + if (currentChargeTicks < maxChargeTicks) { + currentChargeTicks++; + entry.setUniqueProperty(CHARGE_TICKS, currentChargeTicks); + BodyStatusHelper.setCustomFloat(player, "left_arm", "pstock_smash_charge", currentChargeTicks); + } - // Calculate charge percentage - float chargePercent = Math.min((currentChargeTicks / (float) maxChargeTicks) * 100.0f, 100.0f); - float storedPower = PowerStockHelper.getStoredPower(player); - float powerUsed = storedPower * chargePercent / 100.0f; + // Calculate charge percentage + float chargePercent = Math.min((currentChargeTicks / (float)maxChargeTicks) * 100.0f, 100.0f); + float storedPower = PowerStockHelper.getStoredPower(player); + float powerUsed = storedPower * chargePercent / 100.0f; - // Show charge message every 5 ticks - if (currentChargeTicks % 5 == 0) { - sendPlayerPercentageMessage(player, powerUsed, chargePercent, "Charging Smash"); - } + // Show charge message every 5 ticks + if (currentChargeTicks % 5 == 0) { + sendPlayerPercentageMessage(player, powerUsed, chargePercent, "Charging Smash"); + } - // Charge-based particle effects along player's look direction - if (currentChargeTicks % 5 == 0) { - addChargingParticles(player, level, chargePercent); - } + // Charge-based particle effects along player's look direction + if (currentChargeTicks % 5 == 0) { + addChargingParticles(player, level, chargePercent); + } - // Sound effects at certain charge levels - if (currentChargeTicks == 20) { - level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.3f, 1.2f); - } else if (currentChargeTicks == 50) { - level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.5f, 1.5f); - } else if (currentChargeTicks == 80) { - level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.7f, 1.8f); - } else if (currentChargeTicks == 99) { - level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 1.0f, 2.0f); - } + // Sound effects at certain charge levels + if (currentChargeTicks == 20) { + level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.3f, 1.2f); + } else if (currentChargeTicks == 50) { + level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.5f, 1.5f); + } else if (currentChargeTicks == 80) { + level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 0.7f, 1.8f); + } else if (currentChargeTicks == 99) { + level.playSound(null, player.blockPosition(), SoundEvents.BEACON_POWER_SELECT, SoundSource.PLAYERS, 1.0f, 2.0f); } + } - private void addChargingParticles(ServerPlayer player, ServerLevel level, float chargePercent) { - Vec3 lookDirection = player.getLookAngle(); - float storedPower = PowerStockHelper.getStoredPower(player); + private void addChargingParticles(ServerPlayer player, ServerLevel level, float chargePercent) { + Vec3 lookDirection = player.getLookAngle(); + float storedPower = PowerStockHelper.getStoredPower(player); - // Scale particle effects based on both charge and stored power (for 500k max) - float powerFactor = storedPower > 0 ? Math.min(1.0f, (float) Math.sqrt(storedPower) / 707.0f) : 0.0f; + // Scale particle effects based on both charge and stored power (for 500k max) + float powerFactor = storedPower > 0 ? Math.min(1.0f, (float)Math.sqrt(storedPower) / 707.0f) : 0.0f; - // Stronger particle scaling for a flashy feel - int baseParticleCount = (int) Math.ceil(chargePercent / 6.0); - int powerParticleCount = (int) Math.ceil(powerFactor * powerFactor * 40); - int particleCount = Math.max(0, baseParticleCount + powerParticleCount); + // Stronger particle scaling for a flashy feel + int baseParticleCount = (int) Math.ceil(chargePercent / 6.0); + int powerParticleCount = (int) Math.ceil(powerFactor * powerFactor * 40); + int particleCount = Math.max(0, baseParticleCount + powerParticleCount); - // Add minimal "dud" particles for very low power levels - if (particleCount == 0 && chargePercent > 30 && storedPower >= 0) { - particleCount = 1; // At least one tiny particle to show something happened - } + // Add minimal "dud" particles for very low power levels + if (particleCount == 0 && chargePercent > 30 && storedPower >= 0) { + particleCount = 1; // At least one tiny particle to show something happened + } - // Preview distance also scales with power - double baseDistance = 1 + (chargePercent / 20.0); - double previewDistance = Math.min(20, baseDistance * (0.2f + powerFactor * 1.2f)); - - // Show charging preview along look direction - for (double i = 1; i < previewDistance; i += 0.8) { - double px = player.getX() + (i * lookDirection.x); - double py = player.getY() + (i * lookDirection.y) + player.getEyeHeight(); - double pz = player.getZ() + (i * lookDirection.z); - - // Only show particles if we actually have particles to show - if (particleCount > 0) { - // Scale particle intensity based on power - int smokeParticles = Math.max(0, particleCount / 2); - int critParticles = Math.max(0, particleCount / 3); - - if (chargePercent < 30) { - if (smokeParticles > 0) { - // For very low power, show tiny "dud" particles - if (powerFactor < 0.1f) { - level.sendParticles(ParticleTypes.SMOKE, px, py, pz, 1, 0.02, 0.02, 0.02, 0.001); - } else { - level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.08, 0.08, 0.08, 0.01); - } - } - } else if (chargePercent < 60) { - if (smokeParticles > 0) { - level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.15, 0.08, 0.15, 0.02); - } - if (critParticles > 0 && powerFactor > 0.15f) { - level.sendParticles(ParticleTypes.CRIT, px, py, pz, critParticles, 0.08, 0.08, 0.08, 0.008); - } - } else if (chargePercent < 80) { - if (smokeParticles > 0) { - level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.2, 0.15, 0.2, 0.03); - } - if (critParticles > 0 && powerFactor > 0.15f) { - level.sendParticles(ParticleTypes.CRIT, px, py, pz, critParticles, 0.12, 0.08, 0.12, 0.012); - } - // Only show explosion particles if we have significant power - if ((int) chargePercent % 10 == 0 && powerFactor > 0.35f) { - level.sendParticles(ParticleTypes.EXPLOSION, px, py, pz, 2, 0.08, 0.08, 0.08, 0.01); - } - } else { - if (smokeParticles > 0) { - level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.25, 0.2, 0.25, 0.04); - } - if (critParticles > 0 && powerFactor > 0.15f) { - level.sendParticles(ParticleTypes.CRIT, px, py, pz, critParticles, 0.18, 0.12, 0.18, 0.02); - } - // Enhanced effects only for higher power levels - if ((int) chargePercent % 10 == 0 && powerFactor > 0.45f) { - level.sendParticles(ParticleTypes.EXPLOSION, px, py, pz, 2, 0.12, 0.08, 0.12, 0.02); - if (powerFactor > 0.7f) - level.sendParticles(ParticleTypes.LARGE_SMOKE, px, py, pz, 3, 0.08, 0.08, 0.08, 0.008); + // Preview distance also scales with power + double baseDistance = 1 + (chargePercent / 20.0); + double previewDistance = Math.min(20, baseDistance * (0.2f + powerFactor * 1.2f)); + + // Show charging preview along look direction + for (double i = 1; i < previewDistance; i += 0.8) { + double px = player.getX() + (i * lookDirection.x); + double py = player.getY() + (i * lookDirection.y) + player.getEyeHeight(); + double pz = player.getZ() + (i * lookDirection.z); + + // Only show particles if we actually have particles to show + if (particleCount > 0) { + // Scale particle intensity based on power + int smokeParticles = Math.max(0, particleCount/2); + int critParticles = Math.max(0, particleCount/3); + + if (chargePercent < 30) { + if (smokeParticles > 0) { + // For very low power, show tiny "dud" particles + if (powerFactor < 0.1f) { + level.sendParticles(ParticleTypes.SMOKE, px, py, pz, 1, 0.02, 0.02, 0.02, 0.001); + } else { + level.sendParticles(ParticleTypes.SMOKE, px, py, pz, smokeParticles, 0.08, 0.08, 0.08, 0.01); } } } else if (chargePercent < 60) { @@ -277,14 +245,14 @@ private void executeDetroitSmash(ServerPlayer player, ServerLevel level, Ability float maxDamage = Math.max(baseDamage, entry.getProperty(MAX_DAMAGE)); float maxKnockback = Math.max(baseKnockback, entry.getProperty(MAX_KNOCKBACK)); float maxRadius = Math.max(baseRadius, entry.getProperty(MAX_RADIUS)); - + float chargePercent = Math.min((chargeTicks / (float)maxChargeTicks) * 100.0f, 100.0f); float storedPower = PowerStockHelper.getStoredPower(player); float powerUsed = storedPower * chargePercent / 100.0f; - + // Linear power ratio based on configured max power float powerRatio = Math.max(0.0f, Math.min(1.0f, powerUsed / maxPower)); - + // Final values map linearly to configured maxima using the power ratio float finalDamage = Math.max(0.0f, maxDamage * powerRatio); float finalKnockback = Math.max(0.0f, maxKnockback * powerRatio); @@ -296,7 +264,7 @@ private void executeDetroitSmash(ServerPlayer player, ServerLevel level, Ability if (powerRatio > 0.1f) { float soundVolume = Math.max(0.1f, Math.min(3.0f, powerRatio * 3.0f)); level.playSound(null, player.blockPosition(), SoundEvents.GENERIC_EXPLODE, SoundSource.PLAYERS, soundVolume, 0.6f); - + if (powerRatio > 0.4f) { level.playSound(null, player.blockPosition(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.PLAYERS, soundVolume * 0.8f, 1.0f); } @@ -366,60 +334,60 @@ private void executeDetroitSmash(ServerPlayer player, ServerLevel level, Ability PowerStockHelper.applyLimbDamage(player, powerUsed, "arm"); // Show completion message - player.sendSystemMessage(Component.literal(String.format("Detroit Smash executed! %.0f damage, %.0f radius, %.0f range", - finalDamage, finalRadius, actualDistance)).withStyle(ChatFormatting.GREEN)); + player.sendSystemMessage(Component.literal(String.format("Detroit Smash executed! %.0f damage, %.0f radius, %.0f range", + finalDamage, finalRadius, actualDistance)).withStyle(ChatFormatting.GREEN)); } private void addParticleTrail(ServerLevel level, Vec3 startPos, Vec3 endPos, float chargePercent) { - Vec3 direction = endPos.subtract(startPos).normalize(); - double distance = startPos.distanceTo(endPos); - - // Get power scaling for trail effects - denser at shorter ranges - float powerFactor = Math.max(0.0f, Math.min(1.0f, (float)(distance - 1.0) / 35.0f)); - - // Stronger trail particles - int trailParticleCount = Math.max(0, Math.round((chargePercent / 15.0f) * (powerFactor * powerFactor * 2.0f + 0.5f))); - double stepSize = Math.max(0.35, 1.2 - powerFactor * 1.0); - - // Add minimal "dud" trail particles for very low power but decent charge - if (trailParticleCount == 0 && chargePercent > 60 && distance > 3) { - trailParticleCount = 1; // At least show something traveled - } - - for (double i = 1; i < distance; i += stepSize) { - Vec3 particlePos = startPos.add(direction.scale(i)); - - if (trailParticleCount > 0) { - // Scale particle spread and intensity with power - double spread = Math.max(0.04, 0.25 * powerFactor); - double speed = Math.max(0.01, 0.12 * powerFactor); - - // For very low power (dud particles), use minimal effects - if (powerFactor < 0.1f) { - level.sendParticles(ParticleTypes.SMOKE, particlePos.x, particlePos.y, particlePos.z, - 1, 0.03, 0.03, 0.03, 0.006); // Tiny puff - } else { - level.sendParticles(ParticleTypes.CRIT, particlePos.x, particlePos.y, particlePos.z, - trailParticleCount, spread, spread, spread, speed); - level.sendParticles(ParticleTypes.SMOKE, particlePos.x, particlePos.y, particlePos.z, - Math.max(1, trailParticleCount * 2), spread * 0.9, spread * 0.9, spread * 0.9, speed * 0.6); - - // Only add explosion particles if we have significant power - if (powerFactor > 0.45f && trailParticleCount > 1) { - level.sendParticles(ParticleTypes.EXPLOSION, particlePos.x, particlePos.y, particlePos.z, - Math.max(0, trailParticleCount/2), spread * 0.6, spread * 0.6, spread * 0.6, speed * 0.35); - } - } - } - } + Vec3 direction = endPos.subtract(startPos).normalize(); + double distance = startPos.distanceTo(endPos); + + // Get power scaling for trail effects - denser at shorter ranges + float powerFactor = Math.max(0.0f, Math.min(1.0f, (float)(distance - 1.0) / 35.0f)); + + // Stronger trail particles + int trailParticleCount = Math.max(0, Math.round((chargePercent / 15.0f) * (powerFactor * powerFactor * 2.0f + 0.5f))); + double stepSize = Math.max(0.35, 1.2 - powerFactor * 1.0); + + // Add minimal "dud" trail particles for very low power but decent charge + if (trailParticleCount == 0 && chargePercent > 60 && distance > 3) { + trailParticleCount = 1; // At least show something traveled + } + + for (double i = 1; i < distance; i += stepSize) { + Vec3 particlePos = startPos.add(direction.scale(i)); + + if (trailParticleCount > 0) { + // Scale particle spread and intensity with power + double spread = Math.max(0.04, 0.25 * powerFactor); + double speed = Math.max(0.01, 0.12 * powerFactor); + + // For very low power (dud particles), use minimal effects + if (powerFactor < 0.1f) { + level.sendParticles(ParticleTypes.SMOKE, particlePos.x, particlePos.y, particlePos.z, + 1, 0.03, 0.03, 0.03, 0.006); // Tiny puff + } else { + level.sendParticles(ParticleTypes.CRIT, particlePos.x, particlePos.y, particlePos.z, + trailParticleCount, spread, spread, spread, speed); + level.sendParticles(ParticleTypes.SMOKE, particlePos.x, particlePos.y, particlePos.z, + Math.max(1, trailParticleCount * 2), spread * 0.9, spread * 0.9, spread * 0.9, speed * 0.6); + + // Only add explosion particles if we have significant power + if (powerFactor > 0.45f && trailParticleCount > 1) { + level.sendParticles(ParticleTypes.EXPLOSION, particlePos.x, particlePos.y, particlePos.z, + Math.max(0, trailParticleCount/2), spread * 0.6, spread * 0.6, spread * 0.6, speed * 0.35); + } + } + } + } } - private void createExplosionChain(ServerLevel level, ServerPlayer player, Vec3 startPos, Vec3 endPos, - float finalRadius, float powerUsed, float chargePercent, - float configuredMaxPower, float explosionPowerMax, float explosionSpacingMin, int explosionCountMax) { + private void createExplosionChain(ServerLevel level, ServerPlayer player, Vec3 startPos, Vec3 endPos, + float finalRadius, float powerUsed, float chargePercent, + float configuredMaxPower, float explosionPowerMax, float explosionSpacingMin, int explosionCountMax) { Vec3 direction = endPos.subtract(startPos).normalize(); double distance = startPos.distanceTo(endPos); - + // Scale explosions using the same linear power ratio configuration float powerRatio = Math.max(0.0f, Math.min(1.0f, powerUsed / configuredMaxPower)); float explosionPower = Math.min(explosionPowerMax, 1.5f + (finalRadius * 0.4f * powerRatio)); @@ -431,15 +399,15 @@ private void createExplosionChain(ServerLevel level, ServerPlayer player, Vec3 s for (int i = 0; i <= numExplosions; i++) { double explosionDistance = Math.min(i * explosionSpacing, distance); Vec3 explosionPos = startPos.add(direction.scale(explosionDistance)); - + // Vary explosion power slightly along the path (stronger in middle) double distanceRatio = explosionDistance / distance; double powerMultiplier = 0.6 + (0.4 * Math.sin(distanceRatio * Math.PI)); float currentExplosionPower = Math.max(0.5f, explosionPower * (float)powerMultiplier); - + // Create explosion that damages entities - level.explode(player, explosionPos.x, explosionPos.y, explosionPos.z, - currentExplosionPower, Level.ExplosionInteraction.BLOCK); + level.explode(player, explosionPos.x, explosionPos.y, explosionPos.z, + currentExplosionPower, Level.ExplosionInteraction.BLOCK); } player.sendSystemMessage(Component.literal(String.format("High-power Detroit Smash with %d explosions!", numExplosions)) @@ -469,334 +437,131 @@ private LivingEntity findTargetEntity(ServerPlayer player, float range) { } } - private void executeDetroitSmash(ServerPlayer player, ServerLevel level, AbilityInstance entry, int chargeTicks) { - int maxChargeTicks = entry.getProperty(MAX_CHARGE_TICKS); - float maxDistance = entry.getProperty(MAX_DISTANCE); - float baseDamage = entry.getProperty(BASE_DAMAGE); - float baseKnockback = entry.getProperty(BASE_KNOCKBACK); - float baseRadius = entry.getProperty(BASE_RADIUS); - float maxPower = Math.max(1.0f, entry.getProperty(MAX_POWER)); - float maxDamage = Math.max(baseDamage, entry.getProperty(MAX_DAMAGE)); - float maxKnockback = Math.max(baseKnockback, entry.getProperty(MAX_KNOCKBACK)); - float maxRadius = Math.max(baseRadius, entry.getProperty(MAX_RADIUS)); - - float chargePercent = Math.min((chargeTicks / (float) maxChargeTicks) * 100.0f, 100.0f); - float storedPower = PowerStockHelper.getStoredPower(player); - float powerUsed = storedPower * chargePercent / 100.0f; - - // Linear power ratio based on configured max power - float powerRatio = Math.max(0.0f, Math.min(1.0f, powerUsed / maxPower)); - - // Final values map linearly to configured maxima using the power ratio - float finalDamage = Math.max(0.0f, maxDamage * powerRatio); - float finalKnockback = Math.max(0.0f, maxKnockback * powerRatio); - float finalRadius = Math.max(0.0f, maxRadius * powerRatio); - float finalDistance = Math.max(1.0f, entry.getProperty(MAX_DISTANCE) * powerRatio); - - // Execute the ray smash - sounds and initial particles (scale with power) - // Only play sounds if we have decent power, start very quiet - if (powerRatio > 0.1f) { - float soundVolume = Math.max(0.1f, Math.min(3.0f, powerRatio * 3.0f)); - level.playSound(null, player.blockPosition(), SoundEvents.GENERIC_EXPLODE, SoundSource.PLAYERS, soundVolume, 0.6f); - - if (powerRatio > 0.4f) { - level.playSound(null, player.blockPosition(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.PLAYERS, soundVolume * 0.8f, 1.0f); - } - if (powerRatio > 0.6f) { - level.playSound(null, player.blockPosition(), SoundEvents.FIREWORK_ROCKET_BLAST, SoundSource.PLAYERS, soundVolume * 0.6f, 0.8f); - } - } - - // Ray-based targeting system - Vec3 eyePosition = player.getEyePosition(); - Vec3 lookDirection = player.getLookAngle(); - Vec3 endPosition = eyePosition.add(lookDirection.scale(finalDistance)); - - // Perform ray trace - BlockHitResult hitResult = level.clip(new ClipContext(eyePosition, endPosition, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, player)); - - Vec3 impactPoint = hitResult.getType() == HitResult.Type.BLOCK ? hitResult.getLocation() : endPosition; - double actualDistance = eyePosition.distanceTo(impactPoint); - - // Give the user temporary resistance to prevent self-death from explosions - player.addEffect(new MobEffectInstance(MobEffects.DAMAGE_RESISTANCE, 200, 4, true, false)); - - // Spawn immediate swing visuals near the player - Vec3 handPos = eyePosition.add(lookDirection.scale(1.5)); - level.sendParticles(ParticleTypes.SWEEP_ATTACK, handPos.x, handPos.y - 0.2, handPos.z, 6, 0.2, 0.2, 0.2, 0.0); - level.sendParticles(ParticleTypes.CRIT, handPos.x, handPos.y, handPos.z, 12, 0.25, 0.25, 0.25, 0.05); - level.sendParticles(ParticleTypes.CLOUD, handPos.x, handPos.y, handPos.z, 8, 0.25, 0.25, 0.25, 0.05); - - // Direct entity hit check along the ray for a satisfying punch - LivingEntity directTarget = findTargetEntity(player, (float) finalDistance); - if (directTarget != null && directTarget != player && directTarget.isAlive()) { - impactPoint = directTarget.position(); - // Perform vanilla attack for animations/damage hooks - player.attack(directTarget); - // Add heavy extra damage scaling with power - float bonus = finalDamage * 0.75f; - directTarget.hurt(player.damageSources().playerAttack(player), bonus); - // Strong knockback on direct hit - Vec3 kbDir = directTarget.position().subtract(player.position()).normalize().add(0, 0.2, 0); - directTarget.setDeltaMovement(directTarget.getDeltaMovement().add(kbDir.scale(finalKnockback * 0.6))); - if (directTarget instanceof ServerPlayer sp) { - sp.connection.send(new ClientboundSetEntityMotionPacket(sp)); - } - } - - // Show particle trail along the ray path - addParticleTrail(level, eyePosition, impactPoint, chargePercent); - - // Create TNT explosions for mid to high power levels - boolean shouldCreateExplosions = storedPower >= 50000 && chargePercent >= 50 && powerRatio > 0.3f; - if (shouldCreateExplosions) { - float configuredMaxPower = Math.max(1.0f, entry.getProperty(MAX_POWER)); - float explosionPowerMax = Math.max(1.0f, entry.getProperty(EXPLOSION_POWER_MAX)); - float explosionSpacingMin = Math.max(0.5f, entry.getProperty(EXPLOSION_SPACING_MIN)); - int explosionCountMax = Math.max(1, entry.getProperty(EXPLOSION_COUNT_MAX)); - createExplosionChain(level, player, eyePosition, impactPoint, finalRadius, powerUsed, chargePercent, - configuredMaxPower, explosionPowerMax, explosionSpacingMin, explosionCountMax); - } - - // Enhanced visual effects at impact point - addImpactEffects(level, impactPoint, finalRadius, chargePercent, powerRatio); - - // Area of Effect Damage and Knockback - applyAreaDamage(level, player, impactPoint, finalRadius, finalDamage, finalKnockback); + return best; + } - // Apply overuse damage to arms - PowerStockHelper.applyLimbDamage(player, powerUsed, "arm"); + private void addImpactEffects(ServerLevel level, Vec3 impactPoint, float finalRadius, float chargePercent, float powerScalingFactor) { + // Scale impact effects based on power and charge + float effectIntensity = Math.max(0.0f, powerScalingFactor * powerScalingFactor * (chargePercent / 90.0f)); - // Show completion message - player.sendSystemMessage(Component.literal(String.format("Detroit Smash executed! %.0f damage, %.0f radius, %.0f range", - finalDamage, finalRadius, actualDistance)).withStyle(ChatFormatting.GREEN)); + // Only create major effects if we have very significant power + if (powerScalingFactor > 0.6f) { + level.sendParticles(ParticleTypes.EXPLOSION_EMITTER, + impactPoint.x, impactPoint.y, impactPoint.z, 1, 0, 0, 0, 0); } - private void addParticleTrail(ServerLevel level, Vec3 startPos, Vec3 endPos, float chargePercent) { - Vec3 direction = endPos.subtract(startPos).normalize(); - double distance = startPos.distanceTo(endPos); - - // Get power scaling for trail effects - denser at shorter ranges - float powerFactor = Math.max(0.0f, Math.min(1.0f, (float) (distance - 1.0) / 35.0f)); + // Stronger particle counts + int critCount = Math.max(0, Math.round((10 + chargePercent) * effectIntensity)); + int smokeCount = Math.max(0, Math.round((18 + chargePercent) * effectIntensity)); + int explosionCount = Math.max(0, Math.round((4 + chargePercent/5) * effectIntensity)); - // Stronger trail particles - int trailParticleCount = Math.max(0, Math.round((chargePercent / 15.0f) * (powerFactor * powerFactor * 2.0f + 0.5f))); - double stepSize = Math.max(0.35, 1.2 - powerFactor * 1.0); - - // Add minimal "dud" trail particles for very low power but decent charge - if (trailParticleCount == 0 && chargePercent > 60 && distance > 3) { - trailParticleCount = 1; // At least show something traveled - } - - for (double i = 1; i < distance; i += stepSize) { - Vec3 particlePos = startPos.add(direction.scale(i)); - - if (trailParticleCount > 0) { - // Scale particle spread and intensity with power - double spread = Math.max(0.04, 0.25 * powerFactor); - double speed = Math.max(0.01, 0.12 * powerFactor); - - // For very low power (dud particles), use minimal effects - if (powerFactor < 0.1f) { - level.sendParticles(ParticleTypes.SMOKE, particlePos.x, particlePos.y, particlePos.z, - 1, 0.03, 0.03, 0.03, 0.006); // Tiny puff - } else { - level.sendParticles(ParticleTypes.CRIT, particlePos.x, particlePos.y, particlePos.z, - trailParticleCount, spread, spread, spread, speed); - level.sendParticles(ParticleTypes.SMOKE, particlePos.x, particlePos.y, particlePos.z, - Math.max(1, trailParticleCount * 2), spread * 0.9, spread * 0.9, spread * 0.9, speed * 0.6); - - // Only add explosion particles if we have significant power - if (powerFactor > 0.45f && trailParticleCount > 1) { - level.sendParticles(ParticleTypes.EXPLOSION, particlePos.x, particlePos.y, particlePos.z, - Math.max(0, trailParticleCount / 2), spread * 0.6, spread * 0.6, spread * 0.6, speed * 0.35); - } - } - } + // Add minimal "dud" impact particles for very low power levels + if (critCount == 0 && smokeCount == 0 && chargePercent > 20) { + smokeCount = 1; // At least a tiny puff of smoke for the impact + if (chargePercent > 50) { + critCount = 1; // Maybe one crit particle if they charged a bit } } - private void createExplosionChain(ServerLevel level, ServerPlayer player, Vec3 startPos, Vec3 endPos, - float finalRadius, float powerUsed, float chargePercent, - float configuredMaxPower, float explosionPowerMax, float explosionSpacingMin, int explosionCountMax) { - Vec3 direction = endPos.subtract(startPos).normalize(); - double distance = startPos.distanceTo(endPos); - - // Scale explosions using the same linear power ratio configuration - float powerRatio = Math.max(0.0f, Math.min(1.0f, powerUsed / configuredMaxPower)); - float explosionPower = Math.min(explosionPowerMax, 1.5f + (finalRadius * 0.4f * powerRatio)); - float explosionSpacing = Math.max(explosionSpacingMin, 10.0f - (chargePercent * 0.08f) - (powerRatio * 4.0f)); - int maxExplosions = Math.min(explosionCountMax, Math.round(10 + powerRatio * (explosionCountMax - 10))); - int numExplosions = Math.min(maxExplosions, (int) Math.ceil(distance / explosionSpacing)); - - // Create explosions along the ray path - for (int i = 0; i <= numExplosions; i++) { - double explosionDistance = Math.min(i * explosionSpacing, distance); - Vec3 explosionPos = startPos.add(direction.scale(explosionDistance)); - - // Vary explosion power slightly along the path (stronger in middle) - double distanceRatio = explosionDistance / distance; - double powerMultiplier = 0.6 + (0.4 * Math.sin(distanceRatio * Math.PI)); - float currentExplosionPower = Math.max(0.5f, explosionPower * (float) powerMultiplier); - - // Create explosion that damages entities - level.explode(player, explosionPos.x, explosionPos.y, explosionPos.z, - currentExplosionPower, Level.ExplosionInteraction.BLOCK); - } - - player.sendSystemMessage(Component.literal(String.format("High-power Detroit Smash with %d explosions!", numExplosions)) - .withStyle(ChatFormatting.RED)); + // Send particles - even minimal ones for dud effects + if (critCount > 0) { + // For dud effects, use very small radius and speed + float effectiveRadius = powerScalingFactor < 0.1f ? 0.1f : finalRadius; + level.sendParticles(ParticleTypes.CRIT, + impactPoint.x, impactPoint.y, impactPoint.z, + critCount, effectiveRadius, effectiveRadius, effectiveRadius, Math.max(0.005, 0.15 * powerScalingFactor)); } - - // Simple entity raycast to find the closest living entity along the player's look within range - private LivingEntity findTargetEntity(ServerPlayer player, float range) { - Vec3 eyePos = player.getEyePosition(); - Vec3 lookVec = player.getLookAngle(); - Vec3 endPos = eyePos.add(lookVec.scale(range)); - - LivingEntity best = null; - double closest = range; - - List entities = player.level().getEntitiesOfClass(LivingEntity.class, - new AABB(eyePos, endPos).inflate(1.0), - e -> e != player && e.isAlive()); - - for (LivingEntity e : entities) { - if (e.getBoundingBox().inflate(0.3).clip(eyePos, endPos).isPresent()) { - double dist = eyePos.distanceTo(e.position()); - if (dist < closest) { - closest = dist; - best = e; - } - } - } - - return best; + if (smokeCount > 0) { + // For dud effects, use very small radius and speed + float effectiveRadius = powerScalingFactor < 0.1f ? 0.15f : finalRadius * 1.5f; + level.sendParticles(ParticleTypes.SMOKE, + impactPoint.x, impactPoint.y, impactPoint.z, + smokeCount, effectiveRadius, effectiveRadius, effectiveRadius, Math.max(0.01, 0.2 * powerScalingFactor)); + } + if (explosionCount > 0) { + level.sendParticles(ParticleTypes.EXPLOSION, + impactPoint.x, impactPoint.y, impactPoint.z, + explosionCount, finalRadius * 0.8f, finalRadius * 0.8f, finalRadius * 0.8f, Math.max(0.005, 0.1 * powerScalingFactor)); } - private void addImpactEffects(ServerLevel level, Vec3 impactPoint, float finalRadius, float chargePercent, float powerScalingFactor) { - // Scale impact effects based on power and charge - float effectIntensity = Math.max(0.0f, powerScalingFactor * powerScalingFactor * (chargePercent / 90.0f)); - - // Only create major effects if we have very significant power - if (powerScalingFactor > 0.6f) { - level.sendParticles(ParticleTypes.EXPLOSION_EMITTER, - impactPoint.x, impactPoint.y, impactPoint.z, 1, 0, 0, 0, 0); - } - - // Stronger particle counts - int critCount = Math.max(0, Math.round((10 + chargePercent) * effectIntensity)); - int smokeCount = Math.max(0, Math.round((18 + chargePercent) * effectIntensity)); - int explosionCount = Math.max(0, Math.round((4 + chargePercent / 5) * effectIntensity)); - - // Add minimal "dud" impact particles for very low power levels - if (critCount == 0 && smokeCount == 0 && chargePercent > 20) { - smokeCount = 1; // At least a tiny puff of smoke for the impact - if (chargePercent > 50) { - critCount = 1; // Maybe one crit particle if they charged a bit - } - } - - // Send particles - even minimal ones for dud effects - if (critCount > 0) { - // For dud effects, use very small radius and speed - float effectiveRadius = powerScalingFactor < 0.1f ? 0.1f : finalRadius; - level.sendParticles(ParticleTypes.CRIT, - impactPoint.x, impactPoint.y, impactPoint.z, - critCount, effectiveRadius, effectiveRadius, effectiveRadius, Math.max(0.005, 0.15 * powerScalingFactor)); - } - if (smokeCount > 0) { - // For dud effects, use very small radius and speed - float effectiveRadius = powerScalingFactor < 0.1f ? 0.15f : finalRadius * 1.5f; - level.sendParticles(ParticleTypes.SMOKE, - impactPoint.x, impactPoint.y, impactPoint.z, - smokeCount, effectiveRadius, effectiveRadius, effectiveRadius, Math.max(0.01, 0.2 * powerScalingFactor)); - } - if (explosionCount > 0) { - level.sendParticles(ParticleTypes.EXPLOSION, - impactPoint.x, impactPoint.y, impactPoint.z, - explosionCount, finalRadius * 0.8f, finalRadius * 0.8f, finalRadius * 0.8f, Math.max(0.005, 0.1 * powerScalingFactor)); - } - - // Shockwave ring around impact for flair - if (powerScalingFactor > 0.3f) { - int rings = Math.max(1, (int) (powerScalingFactor * 3)); - for (int r = 1; r <= rings; r++) { - double ringRadius = finalRadius * (0.5 + 0.3 * r); - for (int a = 0; a < 24; a++) { - double theta = (Math.PI * 2 * a) / 24.0; - double rx = impactPoint.x + ringRadius * Math.cos(theta); - double rz = impactPoint.z + ringRadius * Math.sin(theta); - level.sendParticles(ParticleTypes.CLOUD, rx, impactPoint.y + 0.2, rz, 1, 0.02, 0.02, 0.02, 0.02); - } + // Shockwave ring around impact for flair + if (powerScalingFactor > 0.3f) { + int rings = Math.max(1, (int)(powerScalingFactor * 3)); + for (int r = 1; r <= rings; r++) { + double ringRadius = finalRadius * (0.5 + 0.3 * r); + for (int a = 0; a < 24; a++) { + double theta = (Math.PI * 2 * a) / 24.0; + double rx = impactPoint.x + ringRadius * Math.cos(theta); + double rz = impactPoint.z + ringRadius * Math.sin(theta); + level.sendParticles(ParticleTypes.CLOUD, rx, impactPoint.y + 0.2, rz, 1, 0.02, 0.02, 0.02, 0.02); } } + } - // Scale sound effects with power - much quieter at low levels - if (powerScalingFactor > 0.1f) { - float impactSoundVolume = Math.max(0.1f, Math.min(4.0f, powerScalingFactor * 4.0f)); - level.playSound(null, BlockPos.containing(impactPoint), SoundEvents.GENERIC_EXPLODE, SoundSource.PLAYERS, impactSoundVolume, 0.4f); + // Scale sound effects with power - much quieter at low levels + if (powerScalingFactor > 0.1f) { + float impactSoundVolume = Math.max(0.1f, Math.min(4.0f, powerScalingFactor * 4.0f)); + level.playSound(null, BlockPos.containing(impactPoint), SoundEvents.GENERIC_EXPLODE, SoundSource.PLAYERS, impactSoundVolume, 0.4f); - if (powerScalingFactor > 0.5f) { - level.playSound(null, BlockPos.containing(impactPoint), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.PLAYERS, impactSoundVolume * 0.7f, 0.8f); - } + if (powerScalingFactor > 0.5f) { + level.playSound(null, BlockPos.containing(impactPoint), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.PLAYERS, impactSoundVolume * 0.7f, 0.8f); } } + } - private void applyAreaDamage(ServerLevel level, ServerPlayer caster, Vec3 impactPoint, - float finalRadius, float finalDamage, float finalKnockback) { - // Find entities in radius - AABB searchArea = new AABB( - impactPoint.x - finalRadius, impactPoint.y - finalRadius, impactPoint.z - finalRadius, - impactPoint.x + finalRadius, impactPoint.y + finalRadius, impactPoint.z + finalRadius - ); - - List nearbyEntities = level.getEntitiesOfClass(LivingEntity.class, searchArea); - - for (LivingEntity entity : nearbyEntities) { - if (entity == caster) continue; // Don't damage the caster - - double distance = entity.position().distanceTo(impactPoint); - if (distance <= finalRadius) { - // Calculate damage and knockback based on distance (closer = more damage) - double damageMultiplier = Math.max(0.3, 1.0 - (distance / finalRadius)); - double knockbackMultiplier = Math.max(0.2, 1.0 - (distance / finalRadius)); - - float actualDamage = (float) (finalDamage * damageMultiplier); - float actualKnockback = (float) (finalKnockback * knockbackMultiplier); - - // Apply damage using explosion damage source - DamageSource explosionDamage = level.damageSources().explosion(caster, caster); - entity.hurt(explosionDamage, actualDamage); - - // Apply knockback effect - if (distance > 0.1) { // Avoid division by zero - Vec3 knockbackDirection = entity.position().subtract(impactPoint).normalize(); - - // Add upward component for more realistic knockback - knockbackDirection = new Vec3( - knockbackDirection.x, - Math.max(0.2, knockbackDirection.y + 0.3), - knockbackDirection.z - ); - - Vec3 knockbackVelocity = knockbackDirection.scale(actualKnockback * 0.25); - entity.setDeltaMovement(entity.getDeltaMovement().add(knockbackVelocity)); - if (entity instanceof ServerPlayer sp) { - sp.connection.send(new ClientboundSetEntityMotionPacket(sp)); - } + private void applyAreaDamage(ServerLevel level, ServerPlayer caster, Vec3 impactPoint, + float finalRadius, float finalDamage, float finalKnockback) { + // Find entities in radius + AABB searchArea = new AABB( + impactPoint.x - finalRadius, impactPoint.y - finalRadius, impactPoint.z - finalRadius, + impactPoint.x + finalRadius, impactPoint.y + finalRadius, impactPoint.z + finalRadius + ); + + List nearbyEntities = level.getEntitiesOfClass(LivingEntity.class, searchArea); + + for (LivingEntity entity : nearbyEntities) { + if (entity == caster) continue; // Don't damage the caster + + double distance = entity.position().distanceTo(impactPoint); + if (distance <= finalRadius) { + // Calculate damage and knockback based on distance (closer = more damage) + double damageMultiplier = Math.max(0.3, 1.0 - (distance / finalRadius)); + double knockbackMultiplier = Math.max(0.2, 1.0 - (distance / finalRadius)); + + float actualDamage = (float)(finalDamage * damageMultiplier); + float actualKnockback = (float)(finalKnockback * knockbackMultiplier); + + // Apply damage using explosion damage source + DamageSource explosionDamage = level.damageSources().explosion(caster, caster); + entity.hurt(explosionDamage, actualDamage); + + // Apply knockback effect + if (distance > 0.1) { // Avoid division by zero + Vec3 knockbackDirection = entity.position().subtract(impactPoint).normalize(); + + // Add upward component for more realistic knockback + knockbackDirection = new Vec3( + knockbackDirection.x, + Math.max(0.2, knockbackDirection.y + 0.3), + knockbackDirection.z + ); + + Vec3 knockbackVelocity = knockbackDirection.scale(actualKnockback * 0.25); + entity.setDeltaMovement(entity.getDeltaMovement().add(knockbackVelocity)); + if (entity instanceof ServerPlayer sp) { + sp.connection.send(new ClientboundSetEntityMotionPacket(sp)); } } } } + } - @Override - public String getDocumentationDescription() { - return "A charging punch ability based on My Hero Academia's Detroit Smash. Scales damage, range, and effects based on stored power in the user's chest. Creates ray-cast explosions along the punch trajectory with proper damage and knockback for all entities including players."; - } - - static { - CHARGE_TICKS = (new IntegerProperty("charge_ticks")).sync(SyncType.NONE).disablePersistence(); - } + @Override + public String getDocumentationDescription() { + return "A charging punch ability based on My Hero Academia's Detroit Smash. Scales damage, range, and effects based on stored power in the user's chest. Creates ray-cast explosions along the punch trajectory with proper damage and knockback for all entities including players."; } + static { + CHARGE_TICKS = (new IntegerProperty("charge_ticks")).sync(SyncType.NONE).disablePersistence(); + } +} \ No newline at end of file diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/conditions/BurstRenderCondition.java b/forge/src/main/java/com/github/b4ndithelps/forge/conditions/BurstRenderCondition.java index 20e8d773..d6517a80 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/conditions/BurstRenderCondition.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/conditions/BurstRenderCondition.java @@ -1,7 +1,5 @@ package com.github.b4ndithelps.forge.conditions; -import com.github.b4ndithelps.forge.capabilities.Body.BodyPart; -import com.github.b4ndithelps.forge.capabilities.Body.DamageStage; import com.github.b4ndithelps.forge.systems.BodyStatusHelper; import com.google.gson.JsonObject; import net.minecraft.world.entity.LivingEntity; diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/events/PhantomSpawnHandler.java b/forge/src/main/java/com/github/b4ndithelps/forge/events/PhantomSpawnHandler.java index 74bd1d04..3dce0974 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/events/PhantomSpawnHandler.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/events/PhantomSpawnHandler.java @@ -1,6 +1,11 @@ package com.github.b4ndithelps.forge.events; import com.github.b4ndithelps.BanditsQuirkLib; +import com.github.b4ndithelps.forge.systems.GenomeHelper; +import com.github.b4ndithelps.genetics.Gene; +import com.github.b4ndithelps.genetics.GeneticsHelper; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.monster.Phantom; @@ -24,11 +29,23 @@ public static void onCheckSpawn(MobSpawnEvent.FinalizeSpawn event) { var players = event.getLevel().players(); for (var player : players) { - if (SuperpowerUtil.hasSuperpower(player, ResourceLocation.parse("bql:phantom_immunity"))) { + if (hasGene(player, "bandits_quirk_lib:gene.phantom_immunity")) { event.setSpawnCancelled(true); break; } } } } + + + public static boolean hasGene(Player player, String geneId) { + ListTag genome = GenomeHelper.getGenome(player); + for (int i = 0; i < genome.size(); i++) { + CompoundTag gene = genome.getCompound(i); + if (geneId.equals(gene.getString("id"))) { + return true; + } + } + return false; + } } diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/events/ZombieTargetingHandler.java b/forge/src/main/java/com/github/b4ndithelps/forge/events/ZombieTargetingHandler.java index e46e6d5d..60609dd1 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/events/ZombieTargetingHandler.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/events/ZombieTargetingHandler.java @@ -1,5 +1,8 @@ package com.github.b4ndithelps.forge.events; +import com.github.b4ndithelps.forge.systems.GenomeHelper; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.event.entity.living.LivingChangeTargetEvent; @@ -19,11 +22,22 @@ public static void onTargetSet(LivingChangeTargetEvent event) { LivingEntity target = event.getNewTarget(); if (!(target instanceof Player player)) return; - if (SuperpowerUtil.hasSuperpower(player, ResourceLocation.parse("bql:dead_cells"))) { + if (hasGene(player, "bandits_quirk_lib:gene.dead_cells")) { // Zombies attack back automatically if hurt, so we (me and chatgpt) only want to block "normal aggro" if (zombie.getLastAttacker() != player) { event.setCanceled(true); } } } + + public static boolean hasGene(Player player, String geneId) { + ListTag genome = GenomeHelper.getGenome(player); + for (int i = 0; i < genome.size(); i++) { + CompoundTag gene = genome.getCompound(i); + if (geneId.equals(gene.getString("id"))) { + return true; + } + } + return false; + } } diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/network/BlackScreenNetwork.java b/forge/src/main/java/com/github/b4ndithelps/forge/network/BlackScreenNetwork.java index cdcaa464..547e5e7e 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/network/BlackScreenNetwork.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/network/BlackScreenNetwork.java @@ -1,6 +1,5 @@ package com.github.b4ndithelps.forge.network; -import com.github.b4ndithelps.forge.capabilities.StaminaDataProvider; import com.github.b4ndithelps.forge.client.ScreenFadeOverlay; import com.github.b4ndithelps.forge.sounds.ModSounds; import net.minecraft.client.Minecraft; diff --git a/forge/src/main/resources/assets/bandits_quirk_lib/lang/en_us.json b/forge/src/main/resources/assets/bandits_quirk_lib/lang/en_us.json index 057614ee..aeed2d3d 100644 --- a/forge/src/main/resources/assets/bandits_quirk_lib/lang/en_us.json +++ b/forge/src/main/resources/assets/bandits_quirk_lib/lang/en_us.json @@ -65,5 +65,10 @@ "bandits_quirk_lib:gene.step_up": "Step Assist", "bandits_quirk_lib:gene.swim_speed": "Swim Speed", "bandits_quirk_lib:gene.walk_speed": "Walk Speed", - "bandits_quirk_lib:gene.zoom": "Zoom" + "bandits_quirk_lib:gene.zoom": "Zoom", + "bandits_quirk_lib:gene.ender_gene": "Ender", + "bandits_quirk_lib:gene.dead_cells": "Dead Cells", + "bandits_quirk_lib:gene.phantom_immunity": "Phantom Immunity", + "bandits_quirk_lib:gene.adrenaline": "Adrenaline", + "bandits_quirk_lib:gene.burst_gene": "Burst" } \ No newline at end of file diff --git a/forge/src/main/resources/data/bandits_quirk_lib/genes/adrenaline.json b/forge/src/main/resources/data/bandits_quirk_lib/genes/adrenaline.json new file mode 100644 index 00000000..34439c36 --- /dev/null +++ b/forge/src/main/resources/data/bandits_quirk_lib/genes/adrenaline.json @@ -0,0 +1,8 @@ +{ + "id": "bandits_quirk_lib:gene.adrenaline", + "category": "lowend", + "rarity": "common", + "quality_range": [10, 100], + "combinable": true, + "description": "A gene that releases adrenaline once user is in danger." +} \ No newline at end of file diff --git a/forge/src/main/resources/data/bandits_quirk_lib/genes/burst_gene.json b/forge/src/main/resources/data/bandits_quirk_lib/genes/burst_gene.json new file mode 100644 index 00000000..049df304 --- /dev/null +++ b/forge/src/main/resources/data/bandits_quirk_lib/genes/burst_gene.json @@ -0,0 +1,14 @@ +{ + "id": "bandits_quirk_lib:gene.burst_gene", + "category": "quirk", + "rarity": "rare", + "quality_range": [10, 100], + "combinable": true, + "description": "Explode parts fo yourself.", + "combination": { + "requires": [ + { "id": "bandits_quirk_lib:gene.glowing_skin", "min_quality": 60 } + ], + "builder": { "count": 1, "min_quality": 50 } + } +} \ No newline at end of file diff --git a/forge/src/main/resources/data/bandits_quirk_lib/genes/dead_cells.json b/forge/src/main/resources/data/bandits_quirk_lib/genes/dead_cells.json new file mode 100644 index 00000000..841fa60f --- /dev/null +++ b/forge/src/main/resources/data/bandits_quirk_lib/genes/dead_cells.json @@ -0,0 +1,8 @@ +{ + "id": "bandits_quirk_lib:gene.dead_cells", + "category": "lowend", + "rarity": "common", + "quality_range": [10, 100], + "combinable": true, + "description": "A gene that makes zombies take you as one of their own." +} \ No newline at end of file diff --git a/forge/src/main/resources/data/bandits_quirk_lib/genes/ender_gene.json b/forge/src/main/resources/data/bandits_quirk_lib/genes/ender_gene.json new file mode 100644 index 00000000..29626e6d --- /dev/null +++ b/forge/src/main/resources/data/bandits_quirk_lib/genes/ender_gene.json @@ -0,0 +1,8 @@ +{ + "id": "bandits_quirk_lib:gene.ender_gene", + "category": "lowend", + "rarity": "common", + "quality_range": [10, 100], + "combinable": true, + "description": "A gene that allows you to teleport, although you cant control it." +} \ No newline at end of file diff --git a/forge/src/main/resources/data/bandits_quirk_lib/genes/phantom_immunity.json b/forge/src/main/resources/data/bandits_quirk_lib/genes/phantom_immunity.json new file mode 100644 index 00000000..35210a10 --- /dev/null +++ b/forge/src/main/resources/data/bandits_quirk_lib/genes/phantom_immunity.json @@ -0,0 +1,8 @@ +{ + "id": "bandits_quirk_lib:gene.phantom_immunity", + "category": "lowend", + "rarity": "common", + "quality_range": [10, 100], + "combinable": true, + "description": "A gene that prevents phantoms from spawning on user." +} \ No newline at end of file From 8b1b00b5b706e9c9276c688392c260cba490a163 Mon Sep 17 00:00:00 2001 From: KeeperOfHearts Date: Wed, 24 Dec 2025 17:43:47 +0100 Subject: [PATCH 7/7] change (configurable values) made configurable: - burst gene factor scaling - ender tp distance - adrenaline cooldown - adrenaline duration --- .../forge/abilities/AdrenalineAbility.java | 15 ++--- .../forge/abilities/BurstGeneAbility.java | 60 +++---------------- .../forge/abilities/EnderGeneTP.java | 9 ++- .../SuperBurstChargeGeneAbility.java | 12 ++-- .../SuperBurstExplosionGeneAbility.java | 11 ++-- .../b4ndithelps/forge/config/BQLConfig.java | 25 ++++++++ 6 files changed, 56 insertions(+), 76 deletions(-) diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java index 8cd7d90b..519248ed 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/AdrenalineAbility.java @@ -1,13 +1,10 @@ package com.github.b4ndithelps.forge.abilities; -import com.github.b4ndithelps.forge.client.ScreenFadeOverlay; +import com.github.b4ndithelps.forge.config.BQLConfig; import com.github.b4ndithelps.forge.network.BQLNetwork; import com.github.b4ndithelps.forge.network.BlackScreenNetwork; -import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.LivingEntity; @@ -15,10 +12,6 @@ import net.threetag.palladium.power.IPowerHolder; import net.threetag.palladium.power.ability.Ability; import net.threetag.palladium.power.ability.AbilityInstance; -import net.threetag.palladium.util.property.IntegerProperty; -import net.threetag.palladium.util.property.PalladiumProperty; -import net.threetag.palladium.util.property.PropertyManager; -import net.threetag.palladium.util.property.SyncType; public class AdrenalineAbility extends Ability { @@ -35,13 +28,13 @@ public void firstTick(LivingEntity entity, AbilityInstance entry, IPowerHolder h long ticks = level.getGameTime(); long lastUseTick = player.getPersistentData().getLong("lastUseTimeAdrenaline"); - if (ticks - lastUseTick < 1200) { // 60 seconds cooldown + if (ticks - lastUseTick < BQLConfig.INSTANCE.adrenalineCooldown.get() * 20) { // 60 seconds cooldown return; } -// player.sendSystemMessage(Component.literal(lastUseTick+"")); + player.getPersistentData().putLong("lastUseTimeAdrenaline", ticks); BQLNetwork.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), new BlackScreenNetwork(player.getUUID())); - player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SPEED, 600, 1)); + player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SPEED, BQLConfig.INSTANCE.adrenalineDurationSeconds.get(), 1)); } } diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/BurstGeneAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/BurstGeneAbility.java index 42732d3d..11e4913a 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/BurstGeneAbility.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/BurstGeneAbility.java @@ -1,6 +1,7 @@ package com.github.b4ndithelps.forge.abilities; +import com.github.b4ndithelps.forge.config.BQLConfig; import com.github.b4ndithelps.forge.particle.ModParticles; import com.github.b4ndithelps.forge.sounds.ModSounds; import com.github.b4ndithelps.forge.systems.BodyStatusHelper; @@ -47,7 +48,6 @@ import static com.github.b4ndithelps.forge.utils.ActionBarHelper.sendPercentageDisplay; public class BurstGeneAbility extends Ability { - private static final float FACTOR_SCALING = 2.5F; // Unique properties for tracking state public static final PalladiumProperty CHARGE_TICKS; @@ -141,67 +141,21 @@ public void tick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder } -// @Override -// public void lastTick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder, boolean enabled) { -// if (entity instanceof ServerPlayer player && entity.level() instanceof ServerLevel serverLevel) { -// int chargeTicks = entry.getProperty(CHARGE_TICKS); -// -// // Prevent bug where it forces a smash on reload -// if (chargeTicks == 0) return; -// -// // Trigger arm swing animation -//// player.swing(InteractionHand.MAIN_HAND, true); -// -// // Execute the Detroit Smash -// executeBurst(player, serverLevel, entry, chargeTicks); -// -// // Reset charge -// entry.setUniqueProperty(CHARGE_TICKS, 0); -// BodyStatusHelper.setCustomFloat(player, "chest", "burst_gene_charge", 0.0f); -// } -// } - -// private void handleChargingPhase(ServerPlayer player, ServerLevel level, AbilityInstance entry) { -// int maxChargeTicks = MAX_CHARGE_TICKS; -// int currentChargeTicks = entry.getProperty(CHARGE_TICKS); -// -// // Increment charge if not at max -// if (currentChargeTicks < maxChargeTicks) { -// currentChargeTicks++; -// entry.setUniqueProperty(CHARGE_TICKS, currentChargeTicks); -// BodyStatusHelper.setCustomFloat(player, "chest", "burst_gene_charge", currentChargeTicks); -// } -// -// float chargeAmount = (float) (CHARGE_SCALING * currentChargeTicks * (QuirkFactorHelper.getQuirkFactor(player)+1) / 20); -// float currentHealth = player.getHealth(); -// -// var color = ChatFormatting.GREEN; -// -// if (chargeAmount * 4 >= currentHealth) { -// color = ChatFormatting.BLACK; -// } else if (chargeAmount * 4 >= currentHealth * 0.75f) { -// color = ChatFormatting.DARK_RED; -// } else if (chargeAmount * 4 >= currentHealth * 0.4f) { -// color = ChatFormatting.YELLOW; -// } -// -// sendPercentageDisplay(player, "Burst Gene Charge", (currentChargeTicks / (float)maxChargeTicks) * 100.0f, -// ChatFormatting.GRAY, color, currentChargeTicks == maxChargeTicks ? "MAX" : "Charging");; -// } - private void executeBurst(ServerPlayer player, ServerLevel level, AbilityInstance entry) { float factor = (float) (QuirkFactorHelper.getQuirkFactor(player)+1); - player.hurt(level.damageSources().generic(), factor*FACTOR_SCALING*3); + double factor_scaling = BQLConfig.INSTANCE.burstGeneFactorScaling.get(); + float damageDone = (float) (factor * factor_scaling * 3); + player.hurt(level.damageSources().generic(), damageDone); level.explode( (Entity) player, // entity that caused it (null = no source) player.getX(), // x player.getY() + 1, // y player.getZ(), // z - factor * FACTOR_SCALING * 1.5F, // explosion power (TNT = 4) + (float) (factor * factor_scaling * 1.5F), // explosion power (TNT = 4) Level.ExplosionInteraction.MOB // what kind of explosion (controls block damage) ); - float playersHealthResistance = (float) Math.max(1, (player.getMaxHealth() - 20) * 0.5); - BodyStatusHelper.damageAll(player, factor * FACTOR_SCALING * 15 / playersHealthResistance); + + BodyStatusHelper.damageAll(player, (player.getMaxHealth() - damageDone) / player.getMaxHealth()); } diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/EnderGeneTP.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/EnderGeneTP.java index 35ed01f7..37c0236c 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/EnderGeneTP.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/EnderGeneTP.java @@ -1,5 +1,6 @@ package com.github.b4ndithelps.forge.abilities; +import com.github.b4ndithelps.forge.config.BQLConfig; import com.github.b4ndithelps.forge.network.BQLNetwork; import com.github.b4ndithelps.forge.network.BlackScreenNetwork; import net.minecraft.network.chat.Component; @@ -31,10 +32,12 @@ public void firstTick(LivingEntity entity, AbilityInstance entry, IPowerHolder h double startY = entity.getY(); double startZ = entity.getZ(); + int distance = BQLConfig.INSTANCE.enderGeneTpDistance.get(); + for (int i = 0; i < 16; ++i) { // 16 attempts, just like vanilla - double targetX = startX + (entity.getRandom().nextDouble() - 0.5D) * 16.0D; - double targetY = startY + (entity.getRandom().nextInt(16) - 8); - double targetZ = startZ + (entity.getRandom().nextDouble() - 0.5D) * 16.0D; + double targetX = startX + (entity.getRandom().nextDouble() - 0.5D) * distance; + double targetY = startY + (entity.getRandom().nextInt(distance) - distance/2.0); + double targetZ = startZ + (entity.getRandom().nextDouble() - 0.5D) * distance; if (entity.isPassenger()) entity.stopRiding(); diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstChargeGeneAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstChargeGeneAbility.java index 3bcf9f01..138b5466 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstChargeGeneAbility.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstChargeGeneAbility.java @@ -1,6 +1,7 @@ package com.github.b4ndithelps.forge.abilities; +import com.github.b4ndithelps.forge.config.BQLConfig; import com.github.b4ndithelps.forge.effects.ModEffects; import com.github.b4ndithelps.forge.network.BQLNetwork; import com.github.b4ndithelps.forge.network.PlayerAnimationPacket; @@ -24,7 +25,6 @@ import net.threetag.palladium.util.property.*; public class SuperBurstChargeGeneAbility extends Ability { - private static final float FACTOR_SCALING = 10F; // Unique properties for tracking state public static final PalladiumProperty CHARGE_TICKS; @@ -84,7 +84,7 @@ public void tick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder } sendInfoMessage(player, charge, factor); - if (player.getHealth() - factor*FACTOR_SCALING <= 0) { + if (player.getHealth() - factor*BQLConfig.INSTANCE.superBurstFactorScaling.get() <= 0) { BodyStatusHelper.setCustomFloat(player, "chest", "super_burst_charge", charge-0.5f); } BodyStatusHelper.setCustomFloat(player, "chest", "super_burst_charge", ++charge); @@ -104,11 +104,13 @@ public void lastTick(LivingEntity entity, AbilityInstance entry, IPowerHolder ho private void sendInfoMessage(ServerPlayer player, float chargePercent, float factor) { ChatFormatting color = ChatFormatting.GREEN; - if (player.getHealth() - factor*FACTOR_SCALING <= 0) { + double factorScaling = BQLConfig.INSTANCE.superBurstFactorScaling.get(); + + if (player.getHealth() - factor*factorScaling <= 0) { color = ChatFormatting.BLACK; - } else if (player.getHealth() - factor*FACTOR_SCALING <= player.getHealth() * 0.25f) { + } else if (player.getHealth() - factor*factorScaling <= player.getHealth() * 0.25f) { color = ChatFormatting.DARK_RED; - } else if (player.getHealth() - factor*FACTOR_SCALING <= player.getHealth() * 0.6f) { + } else if (player.getHealth() - factor*factorScaling <= player.getHealth() * 0.6f) { color = ChatFormatting.YELLOW; } ActionBarHelper.sendPercentageDisplay( diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstExplosionGeneAbility.java b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstExplosionGeneAbility.java index 9693c9c3..8ba897f9 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstExplosionGeneAbility.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/abilities/SuperBurstExplosionGeneAbility.java @@ -1,6 +1,7 @@ package com.github.b4ndithelps.forge.abilities; +import com.github.b4ndithelps.forge.config.BQLConfig; import com.github.b4ndithelps.forge.network.BQLNetwork; import com.github.b4ndithelps.forge.network.PlayerAnimationPacket; import com.github.b4ndithelps.forge.systems.BodyStatusHelper; @@ -9,6 +10,7 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.level.Level; import net.minecraftforge.network.PacketDistributor; @@ -18,7 +20,6 @@ import org.joml.Vector3f; public class SuperBurstExplosionGeneAbility extends Ability { - private static final float FACTOR_SCALING = 10F; public SuperBurstExplosionGeneAbility() { super(); @@ -47,15 +48,17 @@ public void tick(LivingEntity entity, AbilityInstance entry, IPowerHolder holder private void executeBurst(ServerPlayer player, ServerLevel level) { + double FACTOR_SCALING = BQLConfig.INSTANCE.superBurstFactorScaling.get(); + float charge = BodyStatusHelper.getCustomFloat(player, "chest", "super_burst_charge"); float factor = (float) (QuirkFactorHelper.getQuirkFactor(player)+1) * charge/20; - player.hurt(level.damageSources().generic(), factor*FACTOR_SCALING); + player.hurt(level.damageSources().generic(), (float) (factor*FACTOR_SCALING)); level.explode( - player, // entity that caused it (null = no source) + (Entity) player, // entity that caused it (null = no source) player.getX(), // x player.getY() + 1, // y player.getZ(), // z - factor * FACTOR_SCALING * 1.5F, // factor * FACTOR_SCALING * 1.5F + (float) (factor * FACTOR_SCALING * 1.5F), // factor * FACTOR_SCALING * 1.5F Level.ExplosionInteraction.TNT // what kind of explosion (controls block damage) ); float playersHealthResistance = (float) Math.max(0.01 ,1 - ((player.getMaxHealth() - factor*FACTOR_SCALING) / player.getMaxHealth())); diff --git a/forge/src/main/java/com/github/b4ndithelps/forge/config/BQLConfig.java b/forge/src/main/java/com/github/b4ndithelps/forge/config/BQLConfig.java index f206a9b3..9b124484 100644 --- a/forge/src/main/java/com/github/b4ndithelps/forge/config/BQLConfig.java +++ b/forge/src/main/java/com/github/b4ndithelps/forge/config/BQLConfig.java @@ -79,6 +79,11 @@ public class BQLConfig { public final ForgeConfigSpec.ConfigValue> seqLenZombieRange; public final ForgeConfigSpec.ConfigValue> seqLenHuskRange; public final ForgeConfigSpec.ConfigValue> seqLenDrownedRange; + public final ForgeConfigSpec.IntValue adrenalineDurationSeconds; + public final ForgeConfigSpec.IntValue adrenalineCooldown; + public final ForgeConfigSpec.DoubleValue burstGeneFactorScaling; + public final ForgeConfigSpec.DoubleValue superBurstFactorScaling; + public final ForgeConfigSpec.IntValue enderGeneTpDistance; // Player Genome public final ForgeConfigSpec.IntValue playerMaxGenes; @@ -321,6 +326,26 @@ public BQLConfig(ForgeConfigSpec.Builder builder) { .comment("Maximum number of genes a player can have at once") .defineInRange("player_max_genes", 12, 1, 64); + this.adrenalineDurationSeconds = builder + .comment("Duration in seconds of movement speed buff applied by the Adrenaline gene") + .defineInRange("adrenaline_duration_seconds", 10, 1, 3600); + + this.adrenalineCooldown = builder + .comment("Cooldown in seconds for the Adrenaline ability") + .defineInRange("adrenaline_cooldown", 60, 0, 100000); + + this.burstGeneFactorScaling = builder + .comment("Scaling factor used by the general burst gene damage/force calculations") + .defineInRange("burst_gene_factor_scaling", 2.5, 0.0, 100.0); + + this.superBurstFactorScaling = builder + .comment("Scaling factor used by the Super Burst gene ability)") + .defineInRange("super_burst_factor_scaling", 10.0, 0.0, 100.0); + + this.enderGeneTpDistance = builder + .comment("Maximum teleport distance (blocks) for the Ender gene") + .defineInRange("ender_gene_tpdistance", 16, 1, 1024); + builder.pop(); } } \ No newline at end of file