This commit is contained in:
2026-07-26 14:20:08 +02:00
parent 84966a381d
commit 442d3d8a24
13 changed files with 565 additions and 28 deletions
@@ -33,6 +33,9 @@ public final class ConfigMM {
public final ForgeConfigSpec.IntValue TEMPO_BAR_Y_OFFSET;
public final ForgeConfigSpec.IntValue SANITY_BAR_X_OFFSET;
public final ForgeConfigSpec.IntValue SANITY_BAR_Y_OFFSET;
public final ForgeConfigSpec.BooleanValue ENABLE_MADNESS_BAR;
public final ForgeConfigSpec.IntValue MADNESS_BAR_X_OFFSET;
public final ForgeConfigSpec.IntValue MADNESS_BAR_Y_OFFSET;
Client(ForgeConfigSpec.Builder b) {
b.push("visual");
@@ -75,6 +78,18 @@ public final class ConfigMM {
.comment("Vertical offset for the sanity bar relative to its default position.")
.defineInRange("sanityBarYOffset", 0, -300, 300);
ENABLE_MADNESS_BAR = b
.comment("Show the top-center Madness danger bar while Madness is above zero.")
.define("enableMadnessBar", true);
MADNESS_BAR_X_OFFSET = b
.comment("Horizontal offset for the Madness bar relative to the centered default position.")
.defineInRange("madnessBarXOffset", 0, -300, 300);
MADNESS_BAR_Y_OFFSET = b
.comment("Vertical offset for the Madness bar relative to the top-center default position.")
.defineInRange("madnessBarYOffset", 0, -300, 300);
b.pop();
}
}
@@ -5,10 +5,62 @@ public class ClientData {
public static float INSANITY = 0f;
public static int TEMPO = 0;
public static int VENT_COOLDOWN = 0;
public static float MADNESS = 0f;
public static float MAX_MADNESS = 20f;
public static float MADNESS_DECAY_PER_TICK = 0.01f;
public static float MADNESS_DECAY_DELAY_TICKS = 0f;
private static float displayedSanity = 80f;
private static float displayedInsanity = 0f;
private static float displayedTempo = 0f;
private static float displayedMadness = 0f;
private static float madnessRenderTarget = 0f;
private static long lastMadnessRenderNanos = System.nanoTime();
// NEW: synced from server
public static float MAX_SANITY = 80f;
public static int MAX_TEMPO = 120;
public static boolean SANITY_ENABLED = true;
public static boolean TEMPO_ENABLED = true;
public static void setMadness(float madness) {
MADNESS = Math.max(0f, madness);
madnessRenderTarget = MADNESS;
}
public static void updateInterpolatedValues() {
long now = System.nanoTime();
float elapsedSeconds = Math.min(0.25f, (now - lastMadnessRenderNanos) / 1_000_000_000f);
lastMadnessRenderNanos = now;
float elapsedTicks = elapsedSeconds * 20f;
float pausedTicks = Math.min(elapsedTicks, MADNESS_DECAY_DELAY_TICKS);
MADNESS_DECAY_DELAY_TICKS -= pausedTicks;
float decayTicks = elapsedTicks - pausedTicks;
if (decayTicks > 0f) {
madnessRenderTarget = Math.max(0f,
madnessRenderTarget - decayTicks * MADNESS_DECAY_PER_TICK);
}
float lerp = 1f - (float) Math.exp(-10f * elapsedSeconds);
displayedSanity += (SANITY - displayedSanity) * lerp;
displayedInsanity += (INSANITY - displayedInsanity) * lerp;
displayedTempo += (TEMPO - displayedTempo) * lerp;
displayedMadness += (madnessRenderTarget - displayedMadness) * lerp;
}
public static float getDisplayedSanity() {
return displayedSanity;
}
public static float getDisplayedInsanity() {
return displayedInsanity;
}
public static float getDisplayedTempo() {
return displayedTempo;
}
public static float getDisplayedMadness() {
return displayedMadness;
}
}
@@ -21,6 +21,9 @@ public class HudEditorScreen extends Screen {
private static final int DEFAULT_OFFSET = 0;
private static final int BASE_TEMPO_X = 8;
private static final int BASE_Y_OFFSET = 48;
private static final int MADNESS_BAR_WIDTH = 182;
private static final int MADNESS_BAR_HEIGHT = 7;
private static final int BASE_MADNESS_Y = 13;
private final Screen previous;
@@ -30,15 +33,20 @@ public class HudEditorScreen extends Screen {
private int tempoOffsetY;
private int sanityOffsetX;
private int sanityOffsetY;
private int madnessOffsetX;
private int madnessOffsetY;
private boolean draggingTempo;
private boolean draggingSanity;
private boolean draggingMadness;
private double dragStartX;
private double dragStartY;
private int dragStartTempoX;
private int dragStartTempoY;
private int dragStartSanityX;
private int dragStartSanityY;
private int dragStartMadnessX;
private int dragStartMadnessY;
private IntSlider barWidthSlider;
private IntSlider barHeightSlider;
@@ -46,6 +54,8 @@ public class HudEditorScreen extends Screen {
private IntSlider tempoYSlider;
private IntSlider sanityXSlider;
private IntSlider sanityYSlider;
private IntSlider madnessXSlider;
private IntSlider madnessYSlider;
public HudEditorScreen(Screen previous) {
super(Component.translatable("screen.mindmotion.hud_editor.title"));
@@ -56,6 +66,8 @@ public class HudEditorScreen extends Screen {
this.tempoOffsetY = ConfigMM.CLIENT.TEMPO_BAR_Y_OFFSET.get();
this.sanityOffsetX = ConfigMM.CLIENT.SANITY_BAR_X_OFFSET.get();
this.sanityOffsetY = ConfigMM.CLIENT.SANITY_BAR_Y_OFFSET.get();
this.madnessOffsetX = ConfigMM.CLIENT.MADNESS_BAR_X_OFFSET.get();
this.madnessOffsetY = ConfigMM.CLIENT.MADNESS_BAR_Y_OFFSET.get();
}
@Override
@@ -141,6 +153,22 @@ public class HudEditorScreen extends Screen {
sanityOffsetY,
value -> setSanityOffsetY(value, false)
));
y += step;
}
if (isMadnessEnabled()) {
madnessXSlider = addRenderableWidget(new IntSlider(
leftX, y, sliderWidth, 20,
"screen.mindmotion.hud_editor.madness_x",
MIN_OFFSET, MAX_OFFSET, madnessOffsetX,
value -> setMadnessOffsetX(value, false)
));
madnessYSlider = addRenderableWidget(new IntSlider(
rightX, y, sliderWidth, 20,
"screen.mindmotion.hud_editor.madness_y",
MIN_OFFSET, MAX_OFFSET, madnessOffsetY,
value -> setMadnessOffsetY(value, false)
));
}
int buttonY = this.height - 28;
@@ -193,6 +221,15 @@ public class HudEditorScreen extends Screen {
dragStartSanityY = sanityOffsetY;
return true;
}
BarPosition madnessBar = getMadnessBarPosition();
if (isMadnessEnabled() && isMouseOver(mouseX, mouseY, madnessBar)) {
draggingMadness = true;
dragStartX = mouseX;
dragStartY = mouseY;
dragStartMadnessX = madnessOffsetX;
dragStartMadnessY = madnessOffsetY;
return true;
}
}
return super.mouseClicked(mouseX, mouseY, button);
}
@@ -214,6 +251,13 @@ public class HudEditorScreen extends Screen {
setSanityOffsetY(newY, true);
return true;
}
if (draggingMadness) {
int newX = clamp(dragStartMadnessX + (int) Math.round(mouseX - dragStartX), MIN_OFFSET, MAX_OFFSET);
int newY = clamp(dragStartMadnessY + (int) Math.round(mouseY - dragStartY), MIN_OFFSET, MAX_OFFSET);
setMadnessOffsetX(newX, true);
setMadnessOffsetY(newY, true);
return true;
}
}
return super.mouseDragged(mouseX, mouseY, button, dragX, dragY);
}
@@ -223,6 +267,7 @@ public class HudEditorScreen extends Screen {
if (button == 0) {
draggingTempo = false;
draggingSanity = false;
draggingMadness = false;
}
return super.mouseReleased(mouseX, mouseY, button);
}
@@ -237,12 +282,13 @@ public class HudEditorScreen extends Screen {
private void renderPreview(GuiGraphics gui) {
BarPosition tempoBar = getTempoBarPosition();
BarPosition sanityBar = getSanityBarPosition();
BarPosition madnessBar = getMadnessBarPosition();
if (isTempoEnabled()) {
drawRoundedBar(gui, tempoBar, 0xFF33AFFF, (int) (barWidth * 0.75f));
gui.drawString(this.font, Component.literal("Tempo"), tempoBar.x, tempoBar.y - 12, 0x66CCFF, false);
}
if (isSanityEnabled()) {
drawRoundedBar(gui, sanityBar, 0xFFFFFFFF, (int) (barWidth * 0.6f));
drawRoundedBar(gui, sanityBar, 0xFFE8E8E8, (int) (barWidth * 0.6f));
int sanityLabelWidth = this.font.width("Sanity");
gui.drawString(
this.font,
@@ -253,6 +299,11 @@ public class HudEditorScreen extends Screen {
false
);
}
if (isMadnessEnabled()) {
drawBossStyleBar(gui, madnessBar, 0.72f);
gui.drawCenteredString(this.font, Component.literal("Madness"),
madnessBar.x + madnessBar.width / 2, madnessBar.y - 11, 0xFFFFFF55);
}
}
private BarPosition getTempoBarPosition() {
@@ -276,6 +327,15 @@ public class HudEditorScreen extends Screen {
);
}
private BarPosition getMadnessBarPosition() {
return new BarPosition(
(this.width - MADNESS_BAR_WIDTH) / 2 + madnessOffsetX,
BASE_MADNESS_Y + madnessOffsetY,
MADNESS_BAR_WIDTH,
MADNESS_BAR_HEIGHT
);
}
private void setBarWidth(int value, boolean updateSlider) {
barWidth = clamp(value, MIN_BAR_WIDTH, MAX_BAR_WIDTH);
ConfigMM.CLIENT.BAR_WIDTH.set(barWidth);
@@ -324,6 +384,22 @@ public class HudEditorScreen extends Screen {
}
}
private void setMadnessOffsetX(int value, boolean updateSlider) {
madnessOffsetX = clamp(value, MIN_OFFSET, MAX_OFFSET);
ConfigMM.CLIENT.MADNESS_BAR_X_OFFSET.set(madnessOffsetX);
if (updateSlider && madnessXSlider != null) {
madnessXSlider.setIntValue(madnessOffsetX);
}
}
private void setMadnessOffsetY(int value, boolean updateSlider) {
madnessOffsetY = clamp(value, MIN_OFFSET, MAX_OFFSET);
ConfigMM.CLIENT.MADNESS_BAR_Y_OFFSET.set(madnessOffsetY);
if (updateSlider && madnessYSlider != null) {
madnessYSlider.setIntValue(madnessOffsetY);
}
}
private void resetDefaults() {
setBarWidth(DEFAULT_BAR_WIDTH, true);
setBarHeight(DEFAULT_BAR_HEIGHT, true);
@@ -331,6 +407,8 @@ public class HudEditorScreen extends Screen {
setTempoOffsetY(DEFAULT_OFFSET, true);
setSanityOffsetX(DEFAULT_OFFSET, true);
setSanityOffsetY(DEFAULT_OFFSET, true);
setMadnessOffsetX(DEFAULT_OFFSET, true);
setMadnessOffsetY(DEFAULT_OFFSET, true);
}
private void saveConfig() {
@@ -345,6 +423,10 @@ public class HudEditorScreen extends Screen {
return ConfigMM.COMMON.ENABLE_SANITY.get() && ClientData.SANITY_ENABLED;
}
private static boolean isMadnessEnabled() {
return isSanityEnabled() && ConfigMM.CLIENT.ENABLE_MADNESS_BAR.get();
}
private static int clamp(int value, int min, int max) {
return Math.min(Math.max(value, min), max);
}
@@ -355,6 +437,31 @@ public class HudEditorScreen extends Screen {
int top = color;
int bottom = (color & 0x00FFFFFF) | 0xFF000000;
drawRoundedRect(gui, pos.x, pos.y, fillWidth, pos.height, top, bottom);
if (fillWidth > 2) {
gui.fill(pos.x + 1, pos.y, pos.x + fillWidth - 1, pos.y + 1, lightenColor(color, 0.18f));
}
}
private void drawBossStyleBar(GuiGraphics gui, BarPosition pos, float progress) {
gui.fill(pos.x, pos.y, pos.x + pos.width, pos.y + pos.height, 0xFF000000);
gui.fill(pos.x + 1, pos.y + 1, pos.x + pos.width - 1, pos.y + pos.height - 1, 0xFF4A4000);
int fillWidth = Math.round((pos.width - 2) * progress);
int fillColor = mixColor(0xFFAD8D2C, 0xFFFFF4B0, progress);
gui.fillGradient(pos.x + 1, pos.y + 1, pos.x + 1 + fillWidth, pos.y + pos.height - 1,
lightenColor(fillColor, 0.16f), fillColor);
}
private static int mixColor(int from, int to, float amount) {
float t = Math.max(0f, Math.min(1f, amount));
int a = Math.round(((from >>> 24) & 0xFF) + (((to >>> 24) & 0xFF) - ((from >>> 24) & 0xFF)) * t);
int r = Math.round(((from >>> 16) & 0xFF) + (((to >>> 16) & 0xFF) - ((from >>> 16) & 0xFF)) * t);
int g = Math.round(((from >>> 8) & 0xFF) + (((to >>> 8) & 0xFF) - ((from >>> 8) & 0xFF)) * t);
int b = Math.round((from & 0xFF) + ((to & 0xFF) - (from & 0xFF)) * t);
return (a << 24) | (r << 16) | (g << 8) | b;
}
private static int lightenColor(int color, float amount) {
return mixColor(color, (color & 0xFF000000) | 0x00FFFFFF, amount);
}
private void drawRoundedRect(GuiGraphics gui, int x, int y, int width, int height, int color) {
@@ -22,7 +22,9 @@ public class SanityTempoOverlay {
if (mc.player == null || mc.options.hideGui) return;
boolean tempoEnabled = ConfigMM.COMMON.ENABLE_TEMPO.get() && ClientData.TEMPO_ENABLED;
boolean sanityEnabled = ConfigMM.COMMON.ENABLE_SANITY.get() && ClientData.SANITY_ENABLED;
boolean madnessEnabled = sanityEnabled && ConfigMM.CLIENT.ENABLE_MADNESS_BAR.get();
if (!tempoEnabled && !sanityEnabled) return;
ClientData.updateInterpolatedValues();
GuiGraphics gui = event.getGuiGraphics();
int width = event.getWindow().getGuiScaledWidth();
int height = event.getWindow().getGuiScaledHeight();
@@ -37,15 +39,19 @@ public class SanityTempoOverlay {
int yTempo = baseY + ConfigMM.CLIENT.TEMPO_BAR_Y_OFFSET.get();
int xSanity = baseSanityX + ConfigMM.CLIENT.SANITY_BAR_X_OFFSET.get();
int ySanity = baseY + ConfigMM.CLIENT.SANITY_BAR_Y_OFFSET.get();
float sanity = ClientData.SANITY;
float insanity = ClientData.INSANITY;
float tempo = ClientData.TEMPO;
float sanityPercent = sanity / ClientData.MAX_SANITY;
float insanityPercent = insanity / ClientData.MAX_SANITY;
float tempoPercent = (float) tempo / (float) ClientData.MAX_TEMPO;
int sanityFill = (int) (barWidth * sanityPercent);
int tempoFill = (int) (barWidth * tempoPercent);
int insanityFill = (int) (barWidth * insanityPercent);
float sanity = ClientData.getDisplayedSanity();
float insanity = ClientData.getDisplayedInsanity();
float madness = ClientData.getDisplayedMadness();
float tempo = ClientData.getDisplayedTempo();
float sanityPercent = clamp01(sanity / ClientData.MAX_SANITY);
float insanityPercent = clamp01(insanity / ClientData.MAX_SANITY);
float tempoPercent = clamp01(tempo / ClientData.MAX_TEMPO);
int sanityFill = Math.round(barWidth * sanityPercent);
int tempoFill = Math.round(barWidth * tempoPercent);
int insanityFill = Math.round(barWidth * insanityPercent);
double animationTime = System.nanoTime() / 1_000_000_000.0;
double mouseX = mc.mouseHandler.xpos() * width / (double) event.getWindow().getScreenWidth();
double mouseY = mc.mouseHandler.ypos() * height / (double) event.getWindow().getScreenHeight();
RenderSystem.enableBlend();
if (tempoEnabled) {
drawRoundedBar(gui, xTempo, yTempo, barWidth, barHeight, 0xFF33AFFF, tempoFill);
@@ -63,25 +69,47 @@ public class SanityTempoOverlay {
false
);
}
boolean insaneMode = sanity <= 0;
boolean insaneMode = ClientData.SANITY <= 0f;
boolean showMadness = ClientData.MADNESS > 0f;
if (sanityEnabled) {
if (!insaneMode) {
drawRoundedBar(gui, xSanity, ySanity, barWidth, barHeight, 0xFFFFFFFF, sanityFill);
drawRoundedBar(gui, xSanity, ySanity, barWidth, barHeight, 0xFFE8E8E8, sanityFill);
int sanityLabelWidth = mc.font.width("Sanity");
int sanityLabelX = xSanity + barWidth - sanityLabelWidth;
gui.drawString(mc.font, Component.literal("Sanity"), sanityLabelX, ySanity - labelOffset, 0xFFFFFF, false);
} else {
int purple = 0xFFAA33FF;
int darkPurple = 0xFF6611AA;
float insanityPulse = 0.92f + 0.08f * (0.5f + 0.5f * (float) Math.sin(animationTime * 3.5));
int purple = scaleColor(0xFFAA33FF, insanityPulse);
int darkPurple = scaleColor(0xFF6611AA, insanityPulse);
drawRoundedRect(gui, xSanity, ySanity, barWidth, barHeight, 0xDD000000);
drawRoundedRect(gui, xSanity, ySanity, insanityFill, barHeight, purple, darkPurple);
drawTopHighlight(gui, xSanity, ySanity, insanityFill, lightenColor(purple, 0.18f));
int insanityLabelWidth = mc.font.width("Insanity");
int insanityLabelX = xSanity + barWidth - insanityLabelWidth;
gui.drawString(mc.font, Component.literal("Insanity"), insanityLabelX, ySanity - labelOffset, 0xCC66FF, false);
}
}
double mouseX = mc.mouseHandler.xpos() * width / (double) event.getWindow().getScreenWidth();
double mouseY = mc.mouseHandler.ypos() * height / (double) event.getWindow().getScreenHeight();
if (madnessEnabled && showMadness) {
int madnessBarWidth = 182;
int madnessBarHeight = 7;
int madnessX = (width - madnessBarWidth) / 2 + ConfigMM.CLIENT.MADNESS_BAR_X_OFFSET.get();
int madnessY = 13 + ConfigMM.CLIENT.MADNESS_BAR_Y_OFFSET.get();
float madnessPercent = ClientData.MAX_MADNESS <= 0f
? 0f
: clamp01(madness / ClientData.MAX_MADNESS);
int shakeX = madnessPercent >= 0.85f ? urgentShake(animationTime) : 0;
int renderedMadnessX = madnessX + shakeX;
drawBossStyleBar(gui, renderedMadnessX, madnessY, madnessBarWidth, madnessBarHeight,
madnessPercent, animationTime);
gui.drawCenteredString(mc.font, Component.literal("Madness"),
madnessX + madnessBarWidth / 2, madnessY - 11, 0xFFFFFF55);
if (isMouseOver(mouseX, mouseY, renderedMadnessX, madnessY, madnessBarWidth, madnessBarHeight)) {
String text = String.format("%.0f%%", madnessPercent * 100f);
gui.drawCenteredString(mc.font, text, madnessX + madnessBarWidth / 2,
madnessY + madnessBarHeight + 3, 0xFFFFFF88);
}
}
if (tempoEnabled && isMouseOver(mouseX, mouseY, xTempo, yTempo, barWidth, barHeight)) {
String text = String.format("%.0f%%", tempoPercent * 100f);
gui.drawCenteredString(mc.font, text, xTempo + barWidth / 2, yTempo - (labelOffset + 2), 0x66CCFF);
@@ -100,18 +128,84 @@ public class SanityTempoOverlay {
private static void drawRoundedBar(GuiGraphics gui, int x, int y, int width, int height, int color, int fillWidth) {
int bg = 0xCC000000;
drawRoundedRect(gui, x, y, width, height, bg);
int top = color;
int bottom = (color & 0x00FFFFFF) | 0xFF000000;
drawRoundedRect(gui, x, y, fillWidth, height, top, bottom);
if (fillWidth <= 0) return;
int clampedFill = Math.min(width, fillWidth);
drawRoundedRect(gui, x, y, clampedFill, height, color, scaleColor(color, 0.78f));
drawTopHighlight(gui, x, y, clampedFill, lightenColor(color, 0.18f));
}
private static void drawBossStyleBar(GuiGraphics gui, int x, int y, int width, int height,
float progress, double animationTime) {
gui.fill(x, y, x + width, y + height, 0xFF000000);
gui.fill(x + 1, y + 1, x + width - 1, y + height - 1, 0xFF4A4000);
int innerWidth = width - 2;
int fillWidth = Math.round(innerWidth * progress);
if (fillWidth > 0) {
int fillColor = mixColor(0xFFAD8D2C, 0xFFFFF4B0, progress);
if (progress >= 0.85f) {
float urgentPulse = 1f + 0.16f
* (0.5f + 0.5f * (float) Math.sin(animationTime * 13.0));
fillColor = scaleColor(fillColor, urgentPulse);
}
gui.fillGradient(x + 1, y + 1, x + 1 + fillWidth, y + height - 1,
lightenColor(fillColor, 0.16f), scaleColor(fillColor, 0.78f));
}
}
private static void drawTopHighlight(GuiGraphics gui, int x, int y, int fillWidth, int color) {
if (fillWidth <= 2) return;
gui.fill(x + 1, y, x + fillWidth - 1, y + 1, color);
}
private static int urgentShake(double animationTime) {
long step = (long) (animationTime * 24.0);
return (int) Math.floorMod(step * 31L + 17L, 3L) - 1;
}
private static int mixColor(int from, int to, float amount) {
float t = clamp01(amount);
int a = Math.round(((from >>> 24) & 0xFF) + (((to >>> 24) & 0xFF) - ((from >>> 24) & 0xFF)) * t);
int r = Math.round(((from >>> 16) & 0xFF) + (((to >>> 16) & 0xFF) - ((from >>> 16) & 0xFF)) * t);
int g = Math.round(((from >>> 8) & 0xFF) + (((to >>> 8) & 0xFF) - ((from >>> 8) & 0xFF)) * t);
int b = Math.round((from & 0xFF) + ((to & 0xFF) - (from & 0xFF)) * t);
return (a << 24) | (r << 16) | (g << 8) | b;
}
private static int scaleColor(int color, float factor) {
int a = (color >>> 24) & 0xFF;
int r = Math.min(255, Math.max(0, Math.round(((color >>> 16) & 0xFF) * factor)));
int g = Math.min(255, Math.max(0, Math.round(((color >>> 8) & 0xFF) * factor)));
int b = Math.min(255, Math.max(0, Math.round((color & 0xFF) * factor)));
return (a << 24) | (r << 16) | (g << 8) | b;
}
private static int lightenColor(int color, float amount) {
return mixColor(color, (color & 0xFF000000) | 0x00FFFFFF, amount);
}
private static float clamp01(float value) {
return Math.max(0f, Math.min(1f, value));
}
private static void drawRoundedRect(GuiGraphics gui, int x, int y, int width, int height, int color) {
if (width <= 0 || height <= 0) return;
if (width <= 2 || height <= 2) {
gui.fill(x, y, x + width, y + height, color);
return;
}
gui.fill(x + 1, y, x + width - 1, y + height, color); // center
gui.fill(x, y + 1, x + width, y + height - 1, color); // sides
gui.fill(x + 1, y + 1, x + width - 1, y + height - 1, color);
}
private static void drawRoundedRect(GuiGraphics gui, int x, int y, int width, int height, int topColor, int bottomColor) {
if (width <= 0 || height <= 0) return;
if (width <= 2 || height <= 2) {
gui.fillGradient(x, y, x + width, y + height, topColor, bottomColor);
return;
}
gui.fillGradient(x + 1, y, x + width - 1, y + height, topColor, bottomColor);
gui.fillGradient(x, y + 1, x + width, y + height - 1, topColor, bottomColor);
}
@@ -21,6 +21,8 @@ public final class AscendCompat {
player.getCapability(PlayerCapabilityProvider.SANITY).ifPresent(cap -> {
if (cap instanceof SanityCapability impl) {
impl.setBonusMaxSanity(sanityBonus);
impl.setBonusMaxMadness(willpower * 0.5f);
impl.setBonusMadnessDecayPerTick(willpower * 0.001f);
}
});
}
@@ -1,16 +1,24 @@
package net.fretux.mindmotion.event;
import net.fretux.mindmotion.ConfigMM;
import net.fretux.mindmotion.compat.IronsSpellbooksCompat;
import net.fretux.mindmotion.damage.ModDamageTypes;
import net.fretux.mindmotion.player.PlayerCapabilityProvider;
import net.fretux.mindmotion.player.SanityCapability;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.npc.Villager;
import net.minecraftforge.event.entity.living.LivingDamageEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModList;
public class CommonEvents {
private static final int MADNESS_DECAY_DELAY_TICKS = 2 * 20;
@SubscribeEvent
public void onPlayerHurt(LivingHurtEvent event) {
@@ -22,6 +30,64 @@ public class CommonEvents {
player.getCapability(PlayerCapabilityProvider.SANITY).ifPresent(sanity -> sanity.reduceSanity(loss));
}
@SubscribeEvent
public void onPlayerDamaged(LivingDamageEvent event) {
if (!(event.getEntity() instanceof ServerPlayer player)) return;
if (!ConfigMM.COMMON.ENABLE_SANITY.get()) return;
if (!event.getSource().is(ModDamageTypes.TERROR) || event.getAmount() <= 0f) return;
player.getCapability(PlayerCapabilityProvider.SANITY).ifPresent(sanity -> {
// One heart is two health points, so "2 x hearts taken" equals the
// final post-mitigation health-point damage reported by this event.
sanity.setMadness(sanity.getMadness() + event.getAmount());
sanity.setMadnessDecayDelayTicks(MADNESS_DECAY_DELAY_TICKS);
if (sanity.getMadness() < sanity.getMaxMadness()) return;
sanity.setMadness(0f);
// Defer the backlash until the next player tick. Calling hurt from
// inside LivingDamageEvent can complete two nested lethal hits and
// broadcast both of their death messages.
sanity.setMadnessBacklashPending(true);
});
}
public static void triggerPendingMadnessBacklash(ServerPlayer player) {
player.getCapability(PlayerCapabilityProvider.SANITY).ifPresent(sanity -> {
if (!sanity.isMadnessBacklashPending()) return;
sanity.setMadnessBacklashPending(false);
if (!player.isAlive()) return;
sanity.setMadnessStunTicks(30);
triggerMadnessBacklash(player);
});
}
private static void triggerMadnessBacklash(ServerPlayer player) {
if (!player.isAlive()) return;
player.stopUsingItem();
player.setSprinting(false);
player.setDeltaMovement(0.0, 0.0, 0.0);
player.hurtMarked = true;
player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 30, 255, false, false));
float backlashDamage = player.getMaxHealth() * 0.30f;
player.hurt(ModDamageTypes.madnessBacklash(player.level()), backlashDamage);
int remainingFood = Math.max(0, (int) Math.floor(player.getFoodData().getFoodLevel() * 0.5f));
player.getFoodData().setFoodLevel(remainingFood);
player.getFoodData().setSaturation(Math.min(player.getFoodData().getSaturationLevel(), remainingFood));
if (ModList.get().isLoaded("irons_spellbooks")) {
try {
IronsSpellbooksCompat.reduceMana(player, 0.30f);
} catch (LinkageError | RuntimeException ignored) {
// Optional integrations must never break the backlash itself.
}
}
}
@SubscribeEvent
public void onLivingDeath(LivingDeathEvent event) {
if (!(event.getSource().getEntity() instanceof net.minecraft.world.entity.player.Player player)) return;
@@ -46,13 +112,24 @@ public class CommonEvents {
if (newCap instanceof SanityCapability newImpl && oldCap instanceof SanityCapability oldImpl) {
newImpl.setBaseMaxSanity(oldImpl.getBaseMaxSanity());
newImpl.setBonusMaxSanity(oldImpl.getBonusMaxSanity());
newImpl.setBaseMaxMadness(oldImpl.getBaseMaxMadness());
newImpl.setBonusMaxMadness(oldImpl.getBonusMaxMadness());
newImpl.setBonusMadnessDecayPerTick(oldImpl.getBonusMadnessDecayPerTick());
}
if (event.isWasDeath()) {
newCap.setInsanity(0f);
newCap.setSanity(newCap.getMaxSanity());
newCap.setMadness(0f);
newCap.setMadnessDecayDelayTicks(0);
newCap.setMadnessStunTicks(0);
newCap.setMadnessBacklashPending(false);
} else {
newCap.setSanity(oldCap.getSanity());
newCap.setInsanity(oldCap.getInsanity());
newCap.setMadness(oldCap.getMadness());
newCap.setMadnessDecayDelayTicks(oldCap.getMadnessDecayDelayTicks());
newCap.setMadnessStunTicks(oldCap.getMadnessStunTicks());
newCap.setMadnessBacklashPending(oldCap.isMadnessBacklashPending());
}
})
);
@@ -1,6 +1,7 @@
package net.fretux.mindmotion.event;
import net.fretux.mindmotion.ConfigMM;
import net.fretux.mindmotion.damage.ModDamageTypes;
import net.fretux.mindmotion.network.ModMessages;
import net.fretux.mindmotion.network.SyncStatsS2CPacket;
import net.fretux.mindmotion.player.PlayerCapabilityProvider;
@@ -81,6 +82,11 @@ public class PlayerTickHandler {
boolean sanityEnabled = ConfigMM.COMMON.ENABLE_SANITY.get();
boolean tempoEnabled = ConfigMM.COMMON.ENABLE_TEMPO.get();
handleMadnessStun(player);
if (player instanceof ServerPlayer serverPlayer) {
CommonEvents.triggerPendingMadnessBacklash(serverPlayer);
}
handleMadnessDecay(player);
if (sanityEnabled && sanityTick) handleSanityAndInsanity(player);
if (tempoEnabled) {
handleTempoTick(player, tick);
@@ -100,7 +106,11 @@ public class PlayerTickHandler {
sanity.getMaxSanity(),
tempo.getMaxTempo(),
sanityEnabled,
tempoEnabled
tempoEnabled,
sanity.getMadness(),
sanity.getMaxMadness(),
sanity.getMadnessDecayPerTick(),
sanity.getMadnessDecayDelayTicks()
),
serverPlayer.connection.connection,
net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT
@@ -216,7 +226,7 @@ public class PlayerTickHandler {
int ticks = insanityDamageTicks.getOrDefault(id, 0) + SANITY_TICK_INTERVAL;
insanityDamageTicks.put(id, ticks);
float damage = 1.0f + (ticks / 100f);
player.hurt(player.damageSources().magic(), damage);
player.hurt(ModDamageTypes.terror(player.level()), damage);
player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 40, 1));
return;
}
@@ -289,6 +299,32 @@ public class PlayerTickHandler {
});
}
private static void handleMadnessStun(Player player) {
player.getCapability(PlayerCapabilityProvider.SANITY).ifPresent(sanity -> {
int stunTicks = sanity.getMadnessStunTicks();
if (stunTicks <= 0) return;
sanity.setMadnessStunTicks(stunTicks - 1);
player.stopUsingItem();
player.setSprinting(false);
player.setDeltaMovement(0.0, 0.0, 0.0);
player.hurtMarked = true;
});
}
private static void handleMadnessDecay(Player player) {
player.getCapability(PlayerCapabilityProvider.SANITY).ifPresent(sanity -> {
int delayTicks = sanity.getMadnessDecayDelayTicks();
if (delayTicks > 0) {
sanity.setMadnessDecayDelayTicks(delayTicks - 1);
return;
}
if (sanity.getMadness() > 0f) {
sanity.setMadness(sanity.getMadness() - sanity.getMadnessDecayPerTick());
}
});
}
private static float getSurvivalSanityDelta(Player player, int light) {
float delta = 0f;
float healthPercent = player.getHealth() / player.getMaxHealth();
@@ -483,14 +519,14 @@ public class PlayerTickHandler {
private static void applyPanic(Player player) {
player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 20, 1));
player.hurt(player.damageSources().magic(), 1f);
player.hurt(ModDamageTypes.terror(player.level()), 1f);
applyShiver(player);
applyVertigo(player);
}
private static void applyScratching(Player player) {
player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 30, 1));
player.hurt(player.damageSources().magic(), 2f);
player.hurt(ModDamageTypes.terror(player.level()), 2f);
applyShiver(player);
applyDread(player);
}
@@ -9,7 +9,7 @@ import net.fretux.mindmotion.AscendMindMotion;
public class ModMessages {
private static final String PROTOCOL_VERSION = "2";
private static final String PROTOCOL_VERSION = "5";
public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel(
ResourceLocation.fromNamespaceAndPath(AscendMindMotion.MODID, "main"),
() -> PROTOCOL_VERSION,
@@ -15,6 +15,10 @@ public class SyncStatsS2CPacket {
private final int maxTempo;
private final boolean sanityEnabled;
private final boolean tempoEnabled;
private final float madness;
private final float maxMadness;
private final float madnessDecayPerTick;
private final int madnessDecayDelayTicks;
public SyncStatsS2CPacket(float sanity,
float insanity,
@@ -23,7 +27,11 @@ public class SyncStatsS2CPacket {
float maxSanity,
int maxTempo,
boolean sanityEnabled,
boolean tempoEnabled) {
boolean tempoEnabled,
float madness,
float maxMadness,
float madnessDecayPerTick,
int madnessDecayDelayTicks) {
this.sanity = sanity;
this.insanity = insanity;
this.tempo = tempo;
@@ -32,6 +40,10 @@ public class SyncStatsS2CPacket {
this.maxTempo = maxTempo;
this.sanityEnabled = sanityEnabled;
this.tempoEnabled = tempoEnabled;
this.madness = madness;
this.maxMadness = maxMadness;
this.madnessDecayPerTick = madnessDecayPerTick;
this.madnessDecayDelayTicks = madnessDecayDelayTicks;
}
public static void encode(SyncStatsS2CPacket msg, FriendlyByteBuf buf) {
@@ -43,6 +55,10 @@ public class SyncStatsS2CPacket {
buf.writeInt(msg.maxTempo);
buf.writeBoolean(msg.sanityEnabled);
buf.writeBoolean(msg.tempoEnabled);
buf.writeFloat(msg.madness);
buf.writeFloat(msg.maxMadness);
buf.writeFloat(msg.madnessDecayPerTick);
buf.writeInt(msg.madnessDecayDelayTicks);
}
public static SyncStatsS2CPacket decode(FriendlyByteBuf buf) {
@@ -54,7 +70,12 @@ public class SyncStatsS2CPacket {
int maxTempo = buf.readInt();
boolean sanityEnabled = buf.readBoolean();
boolean tempoEnabled = buf.readBoolean();
return new SyncStatsS2CPacket(sanity, insanity, tempo, ventCooldown, maxSanity, maxTempo, sanityEnabled, tempoEnabled);
float madness = buf.readFloat();
float maxMadness = buf.readFloat();
float madnessDecayPerTick = buf.readFloat();
int madnessDecayDelayTicks = buf.readInt();
return new SyncStatsS2CPacket(sanity, insanity, tempo, ventCooldown, maxSanity, maxTempo,
sanityEnabled, tempoEnabled, madness, maxMadness, madnessDecayPerTick, madnessDecayDelayTicks);
}
public static void handle(SyncStatsS2CPacket msg, Supplier<NetworkEvent.Context> ctx) {
@@ -67,6 +88,10 @@ public class SyncStatsS2CPacket {
ClientData.MAX_TEMPO = msg.maxTempo;
ClientData.SANITY_ENABLED = msg.sanityEnabled;
ClientData.TEMPO_ENABLED = msg.tempoEnabled;
ClientData.setMadness(msg.madness);
ClientData.MAX_MADNESS = msg.maxMadness;
ClientData.MADNESS_DECAY_PER_TICK = msg.madnessDecayPerTick;
ClientData.MADNESS_DECAY_DELAY_TICKS = msg.madnessDecayDelayTicks;
});
ctx.get().setPacketHandled(true);
@@ -78,7 +103,11 @@ public class SyncStatsS2CPacket {
", MaxSanity: " + msg.maxSanity +
", MaxTempo: " + msg.maxTempo +
", SanityEnabled: " + msg.sanityEnabled +
", TempoEnabled: " + msg.tempoEnabled
", TempoEnabled: " + msg.tempoEnabled +
", Madness: " + msg.madness +
", MaxMadness: " + msg.maxMadness
+ ", MadnessDecayPerTick: " + msg.madnessDecayPerTick +
", MadnessDecayDelayTicks: " + msg.madnessDecayDelayTicks
);
}
}
@@ -6,6 +6,16 @@ public interface ISanity {
float getInsanity();
void setInsanity(float insanity);
float getMaxSanity();
float getMadness();
void setMadness(float madness);
float getMaxMadness();
float getMadnessDecayPerTick();
int getMadnessDecayDelayTicks();
void setMadnessDecayDelayTicks(int ticks);
int getMadnessStunTicks();
void setMadnessStunTicks(int ticks);
boolean isMadnessBacklashPending();
void setMadnessBacklashPending(boolean pending);
void addSanity(float amount);
void reduceSanity(float amount);
}
@@ -44,6 +44,13 @@ public class PlayerCapabilityProvider {
tag.putFloat("BonusMaxSanity", instance.getBonusMaxSanity());
tag.putFloat("Sanity", instance.getSanity());
tag.putFloat("Insanity", instance.getInsanity());
tag.putFloat("BaseMaxMadness", instance.getBaseMaxMadness());
tag.putFloat("BonusMaxMadness", instance.getBonusMaxMadness());
tag.putFloat("BonusMadnessDecayPerTick", instance.getBonusMadnessDecayPerTick());
tag.putFloat("Madness", instance.getMadness());
tag.putInt("MadnessDecayDelayTicks", instance.getMadnessDecayDelayTicks());
tag.putInt("MadnessStunTicks", instance.getMadnessStunTicks());
tag.putBoolean("MadnessBacklashPending", instance.isMadnessBacklashPending());
return tag;
}
@@ -55,8 +62,29 @@ public class PlayerCapabilityProvider {
if (nbt.contains("BonusMaxSanity")) {
instance.setBonusMaxSanity(nbt.getFloat("BonusMaxSanity"));
}
if (nbt.contains("BaseMaxMadness")) {
instance.setBaseMaxMadness(nbt.getFloat("BaseMaxMadness"));
}
if (nbt.contains("BonusMaxMadness")) {
instance.setBonusMaxMadness(nbt.getFloat("BonusMaxMadness"));
}
if (nbt.contains("BonusMadnessDecayPerTick")) {
instance.setBonusMadnessDecayPerTick(nbt.getFloat("BonusMadnessDecayPerTick"));
}
instance.setSanity(nbt.getFloat("Sanity"));
instance.setInsanity(nbt.getFloat("Insanity"));
if (nbt.contains("Madness")) {
instance.setMadness(nbt.getFloat("Madness"));
}
if (nbt.contains("MadnessDecayDelayTicks")) {
instance.setMadnessDecayDelayTicks(nbt.getInt("MadnessDecayDelayTicks"));
}
if (nbt.contains("MadnessStunTicks")) {
instance.setMadnessStunTicks(nbt.getInt("MadnessStunTicks"));
}
if (nbt.contains("MadnessBacklashPending")) {
instance.setMadnessBacklashPending(nbt.getBoolean("MadnessBacklashPending"));
}
}
}
@@ -5,6 +5,13 @@ public class SanityCapability implements ISanity {
private float insanity = 0f;
private float baseMaxSanity = 80f;
private float bonusMaxSanity = 0f;
private float madness = 0f;
private float baseMaxMadness = 20f;
private float bonusMaxMadness = 0f;
private float bonusMadnessDecayPerTick = 0f;
private int madnessDecayDelayTicks = 0;
private int madnessStunTicks = 0;
private boolean madnessBacklashPending = false;
@Override
public float getSanity() {
@@ -41,6 +48,56 @@ public class SanityCapability implements ISanity {
return baseMaxSanity + bonusMaxSanity;
}
@Override
public float getMadness() {
return madness;
}
@Override
public void setMadness(float madness) {
this.madness = Math.max(0f, Math.min(getMaxMadness(), madness));
}
@Override
public float getMaxMadness() {
return baseMaxMadness + bonusMaxMadness;
}
@Override
public float getMadnessDecayPerTick() {
return 0.01f + bonusMadnessDecayPerTick;
}
@Override
public int getMadnessDecayDelayTicks() {
return madnessDecayDelayTicks;
}
@Override
public void setMadnessDecayDelayTicks(int ticks) {
this.madnessDecayDelayTicks = Math.max(0, ticks);
}
@Override
public int getMadnessStunTicks() {
return madnessStunTicks;
}
@Override
public void setMadnessStunTicks(int ticks) {
this.madnessStunTicks = Math.max(0, ticks);
}
@Override
public boolean isMadnessBacklashPending() {
return madnessBacklashPending;
}
@Override
public void setMadnessBacklashPending(boolean pending) {
this.madnessBacklashPending = pending;
}
@Override
public void addSanity(float amount) {
setSanity(this.sanity + amount);
@@ -70,6 +127,32 @@ public class SanityCapability implements ISanity {
return bonusMaxSanity;
}
public void setBaseMaxMadness(float baseMaxMadness) {
this.baseMaxMadness = Math.max(1f, baseMaxMadness);
setMadness(this.madness);
}
public float getBaseMaxMadness() {
return baseMaxMadness;
}
public void setBonusMaxMadness(float bonusMaxMadness) {
this.bonusMaxMadness = Math.max(0f, bonusMaxMadness);
setMadness(this.madness);
}
public float getBonusMaxMadness() {
return bonusMaxMadness;
}
public void setBonusMadnessDecayPerTick(float bonusMadnessDecayPerTick) {
this.bonusMadnessDecayPerTick = Math.max(0f, bonusMadnessDecayPerTick);
}
public float getBonusMadnessDecayPerTick() {
return bonusMadnessDecayPerTick;
}
private void clampToMax() {
float max = getMaxSanity();
this.sanity = Math.max(0, Math.min(max, this.sanity));
@@ -10,5 +10,9 @@
"screen.mindmotion.hud_editor.tempo_y": "Tempo Y Offset: %s",
"screen.mindmotion.hud_editor.sanity_x": "Sanity X Offset: %s",
"screen.mindmotion.hud_editor.sanity_y": "Sanity Y Offset: %s",
"screen.mindmotion.hud_editor.reset": "Reset"
"screen.mindmotion.hud_editor.madness_x": "Madness X Offset: %s",
"screen.mindmotion.hud_editor.madness_y": "Madness Y Offset: %s",
"screen.mindmotion.hud_editor.reset": "Reset",
"death.attack.mindmotion.terror": "%1$s was consumed by terror",
"death.attack.mindmotion.madness_backlash": "%1$s succumbed to madness"
}