Skip to content

Commit

Permalink
Merge pull request #1 from vorlie/client-side-particles
Browse files Browse the repository at this point in the history
Add Client-Side Configuration and Particle Options with Translations (v1.0.4)
  • Loading branch information
vorlie authored Jan 13, 2025
2 parents 761ee02 + e1ad0ca commit 704d59f
Show file tree
Hide file tree
Showing 17 changed files with 367 additions and 33 deletions.
36 changes: 29 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
# **LifeDrain**

**LifeDrain** adds a simple but powerful mechanic to Minecraft: stealing life from hostile mobs. Every time you attack, you’ll heal yourself based on the damage dealt, making combat a way to stay alive rather than just survive.

🩸 **Features:**
- Healing mechanics:
- **Healing mechanics:**
- Base healing scales with the difficulty level:
- Easy: 2.0 HP
- Normal: 1.0 HP
- Hard: 0.5 HP
- Bonus healing based on the damage dealt: 20% of the damage dealt.
- Lifesteal cooldown is set to 1000 milliseconds (1 second) by default.
- **Client-Side Features:**
- **Particle Effects:** Enable or disable particle effects when healing is triggered. Particles are client-side, meaning they will only be visible on your screen.
- **Client-Side Configuration Options:** Through **Cloth Config** and **Mod Menu**, you can customize particle effects, cooldown settings, and other features directly from the in-game settings menu.
- Applies only to hostile mobs, ensuring lifesteal is balanced and works as intended in combat.

⚙️ **Configuration:**
- Customize healing values for different difficulty levels:
- Base healing for Easy, Normal, and Hard modes.
- Bonus healing multiplier based on the damage dealt.
- Enable or disable particle effects when healing is triggered.
- Adjust the cooldown for lifesteal activation (time between consecutive lifesteal uses).
- Configuration is saved to `PATH-TO-MINECRAFT-INSTANCE/config/lifedrain.json` and can be modified directly.
- **Client-Side Customization:**
- Enable or disable particle effects when healing is triggered (client-side only).
- Adjust the cooldown for lifesteal activation (time between consecutive lifesteal uses).
- Configuration options are accessible through **Cloth Config** and **Mod Menu**.
- All settings are saved to `lifedrain.json` and can be modified directly.
- The config file is automatically updated to add missing values if they are not found.
- Use the `/check_config` command to automatically check and update the config file if necessary.

**Translations Available:**
The mod is fully translated into the following languages:
- **English**
- **Polish**
- **Spanish (Mexico)**
- **Spanish**
- **Chinese (Simplified)**
- **Korean**
- **Russian**
- **German**
- **Japanese**
- **French**

This mod is lightweight and perfect for anyone who enjoys combat-focused gameplay or just wants a little extra survivability.

**Note:** Although the mod can be loaded in Minecraft `1.21.X`, it is not recommended. Attacking a hostile mob in versions other than `1.21` will cause your game to crash.

**Requirements:**
- Fabric Loader 0.16.10+
- Fabric API 0.102.0+1.21+
- Fabric Loader 0.16.10
- Fabric API 0.102.0 (for Minecraft 1.21)
- Minecraft 1.21
- Java 21+
- Java 21
- **Cloth Config 15.0.140** (Client-side, optional)
- **Mod Menu 11.0.3** (Client-side, optional)
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ repositories {
// 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.
maven { url "https://maven.shedaniel.me/" }
maven { url "https://maven.terraformersmc.com/releases/" }
}

loom {
Expand Down Expand Up @@ -44,6 +46,11 @@ dependencies {

// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"

modApi("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}") {
exclude(group: "net.fabricmc.fabric-api")
}
modApi "com.terraformersmc:modmenu:${project.mod_menu_version}"
}

processResources {
Expand Down
6 changes: 4 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ yarn_mappings=1.21+build.9
loader_version=0.16.10

# Mod Properties
mod_version=1.0.3
mod_version=1.0.4
maven_group=vorlie.lifedrain
archives_base_name=lifedrain

# Dependencies
fabric_version=0.102.0+1.21
fabric_version=0.102.0+1.21
cloth_config_version=15.0.140
mod_menu_version=11.0.3
62 changes: 60 additions & 2 deletions src/client/java/vorlie/lifedrain/LifeDrainClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,67 @@

import net.fabricmc.api.ClientModInitializer;

import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.mob.HostileEntity;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.math.random.Random;

import vorlie.lifedrain.config.ConfigManager;

public class LifeDrainClient implements ClientModInitializer {
private long lastLifestealTime = 0; // Time of the last lifesteal
private long COOLDOWN_TIME; // Cooldown time in milliseconds (1 second)

@Override
public void onInitializeClient() {
// This entrypoint is suitable for setting up client-specific logic, such as rendering.

COOLDOWN_TIME = ConfigManager.CONFIG.lifestealCooldown;

// Initialization logic for client-specific features
AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
if (world.isClient && entity instanceof HostileEntity) {
if (canActivateLifesteal()) {
handleLifesteal(player, (HostileEntity) entity);
}
}
return ActionResult.PASS;
});

ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, success) -> {
ConfigManager.load();
COOLDOWN_TIME = ConfigManager.CONFIG.lifestealCooldown;
});
}

