diff --git a/minecraft-mods/bettermace/.gitignore b/minecraft-mods/bettermace/.gitignore new file mode 100644 index 0000000..3d5c922 --- /dev/null +++ b/minecraft-mods/bettermace/.gitignore @@ -0,0 +1,26 @@ +# Gradle +.gradle/ +build/ +out/ +classes/ + +# IDEA +.idea/ +*.iml +*.ipr +*.iws + +# Eclipse +.settings/ +.metadata/ +.classpath +.project +bin/ + +# fabric-loom +.fabric/ +run/ + +# OS +.DS_Store +Thumbs.db diff --git a/minecraft-mods/bettermace/README.md b/minecraft-mods/bettermace/README.md new file mode 100644 index 0000000..1e3e59f --- /dev/null +++ b/minecraft-mods/bettermace/README.md @@ -0,0 +1,77 @@ +# BetterMace + +Мод для Minecraft **1.21.4** (Fabric), улучшающий использование Булавы (Mace). + +> Зачем: ваниль уже неплохая, но смэш-атакой можно бить только одну цель. Этот мод делает Mace ощутимо полезнее — особенно в толпах мобов и в PvE-рейдах. + +## Возможности + +- **AOE-удар по площади при смэш-атаке.** Когда вы успешно бьёте врага в прыжке/падении (как ванильный smash), все враги в радиусе вокруг цели получают долю бонусного урона от падения. + - Радиус AOE масштабируется по высоте падения (есть верхний предел). + - Соседние цели слегка отбрасываются наружу от точки удара. + - Срабатывает только при настоящем смэше (fall distance > порога, без элитр). +- **Опциональная молния** при экстремально высоком падении (по умолчанию выключена; можно сделать чисто визуальной или с уроном). +- **Опциональный «прыжок» (self-bounce)** игроку после удачного смэша — дополняет ванильный Wind Burst. +- **Полностью настраивается** через `config/bettermace.json` (создастся автоматически при первом запуске). +- Не ломает ванильную логику Density / Breach / Wind Burst — работает поверх неё. + +## Конфиг (`config/bettermace.json`) + +| Поле | По умолчанию | Описание | +|---|---|---| +| `aoeEnabled` | `true` | Включить AOE-удар по площади | +| `aoeRadius` | `2.5` | Базовый радиус AOE (блоки) | +| `aoeRadiusPerFallBlock` | `0.15` | Прибавка к радиусу за каждый блок падения | +| `aoeRadiusMax` | `3.5` | Максимальная прибавка к радиусу | +| `aoeRadiusVertical` | `1.5` | Вертикальная полу-высота зоны | +| `aoeDamageMultiplier` | `0.6` | Доля fall-bonus урона по соседям | +| `aoeKnockbackStrength` | `0.45` | Отбрасывание соседних целей | +| `aoeMinFallDistance` | `1.5` | Минимальное падение для AOE | +| `ignoreTeammates` | `true` | Не бить союзников | +| `lightningOnCriticalFall` | `false` | Молния при сильном падении | +| `lightningFallDistance` | `12.0` | С какого падения призывать молнию | +| `lightningDamages` | `false` | Наносит ли молния урон | +| `smashSelfBounce` | `0.0` | Доп. вертикальный импульс игроку после смэша | + +## Зависимости / совместимость + +- Minecraft `1.21.4` +- Fabric Loader `>= 0.16.0` +- Fabric API `0.114.0+1.21.4` (или совместимая для 1.21.4) +- Java `21` + +### Про Forge / NeoForge + +Под Minecraft 1.21.4 Forge как такового активно не разрабатывается — эквивалент это **NeoForge**. Этот мод сейчас собран только под Fabric. Для NeoForge-версии понадобится отдельный лоадер (можно сделать через мульти-лоадер шаблон Architectury) — это не входит в текущую сборку. + +## Сборка + +```bash +cd minecraft-mods/bettermace +./gradlew build # на первый запуск создаст gradle wrapper jar при необходимости +# Готовый .jar появится в build/libs/bettermace-.jar +``` + +> Для первого билда нужен интернет — Loom скачает Minecraft + Yarn + Fabric API. + +## Установка + +1. Установите [Fabric Loader](https://fabricmc.net/use/installer/) для 1.21.4. +2. Положите в папку `mods/`: + - `fabric-api-.jar` + - `bettermace-.jar` +3. Запустите игру. При первом старте появится `config/bettermace.json` — поправьте под себя. + +## Как это работает (под капотом) + +Мод подмешивается (`@Mixin`) в `net.minecraft.item.MaceItem#postHurtEnemy` и при условии настоящего смэш-удара: + +1. Берёт `player.fallDistance` и считает «ванильную» формулу бонусного урона (`4/2/1` за блок по сегментам `0-3 / 3-8 / 8+`). +2. Находит соседних `LivingEntity` в `Box.expand(radius, vertical, radius)` вокруг ударенной цели. +3. Наносит каждому `aoeDamageMultiplier * bonus` урона через `DamageSources.mobAttack(attacker)`. +4. Толкает их наружу + спавнит партиклы `SWEEP_ATTACK` + проигрывает `MACE_SMASH_GROUND_HEAVY`. +5. По желанию — призывает молнию и/или подбрасывает игрока. + +## Лицензия + +MIT — см. корень репозитория. diff --git a/minecraft-mods/bettermace/build.gradle b/minecraft-mods/bettermace/build.gradle new file mode 100644 index 0000000..7be0a37 --- /dev/null +++ b/minecraft-mods/bettermace/build.gradle @@ -0,0 +1,74 @@ +plugins { + id 'fabric-loom' version '1.9-SNAPSHOT' + id 'maven-publish' +} + +version = project.mod_version +group = project.maven_group + +base { + archivesName = project.archives_base_name +} + +repositories { + // Add repositories to retrieve artifacts from in here. + // You should only use this when depending on other mods because + // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html + // for more information about repositories. +} + +dependencies { + // To change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" +} + +processResources { + inputs.property "version", project.version + + filesMatching("fabric.mod.json") { + expand "version": inputs.properties.version + } +} + +tasks.withType(JavaCompile).configureEach { + it.options.release = 21 +} + +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() + + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +jar { + from("LICENSE") { + rename { "${it}_${project.archivesBaseName}" } + } +} + +// configure the maven publication +publishing { + publications { + create("mavenJava", MavenPublication) { + artifactId = project.archives_base_name + from components.java + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for retrieving dependencies. + } +} diff --git a/minecraft-mods/bettermace/gradle.properties b/minecraft-mods/bettermace/gradle.properties new file mode 100644 index 0000000..b784b9a --- /dev/null +++ b/minecraft-mods/bettermace/gradle.properties @@ -0,0 +1,17 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx2G +org.gradle.parallel=true + +# Fabric Properties +# check these on https://fabricmc.net/develop/ +minecraft_version=1.21.4 +yarn_mappings=1.21.4+build.8 +loader_version=0.16.9 + +# Mod Properties +mod_version=1.0.0 +maven_group=dev.bettermace +archives_base_name=bettermace + +# Dependencies +fabric_version=0.114.0+1.21.4 diff --git a/minecraft-mods/bettermace/gradle/wrapper/gradle-wrapper.properties b/minecraft-mods/bettermace/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..df97d72 --- /dev/null +++ b/minecraft-mods/bettermace/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/minecraft-mods/bettermace/settings.gradle b/minecraft-mods/bettermace/settings.gradle new file mode 100644 index 0000000..e23409d --- /dev/null +++ b/minecraft-mods/bettermace/settings.gradle @@ -0,0 +1,12 @@ +pluginManagement { + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + mavenCentral() + gradlePluginPortal() + } +} + +rootProject.name = 'bettermace' diff --git a/minecraft-mods/bettermace/src/main/java/dev/bettermace/BetterMace.java b/minecraft-mods/bettermace/src/main/java/dev/bettermace/BetterMace.java new file mode 100644 index 0000000..ec3c0bf --- /dev/null +++ b/minecraft-mods/bettermace/src/main/java/dev/bettermace/BetterMace.java @@ -0,0 +1,20 @@ +package dev.bettermace; + +import dev.bettermace.config.BetterMaceConfig; +import net.fabricmc.api.ModInitializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BetterMace implements ModInitializer { + public static final String MOD_ID = "bettermace"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + + @Override + public void onInitialize() { + BetterMaceConfig.load(); + LOGGER.info("[BetterMace] loaded. AOE radius={}, AOE multiplier={}, lightning={}.", + BetterMaceConfig.get().aoeRadius, + BetterMaceConfig.get().aoeDamageMultiplier, + BetterMaceConfig.get().lightningOnCriticalFall); + } +} diff --git a/minecraft-mods/bettermace/src/main/java/dev/bettermace/config/BetterMaceConfig.java b/minecraft-mods/bettermace/src/main/java/dev/bettermace/config/BetterMaceConfig.java new file mode 100644 index 0000000..dfb7d9c --- /dev/null +++ b/minecraft-mods/bettermace/src/main/java/dev/bettermace/config/BetterMaceConfig.java @@ -0,0 +1,82 @@ +package dev.bettermace.config; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import dev.bettermace.BetterMace; +import net.fabricmc.loader.api.FabricLoader; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Конфиг мода. Хранится в config/bettermace.json и читается при старте мода. + */ +public class BetterMaceConfig { + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private static BetterMaceConfig INSTANCE = new BetterMaceConfig(); + + // === AOE smash attack === + /** Включает удар по площади при смэш-атаке булавы. */ + public boolean aoeEnabled = true; + /** Базовый радиус AOE (в блоках). */ + public double aoeRadius = 2.5; + /** Дополнительный радиус за каждый блок падения. */ + public double aoeRadiusPerFallBlock = 0.15; + /** Максимальная прибавка к радиусу от падения. */ + public double aoeRadiusMax = 3.5; + /** Вертикальная половина-высота AOE box. */ + public double aoeRadiusVertical = 1.5; + /** Множитель урона по соседним целям относительно бонуса от падения. */ + public float aoeDamageMultiplier = 0.6f; + /** Сила отбрасывания соседних целей наружу. */ + public double aoeKnockbackStrength = 0.45; + /** Минимальное расстояние падения для активации AOE. */ + public float aoeMinFallDistance = 1.5f; + /** Не атаковать союзников/тех-же-команда сущностей. */ + public boolean ignoreTeammates = true; + + // === Optional lightning on extreme fall === + /** Призывать молнию при особо высоком падении. */ + public boolean lightningOnCriticalFall = false; + /** Минимальное расстояние падения (блоков) для молнии. */ + public float lightningFallDistance = 12.0f; + /** Молния наносит урон/поджигает (false = только визуал). */ + public boolean lightningDamages = false; + + // === Wind burst boost === + /** Дополнительный вертикальный импульс игроку при удачном смэше (бонус к Wind Burst). */ + public double smashSelfBounce = 0.0; + + public static BetterMaceConfig get() { + return INSTANCE; + } + + public static void load() { + Path path = FabricLoader.getInstance().getConfigDir().resolve("bettermace.json"); + try { + if (Files.exists(path)) { + String json = Files.readString(path); + BetterMaceConfig loaded = GSON.fromJson(json, BetterMaceConfig.class); + if (loaded != null) { + INSTANCE = loaded; + } + } else { + save(); + } + } catch (IOException | RuntimeException e) { + BetterMace.LOGGER.error("[BetterMace] failed to load config, using defaults", e); + INSTANCE = new BetterMaceConfig(); + } + } + + public static void save() { + Path path = FabricLoader.getInstance().getConfigDir().resolve("bettermace.json"); + try { + Files.createDirectories(path.getParent()); + Files.writeString(path, GSON.toJson(INSTANCE)); + } catch (IOException e) { + BetterMace.LOGGER.error("[BetterMace] failed to save config", e); + } + } +} diff --git a/minecraft-mods/bettermace/src/main/java/dev/bettermace/mixin/MaceItemMixin.java b/minecraft-mods/bettermace/src/main/java/dev/bettermace/mixin/MaceItemMixin.java new file mode 100644 index 0000000..3aa8122 --- /dev/null +++ b/minecraft-mods/bettermace/src/main/java/dev/bettermace/mixin/MaceItemMixin.java @@ -0,0 +1,119 @@ +package dev.bettermace.mixin; + +import dev.bettermace.config.BetterMaceConfig; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.LightningEntity; +import net.minecraft.entity.SpawnReason; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.MaceItem; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Vec3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.List; + +@Mixin(MaceItem.class) +public class MaceItemMixin { + + @Inject(method = "postHurtEnemy", at = @At("HEAD")) + private void bettermace$applySmashExtras(ItemStack stack, LivingEntity target, LivingEntity attacker, CallbackInfoReturnable cir) { + if (!(attacker instanceof PlayerEntity player)) { + return; + } + if (!(player.getWorld() instanceof ServerWorld serverWorld)) { + return; + } + + BetterMaceConfig cfg = BetterMaceConfig.get(); + float fallDistance = player.fallDistance; + if (fallDistance <= cfg.aoeMinFallDistance) { + return; + } + if (player.isFallFlying()) { + return; + } + + float bonus = bettermace$computeBonusDamage(fallDistance); + + if (cfg.aoeEnabled) { + double radius = cfg.aoeRadius + Math.min(fallDistance * cfg.aoeRadiusPerFallBlock, cfg.aoeRadiusMax); + Box box = target.getBoundingBox().expand(radius, cfg.aoeRadiusVertical, radius); + List nearby = serverWorld.getEntitiesByClass(LivingEntity.class, box, + e -> e != attacker && e != target && e.isAlive() && !e.isSpectator() + && (!cfg.ignoreTeammates || !e.isTeammate(attacker))); + + float aoeDamage = bonus * cfg.aoeDamageMultiplier; + if (aoeDamage > 0.0f) { + Vec3d origin = target.getPos(); + for (LivingEntity entity : nearby) { + entity.damage(serverWorld, serverWorld.getDamageSources().mobAttack(attacker), aoeDamage); + Vec3d push = entity.getPos().subtract(origin); + double len = push.length(); + if (len > 1.0E-4) { + Vec3d outward = push.multiply(1.0 / len); + entity.addVelocity(outward.x * cfg.aoeKnockbackStrength, 0.3, outward.z * cfg.aoeKnockbackStrength); + entity.velocityModified = true; + } + } + + serverWorld.spawnParticles(ParticleTypes.SWEEP_ATTACK, + target.getX(), target.getBodyY(0.5), target.getZ(), + 6, radius * 0.5, 0.1, radius * 0.5, 0.0); + serverWorld.playSound(null, target.getX(), target.getY(), target.getZ(), + SoundEvents.MACE_SMASH_GROUND_HEAVY, SoundCategory.PLAYERS, 0.8f, 0.9f); + } + } + + if (cfg.smashSelfBounce > 0.0) { + player.addVelocity(0.0, cfg.smashSelfBounce, 0.0); + player.velocityModified = true; + } + + if (cfg.lightningOnCriticalFall && fallDistance >= cfg.lightningFallDistance) { + LightningEntity bolt = EntityType.LIGHTNING_BOLT.create(serverWorld, SpawnReason.TRIGGERED); + if (bolt != null) { + bolt.refreshPositionAfterTeleport(target.getX(), target.getY(), target.getZ()); + bolt.setCosmetic(!cfg.lightningDamages); + if (attacker instanceof net.minecraft.server.network.ServerPlayerEntity sp) { + bolt.setChanneler(sp); + } + serverWorld.spawnEntity(bolt); + } + } + } + + /** + * Имитирует ванильную формулу бонуса урона от падения для смэш-атаки булавы: + * 4 урона/блок за первые 3 блока, 2 урона/блок за следующие 5, 1 урона/блок далее. + */ + @Unique + private static float bettermace$computeBonusDamage(float fallDistance) { + float remaining = fallDistance; + float dmg = 0.0f; + float first = Math.min(remaining, 3.0f); + dmg += first * 4.0f; + remaining -= first; + if (remaining <= 0.0f) { + return dmg; + } + float second = Math.min(remaining, 5.0f); + dmg += second * 2.0f; + remaining -= second; + if (remaining <= 0.0f) { + return dmg; + } + dmg += remaining * 1.0f; + return dmg; + } +} diff --git a/minecraft-mods/bettermace/src/main/resources/bettermace.mixins.json b/minecraft-mods/bettermace/src/main/resources/bettermace.mixins.json new file mode 100644 index 0000000..66eb517 --- /dev/null +++ b/minecraft-mods/bettermace/src/main/resources/bettermace.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "package": "dev.bettermace.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + "MaceItemMixin" + ], + "client": [], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/minecraft-mods/bettermace/src/main/resources/fabric.mod.json b/minecraft-mods/bettermace/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..e0527f5 --- /dev/null +++ b/minecraft-mods/bettermace/src/main/resources/fabric.mod.json @@ -0,0 +1,29 @@ +{ + "schemaVersion": 1, + "id": "bettermace", + "version": "${version}", + "name": "BetterMace", + "description": "Улучшает использование Булавы (Mace) в Minecraft 1.21.4: AOE-удар по площади при смэш-атаке, опциональная молния при сильном падении, настраиваемый радиус и множители урона.", + "authors": [ + "sammoa" + ], + "contact": { + "sources": "https://github.com/kinngg2/DeepSeek-Math" + }, + "license": "MIT", + "environment": "*", + "entrypoints": { + "main": [ + "dev.bettermace.BetterMace" + ] + }, + "mixins": [ + "bettermace.mixins.json" + ], + "depends": { + "fabricloader": ">=0.16.0", + "minecraft": "~1.21.4", + "java": ">=21", + "fabric-api": "*" + } +}