private void handleLifesteal(PlayerEntity player, HostileEntity mob) {
if (player != null && mob != null && mob.isAlive()) {
if (player.getHealth() < player.getMaxHealth()) {
if (ConfigManager.CONFIG.enableParticles) {
ClientWorld world = MinecraftClient.getInstance().world;
if (world != null) {
Random random = player.getRandom();
for (int i = 0; i < 10; i++) {
world.addParticle(
ParticleTypes.HEART, // The particle type (HEART in this case)
player.getX() + random.nextDouble() - 0.5, // X offset
player.getY() + random.nextDouble(), // Y offset
player.getZ() + random.nextDouble() - 0.5, // Z offset
0, 0, 0); // Particle velocity (no movement)
}
}
}
}
lastLifestealTime = System.currentTimeMillis();
}
}

private boolean canActivateLifesteal() {
long currentTime = System.currentTimeMillis();
return (currentTime - lastLifestealTime) >= COOLDOWN_TIME;
}
}
}
93 changes: 93 additions & 0 deletions src/client/java/vorlie/lifedrain/ModMenuIntegration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package vorlie.lifedrain;

import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;

import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigCategory;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;

import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;

import net.minecraft.text.Text;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import vorlie.lifedrain.config.ConfigManager;

@Environment(EnvType.CLIENT) // Ensure this only runs on the client side
public class ModMenuIntegration implements ModMenuApi {
private static final Logger LOGGER = LoggerFactory.getLogger("lifedrain");

@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
LOGGER.info("Lifesteal: ModMenuIntegration loaded successfully.");

return parent -> {
ConfigBuilder builder = ConfigBuilder.create()
.setParentScreen(parent)
.setTitle(Text.translatable("config.lifedrain.title"));

ConfigCategory general = builder.getOrCreateCategory(Text.translatable("config.lifedrain.general"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();

// Enable Particles
general.addEntry(entryBuilder
.startBooleanToggle(Text.translatable("config.lifedrain.enableParticles"), ConfigManager.CONFIG.enableParticles)
.setDefaultValue(true)
.setSaveConsumer(value -> ConfigManager.CONFIG.enableParticles = value)
.setTooltip(Text.translatable("config.lifedrain.enableParticles.tooltip"))
.build());

// Lifesteal Cooldown
general.addEntry(entryBuilder
.startIntField(Text.translatable("config.lifedrain.lifestealCooldown"), ConfigManager.CONFIG.lifestealCooldown)
.setDefaultValue(1000)
.setMin(0)
.setSaveConsumer(value -> ConfigManager.CONFIG.lifestealCooldown = value)
.setTooltip(Text.translatable("config.lifedrain.lifestealCooldown.tooltip"))
.build());

// Base Heal (Easy)
general.addEntry(entryBuilder
.startFloatField(Text.translatable("config.lifedrain.baseHealEasy"), ConfigManager.CONFIG.baseHealEasy)
.setDefaultValue(2.0F)
.setMin(0.0F)
.setSaveConsumer(value -> ConfigManager.CONFIG.baseHealEasy = value)
.setTooltip(Text.translatable("config.lifedrain.baseHealEasy.tooltip"))
.build());

// Base Heal (Normal)
general.addEntry(entryBuilder
.startFloatField(Text.translatable("config.lifedrain.baseHealNormal"), ConfigManager.CONFIG.baseHealNormal)
.setDefaultValue(1.0F)
.setMin(0.0F)
.setSaveConsumer(value -> ConfigManager.CONFIG.baseHealNormal = value)
.setTooltip(Text.translatable("config.lifedrain.baseHealNormal.tooltip"))
.build());

// Base Heal (Hard)
general.addEntry(entryBuilder
.startFloatField(Text.translatable("config.lifedrain.baseHealHard"), ConfigManager.CONFIG.baseHealHard)
.setDefaultValue(0.5F)
.setMin(0.0F)
.setSaveConsumer(value -> ConfigManager.CONFIG.baseHealHard = value)
.setTooltip(Text.translatable("config.lifedrain.baseHealHard.tooltip"))
.build());

// Bonus Heal Multiplier
general.addEntry(entryBuilder
.startFloatField(Text.translatable("config.lifedrain.bonusHealMultiplier"), ConfigManager.CONFIG.bonusHealMultiplier)
.setDefaultValue(0.2F)
.setMin(0.0F)
.setSaveConsumer(value -> ConfigManager.CONFIG.bonusHealMultiplier = value)
.setTooltip(Text.translatable("config.lifedrain.bonusHealMultiplier.tooltip"))
.build());

builder.setSavingRunnable(ConfigManager::save);
return builder.build();
};
}
}
28 changes: 6 additions & 22 deletions src/main/java/vorlie/lifedrain/LifeDrain.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;

import net.minecraft.entity.mob.HostileEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.random.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -27,7 +23,7 @@ public class LifeDrain implements ModInitializer {

@Override
public void onInitialize() {
LOGGER.info("LifeDrain mod initialized!");
LOGGER.info("Lifesteal: LifeDrain mod initialized!");
ConfigManager.load(); // Load configuration

COOLDOWN_TIME = ConfigManager.CONFIG.lifestealCooldown;
Expand All @@ -44,14 +40,15 @@ public void onInitialize() {
// Register the config reload event
ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, success) -> {
ConfigManager.load();
LOGGER.info("LifeDrain config reloaded.");
LOGGER.info("Lifesteal: LifeDrain config reloaded.");
COOLDOWN_TIME = ConfigManager.CONFIG.lifestealCooldown;
});

CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
ConfigCheckCommand.register(dispatcher); // Only two parameters are expected here
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
ConfigCheckCommand.register(dispatcher); // Register your custom command
});
}

private void handleLifesteal(PlayerEntity player, HostileEntity mob) {
if (player != null && mob != null && mob.isAlive()) {
// Base healing amount from configuration
Expand All @@ -70,19 +67,6 @@ private void handleLifesteal(PlayerEntity player, HostileEntity mob) {
float totalHeal = baseHeal + bonusHeal;
player.heal(totalHeal);

// Show particles if enabled
if (ConfigManager.CONFIG.enableParticles && player.getWorld() instanceof ServerWorld serverWorld) {
Random random = player.getRandom();
for (int i = 0; i < 10; i++) {
serverWorld.spawnParticles(
ParticleTypes.HEART,
player.getX() + random.nextDouble() - 0.5,
player.getY() + random.nextDouble(),
player.getZ() + random.nextDouble() - 0.5,
1, 0, 0, 0, 0);
}
}

LOGGER.info("Lifesteal: {} healed {} (Base: {}, Bonus: {}).",
player.getName().getString(), totalHeal, baseHeal, bonusHeal);

Expand Down
16 changes: 16 additions & 0 deletions src/main/resources/assets/lifedrain/lang/de_de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"config.lifedrain.title": "LifeDrain Konfiguration",
"config.lifedrain.general": "Allgemein",
"config.lifedrain.enableParticles": "Partikel aktivieren",
"config.lifedrain.lifestealCooldown": "Lebensdiebstahl Abklingzeit (ms)",
"config.lifedrain.baseHealEasy": "Basisheilung (Einfach)",
"config.lifedrain.baseHealNormal": "Basisheilung (Normal)",
"config.lifedrain.baseHealHard": "Basisheilung (Schwierig)",
"config.lifedrain.bonusHealMultiplier": "Bonus Heilungs Multiplikator",
"config.lifedrain.enableParticles.tooltip": "Diese Einstellung betrifft nur den Client. Der Server kann sie überschreiben. Im Einzelspieler-Modus funktionieren die Werte lokal.",
"config.lifedrain.lifestealCooldown.tooltip": "Der Cooldown-Wert des Servers wird verwendet, und die Client-Einstellungen werden ignoriert. Im Einzelspieler-Modus funktionieren die Werte lokal.",
"config.lifedrain.baseHealEasy.tooltip": "Der Server bestimmt die Grundheilungswerte. Im Einzelspieler-Modus funktionieren die Werte lokal.",
"config.lifedrain.baseHealNormal.tooltip": "Der Server bestimmt die Grundheilungswerte. Im Einzelspieler-Modus funktionieren die Werte lokal.",
"config.lifedrain.baseHealHard.tooltip": "Der Server bestimmt die Grundheilungswerte. Im Einzelspieler-Modus funktionieren die Werte lokal.",
"config.lifedrain.bonusHealMultiplier.tooltip": "Der Server bestimmt den Bonusheilungs-Multiplikator. Im Einzelspieler-Modus funktionieren die Werte lokal."
}
16 changes: 16 additions & 0 deletions src/main/resources/assets/lifedrain/lang/en_us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"config.lifedrain.title": "LifeDrain Configuration",
"config.lifedrain.general": "General",
"config.lifedrain.enableParticles": "Enable Particles",
"config.lifedrain.lifestealCooldown": "Lifesteal Cooldown (ms)",
"config.lifedrain.baseHealEasy": "Base Heal (Easy)",
"config.lifedrain.baseHealNormal": "Base Heal (Normal)",
"config.lifedrain.baseHealHard": "Base Heal (Hard)",
"config.lifedrain.bonusHealMultiplier": "Bonus Heal Multiplier",
"config.lifedrain.enableParticles.tooltip": "This setting only affects the client. The server may override it. In single-player, values will work locally.",
"config.lifedrain.lifestealCooldown.tooltip": "Server-side cooldown value is used, and client settings are ignored. In single-player, values will work locally.",
"config.lifedrain.baseHealEasy.tooltip": "Server determines the base healing values. In single-player, values will work locally.",
"config.lifedrain.baseHealNormal.tooltip": "Server determines the base healing values. In single-player, values will work locally.",
"config.lifedrain.baseHealHard.tooltip": "Server determines the base healing values. In single-player, values will work locally.",
"config.lifedrain.bonusHealMultiplier.tooltip": "Server determines the bonus healing multiplier. In single-player, values will work locally."
}
16 changes: 16 additions & 0 deletions src/main/resources/assets/lifedrain/lang/es_es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"config.lifedrain.title": "Configuración de LifeDrain",
"config.lifedrain.general": "General",
"config.lifedrain.enableParticles": "Habilitar partículas",
"config.lifedrain.lifestealCooldown": "Tiempo de recarga de robar vida (ms)",
"config.lifedrain.baseHealEasy": "Curación base (Fácil)",
"config.lifedrain.baseHealNormal": "Curación base (Normal)",
"config.lifedrain.baseHealHard": "Curación base (Difícil)",
"config.lifedrain.bonusHealMultiplier": "Multiplicador de curación adicional",
"config.lifedrain.enableParticles.tooltip": "Esta opción solo afecta al cliente. El servidor puede sobrescribirla. En single-player, los valores funcionarán localmente.",
"config.lifedrain.lifestealCooldown.tooltip": "El valor de cooldown del servidor se usa, y los ajustes del cliente son ignorados. En single-player, los valores funcionarán localmente.",
"config.lifedrain.baseHealEasy.tooltip": "El servidor determina los valores base de curación. En single-player, los valores funcionarán localmente.",
"config.lifedrain.baseHealNormal.tooltip": "El servidor determina los valores base de curación. En single-player, los valores funcionarán localmente.",
"config.lifedrain.baseHealHard.tooltip": "El servidor determina los valores base de curación. En single-player, los valores funcionarán localmente.",
"config.lifedrain.bonusHealMultiplier.tooltip": "El servidor determina el multiplicador de curación adicional. En single-player, los valores funcionarán localmente."
}
16 changes: 16 additions & 0 deletions src/main/resources/assets/lifedrain/lang/es_mx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"config.lifedrain.title": "Configuración de LifeDrain",
"config.lifedrain.general": "General",
"config.lifedrain.enableParticles": "Activar partículas",
"config.lifedrain.lifestealCooldown": "Tiempo de recarga de robo de vida (ms)",
"config.lifedrain.baseHealEasy": "Curación base (Fácil)",
"config.lifedrain.baseHealNormal": "Curación base (Normal)",
"config.lifedrain.baseHealHard": "Curación base (Difícil)",
"config.lifedrain.bonusHealMultiplier": "Multiplicador de curación extra",
"config.lifedrain.enableParticles.tooltip": "Esta opción solo afecta al cliente. El servidor puede sobrescribirla. En single-player, los valores funcionarán localmente.",
"config.lifedrain.lifestealCooldown.tooltip": "El valor de cooldown del servidor se usa, y los ajustes del cliente son ignorados. En single-player, los valores funcionarán localmente.",
"config.lifedrain.baseHealEasy.tooltip": "El servidor determina los valores base de curación. En single-player, los valores funcionarán localmente.",
"config.lifedrain.baseHealNormal.tooltip": "El servidor determina los valores base de curación. En single-player, los valores funcionarán localmente.",
"config.lifedrain.baseHealHard.tooltip": "El servidor determina los valores base de curación. En single-player, los valores funcionarán localmente.",
"config.lifedrain.bonusHealMultiplier.tooltip": "El servidor determina el multiplicador de curación adicional. En single-player, los valores funcionarán localmente."
}
Loading

0 comments on commit 704d59f

Please sign in to comment.