Compare commits
19
Commits
ad51d15805
...
df5d90fb9d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df5d90fb9d | ||
|
|
6e8746c9d1 | ||
|
|
f0398c7011 | ||
|
|
7fc42b34d8 | ||
|
|
9b6a55d66c | ||
|
|
3568996138 | ||
|
|
ebd0a76f0c | ||
|
|
6ab84a1051 | ||
|
|
1f69a3ecb4 | ||
|
|
b682961856 | ||
|
|
7b4fc41dba | ||
|
|
e453c948cc | ||
|
|
8eaec3cc4f | ||
|
|
30ccc6ea5a | ||
|
|
d1cb9c8d3a | ||
|
|
0df0d9334b | ||
|
|
7c44258b9b | ||
|
|
4ff4902981 | ||
|
|
1c3e2ff39f |
@@ -4,6 +4,7 @@ import cc.fros.Fros;
|
||||
import cc.fros.features.Feature;
|
||||
import cc.fros.features.gui.items.buttons.ModuleButton;
|
||||
import cc.fros.features.modules.Module;
|
||||
import cc.fros.features.modules.client.ClickGuiModule;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
@@ -84,27 +85,39 @@ public class FrosGui extends Screen {
|
||||
}
|
||||
|
||||
context.fill(0, 0, context.guiWidth(), context.guiHeight(), new Color(0, 0, 0, 120).hashCode());
|
||||
this.widgets.forEach(components -> components.drawScreen(context, mouseX, mouseY, delta));
|
||||
|
||||
// scale the whole clickgui; widgets work in layout units so mouse
|
||||
// coordinates are divided back down before they reach them
|
||||
float scale = ClickGuiModule.scale();
|
||||
int layoutX = (int) (mouseX / scale);
|
||||
int layoutY = (int) (mouseY / scale);
|
||||
context.pose().pushMatrix();
|
||||
context.pose().scale(scale, scale);
|
||||
this.widgets.forEach(components -> components.drawScreen(context, layoutX, layoutY, delta));
|
||||
context.pose().popMatrix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(MouseButtonEvent click, boolean doubled) {
|
||||
this.widgets.forEach(components -> components.mouseClicked((int) click.x(), (int) click.y(), click.button()));
|
||||
float scale = ClickGuiModule.scale();
|
||||
this.widgets.forEach(components -> components.mouseClicked((int) (click.x() / scale), (int) (click.y() / scale), click.button()));
|
||||
return super.mouseClicked(click, doubled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseReleased(MouseButtonEvent click) {
|
||||
this.widgets.forEach(components -> components.mouseReleased((int) click.x(), (int) click.y(), click.button()));
|
||||
float scale = ClickGuiModule.scale();
|
||||
this.widgets.forEach(components -> components.mouseReleased((int) (click.x() / scale), (int) (click.y() / scale), click.button()));
|
||||
return super.mouseReleased(click);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
|
||||
int step = Math.max(1, Math.round((float) (10 / ClickGuiModule.scale())));
|
||||
if (verticalAmount < 0) {
|
||||
this.widgets.forEach(component -> component.setY(component.getY() - 10));
|
||||
this.widgets.forEach(component -> component.setY(component.getY() - step));
|
||||
} else if (verticalAmount > 0) {
|
||||
this.widgets.forEach(component -> component.setY(component.getY() + 10));
|
||||
this.widgets.forEach(component -> component.setY(component.getY() + step));
|
||||
}
|
||||
return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
|
||||
}
|
||||
@@ -133,10 +146,6 @@ public class FrosGui extends Screen {
|
||||
return this.widgets;
|
||||
}
|
||||
|
||||
public int getTextOffset() {
|
||||
return -6;
|
||||
}
|
||||
|
||||
public static Color getColorClipboard() {
|
||||
return colorClipboard;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import cc.fros.Fros;
|
||||
import cc.fros.features.Feature;
|
||||
import cc.fros.features.gui.items.buttons.ModuleButton;
|
||||
import cc.fros.features.modules.Module;
|
||||
import cc.fros.features.modules.client.ClickGuiModule;
|
||||
import cc.fros.features.modules.client.HudModule;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
@@ -40,27 +41,37 @@ public class HudEditorScreen extends Screen {
|
||||
@Override
|
||||
public void render(GuiGraphics context, int mouseX, int mouseY, float delta) {
|
||||
anyHover = false;
|
||||
this.components.forEach(component -> component.drawScreen(context, mouseX, mouseY, delta));
|
||||
// same ui scale treatment as the clickgui
|
||||
float scale = ClickGuiModule.scale();
|
||||
int layoutX = (int) (mouseX / scale);
|
||||
int layoutY = (int) (mouseY / scale);
|
||||
context.pose().pushMatrix();
|
||||
context.pose().scale(scale, scale);
|
||||
this.components.forEach(component -> component.drawScreen(context, layoutX, layoutY, delta));
|
||||
context.pose().popMatrix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(MouseButtonEvent click, boolean doubled) {
|
||||
this.components.forEach(component -> component.mouseClicked((int) click.x(), (int) click.y(), click.button()));
|
||||
float scale = ClickGuiModule.scale();
|
||||
this.components.forEach(component -> component.mouseClicked((int) (click.x() / scale), (int) (click.y() / scale), click.button()));
|
||||
return super.mouseClicked(click, doubled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseReleased(MouseButtonEvent click) {
|
||||
this.components.forEach(component -> component.mouseReleased((int) click.x(), (int) click.y(), click.button()));
|
||||
float scale = ClickGuiModule.scale();
|
||||
this.components.forEach(component -> component.mouseReleased((int) (click.x() / scale), (int) (click.y() / scale), click.button()));
|
||||
return super.mouseReleased(click);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
|
||||
int step = Math.max(1, Math.round((float) (10 / ClickGuiModule.scale())));
|
||||
if (verticalAmount < 0) {
|
||||
this.components.forEach(component -> component.setY(component.getY() - 10));
|
||||
this.components.forEach(component -> component.setY(component.getY() - step));
|
||||
} else if (verticalAmount > 0) {
|
||||
this.components.forEach(component -> component.setY(component.getY() + 10));
|
||||
this.components.forEach(component -> component.setY(component.getY() + step));
|
||||
}
|
||||
return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package cc.fros.features.gui;
|
||||
|
||||
import cc.fros.util.render.FrosFont;
|
||||
|
||||
import cc.fros.Fros;
|
||||
import cc.fros.features.Feature;
|
||||
import cc.fros.features.gui.items.Item;
|
||||
import cc.fros.features.gui.items.buttons.Button;
|
||||
@@ -50,12 +53,32 @@ public class Widget
|
||||
this.context = context;
|
||||
this.drag(mouseX, mouseY);
|
||||
float totalItemHeight = this.open ? this.getTotalItemHeight() - 2.0f : 0.0f;
|
||||
int color = ClickGuiModule.getInstance().topColor.getValue().getRGB();
|
||||
context.fill(this.x, this.y - 1, this.x + this.width, this.y + this.height - 6, ClickGuiModule.getInstance().rainbow.getValue() ? ColorUtil.rainbow(ClickGuiModule.getInstance().rainbowHue.getValue()).getRGB() : color);
|
||||
boolean rainbow = ClickGuiModule.getInstance().rainbow.getValue();
|
||||
int headerColor = rainbow
|
||||
? ColorUtil.rainbow(ClickGuiModule.getInstance().rainbowHue.getValue()).getRGB()
|
||||
: ClickGuiModule.getInstance().topColor.getValue().getRGB();
|
||||
int accent = rainbow
|
||||
? ColorUtil.rainbow(ClickGuiModule.getInstance().rainbowHue.getValue()).getRGB()
|
||||
: Fros.colorManager.getColorAsInt();
|
||||
|
||||
int headerTop = this.y - 1;
|
||||
int headerBottom = this.y + this.height - 6;
|
||||
int panelBottom = this.open ? Math.round(this.y + this.height + totalItemHeight) : headerBottom;
|
||||
|
||||
// drop shadow behind the whole panel
|
||||
context.fill(this.x + 2, headerTop + 2, this.x + this.width + 2, panelBottom + 2, 0x50000000);
|
||||
|
||||
context.fill(this.x, headerTop, this.x + this.width, headerBottom, headerColor);
|
||||
if (this.open) {
|
||||
RenderUtil.rect(context, this.x, (float) this.y + 12.5f, this.x + this.width, (float) (this.y + this.height) + totalItemHeight, 0x77000000);
|
||||
RenderUtil.rect(context, this.x, headerBottom, this.x + this.width, panelBottom, 0xE6141414);
|
||||
}
|
||||
drawString(this.getName(), (float) this.x + 3.0f, (float) this.y - 4.0f - (float) FrosGui.getClickGui().getTextOffset(), -1);
|
||||
|
||||
// frame the panel and separate the header with an accent line
|
||||
RenderUtil.rect(context, this.x, headerTop, this.x + this.width, panelBottom, 0x99000000, 1.0f);
|
||||
context.fill(this.x, headerBottom - 1, this.x + this.width, headerBottom, accent);
|
||||
|
||||
drawString(this.getName(), (float) this.x + 3.0f,
|
||||
(headerTop + headerBottom) / 2f - FrosFont.visualCenter(), -1);
|
||||
ScissorUtil.enable(context, x, 0, x + width, mc.getWindow().getGuiScaledHeight());
|
||||
|
||||
if (this.open) {
|
||||
@@ -193,6 +216,6 @@ public class Widget
|
||||
}
|
||||
|
||||
protected void drawString(String text, double x, double y, int color) {
|
||||
context.drawString(mc.font, text, (int) x, (int) y, color);
|
||||
FrosFont.draw(context, text, (int) x, (int) y, color);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package cc.fros.features.gui.items;
|
||||
|
||||
import cc.fros.util.render.FrosFont;
|
||||
|
||||
import cc.fros.features.Feature;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
|
||||
@@ -78,7 +80,12 @@ public class Item
|
||||
}
|
||||
|
||||
protected void drawString(GuiGraphics context, String text, double x, double y, int color) {
|
||||
context.drawString(mc.font, text, (int) x, (int) y, color);
|
||||
FrosFont.draw(context, text, (int) x, (int) y, color);
|
||||
}
|
||||
|
||||
/** y coordinate that visually centers text inside this item */
|
||||
protected float textY() {
|
||||
return this.y + this.height / 2f - FrosFont.visualCenter();
|
||||
}
|
||||
|
||||
public boolean isHovering(int mouseX, int mouseY) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package cc.fros.features.gui.items.buttons;
|
||||
|
||||
import cc.fros.Fros;
|
||||
import cc.fros.features.gui.FrosGui;
|
||||
import cc.fros.features.modules.client.ClickGuiModule;
|
||||
import cc.fros.features.settings.Bind;
|
||||
import cc.fros.features.settings.Setting;
|
||||
@@ -29,10 +28,10 @@ public class BindButton
|
||||
int color = ClickGuiModule.getInstance().color.getValue().getRGB();
|
||||
RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515) : (!this.isHovering(mouseX, mouseY) ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())));
|
||||
if (this.isListening) {
|
||||
drawString(context, "Press a Key...", this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), -1);
|
||||
drawString(context, "Press a Key...", this.x + 2.3f, textY(), -1);
|
||||
} else {
|
||||
String str = KeyboardUtil.getKeyName(setting.getValue());
|
||||
drawString(context, this.setting.getName() + " " + ChatFormatting.GRAY + str, this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
|
||||
drawString(context, this.setting.getName() + " " + ChatFormatting.GRAY + str, this.x + 2.3f, textY(), this.getState() ? -1 : -5592406);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package cc.fros.features.gui.items.buttons;
|
||||
|
||||
|
||||
import cc.fros.Fros;
|
||||
import cc.fros.features.gui.FrosGui;
|
||||
import cc.fros.features.modules.client.ClickGuiModule;
|
||||
import cc.fros.features.settings.Setting;
|
||||
import cc.fros.util.render.RenderUtil;
|
||||
@@ -23,7 +22,7 @@ public class BooleanButton
|
||||
@Override
|
||||
public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
|
||||
RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())) : (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515));
|
||||
drawString(context, this.getName(), this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
|
||||
drawString(context, this.getName(), this.x + 2.3f, textY(), this.getState() ? -1 : -5592406);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,7 +22,7 @@ public class Button
|
||||
@Override
|
||||
public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
|
||||
RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())) : (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515));
|
||||
drawString(context, this.getName(), this.x + 2.3f, this.y - 2.0f - (float) FrosGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
|
||||
drawString(context, this.getName(), this.x + 2.3f, textY(), this.getState() ? -1 : -5592406);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cc.fros.features.gui.items.buttons;
|
||||
|
||||
import cc.fros.util.render.FrosFont;
|
||||
|
||||
import cc.fros.Fros;
|
||||
import cc.fros.features.gui.FrosGui;
|
||||
import cc.fros.features.gui.Widget;
|
||||
@@ -42,7 +44,7 @@ public class ColorButton extends Button {
|
||||
this.x + (float) this.width + 5.0f, this.y + (float) this.height - 2.5f,
|
||||
new Color(currentColor.getRGB(), false).getRGB());
|
||||
|
||||
drawString(context, this.getName(), this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), -1);
|
||||
drawString(context, this.getName(), this.x + 2.3f, textY(), -1);
|
||||
|
||||
if (open) {
|
||||
float yOffset = this.height + 1;
|
||||
@@ -119,12 +121,12 @@ public class ColorButton extends Button {
|
||||
int buttonWidth = availableWidth / 2;
|
||||
RenderUtil.rect(context, pickerX, this.y + yOffset, pickerX + buttonWidth, this.y + yOffset + 14,
|
||||
hoveringCopy ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha()) : 0x11555555);
|
||||
drawString(context, "Copy", pickerX + buttonWidth / 2.0 - mc.font.width("Copy") / 2.0, this.y + yOffset + 3, -1);
|
||||
drawString(context, "Copy", pickerX + buttonWidth / 2.0 - FrosFont.width("Copy") / 2.0, this.y + yOffset + 7f - FrosFont.visualCenter(), -1);
|
||||
hoveringCopy = isHoveringArea(mouseX, mouseY, pickerX, this.y + yOffset, pickerX + buttonWidth, this.y + yOffset + 14);
|
||||
|
||||
RenderUtil.rect(context, pickerX + buttonWidth + 1, this.y + yOffset, pickerX + buttonWidth * 2 + 1, this.y + yOffset + 14,
|
||||
hoveringPaste ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha()) : 0x11555555);
|
||||
drawString(context, "Paste", pickerX + buttonWidth + buttonWidth / 2.0 - mc.font.width("Paste") / 2.0 + 1, this.y + yOffset + 3, -1);
|
||||
drawString(context, "Paste", pickerX + buttonWidth + buttonWidth / 2.0 - FrosFont.width("Paste") / 2.0 + 1, this.y + yOffset + 7f - FrosFont.visualCenter(), -1);
|
||||
hoveringPaste = isHoveringArea(mouseX, mouseY, pickerX + buttonWidth + 1, this.y + yOffset, pickerX + buttonWidth * 2 + 1, this.y + yOffset + 14);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package cc.fros.features.gui.items.buttons;
|
||||
|
||||
|
||||
import cc.fros.Fros;
|
||||
import cc.fros.features.gui.FrosGui;
|
||||
import cc.fros.features.modules.client.ClickGuiModule;
|
||||
import cc.fros.features.settings.Setting;
|
||||
import cc.fros.util.render.RenderUtil;
|
||||
@@ -24,7 +23,7 @@ public class EnumButton
|
||||
@Override
|
||||
public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
|
||||
RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())) : (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515));
|
||||
drawString(context, this.setting.getName() + " " + ChatFormatting.GRAY + (this.setting.currentEnumName().equalsIgnoreCase("ABC") ? "ABC" : this.setting.currentEnumName()), this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
|
||||
drawString(context, this.setting.getName() + " " + ChatFormatting.GRAY + (this.setting.currentEnumName().equalsIgnoreCase("ABC") ? "ABC" : this.setting.currentEnumName()), this.x + 2.3f, textY(), this.getState() ? -1 : -5592406);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,7 +32,7 @@ public class Slider
|
||||
this.dragSetting(mouseX, mouseY);
|
||||
RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, !(this.dragging || this.isHovering(mouseX, mouseY)) ? 0x11555555 : -2007673515);
|
||||
RenderUtil.rect(context, this.x, this.y, (this.setting.getValue()).floatValue() <= this.min.floatValue() ? this.x : this.x + ((float) this.width + 7.4f) * this.partialMultiplier(), this.y + (float) this.height - 0.5f, !(this.dragging || this.isHovering(mouseX, mouseY)) ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha()));
|
||||
drawString(context, this.getName() + " " + ChatFormatting.GRAY + (this.setting.getValue() instanceof Float ? this.setting.getValue() : Double.valueOf((this.setting.getValue()).doubleValue())), this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), -1);
|
||||
drawString(context, this.getName() + " " + ChatFormatting.GRAY + (this.setting.getValue() instanceof Float ? this.setting.getValue() : Double.valueOf((this.setting.getValue()).doubleValue())), this.x + 2.3f, textY(), -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package cc.fros.features.gui.items.buttons;
|
||||
|
||||
import cc.fros.Fros;
|
||||
import cc.fros.features.gui.FrosGui;
|
||||
import cc.fros.features.modules.client.ClickGuiModule;
|
||||
import cc.fros.features.settings.Setting;
|
||||
import cc.fros.util.models.Timer;
|
||||
@@ -38,9 +37,9 @@ public class StringButton
|
||||
public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
|
||||
RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())) : (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515));
|
||||
if (this.isListening) {
|
||||
drawString(context, this.currentString.string() + getIdleSign(), this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
|
||||
drawString(context, this.currentString.string() + getIdleSign(), this.x + 2.3f, textY(), this.getState() ? -1 : -5592406);
|
||||
} else {
|
||||
drawString(context, (this.setting.getName().equals("Buttons") ? "Buttons " : (this.setting.getName().equals("Prefix") ? "Prefix " + ChatFormatting.GRAY : "")) + this.setting.getValue(), this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
|
||||
drawString(context, (this.setting.getName().equals("Buttons") ? "Buttons " : (this.setting.getName().equals("Prefix") ? "Prefix " + ChatFormatting.GRAY : "")) + this.setting.getValue(), this.x + 2.3f, textY(), this.getState() ? -1 : -5592406);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ import static cc.fros.features.commands.MessageSignatures.GENERAL;
|
||||
public class ClickGuiModule extends Module {
|
||||
private static ClickGuiModule INSTANCE;
|
||||
|
||||
public final Setting<String> font = str("Font", "SansSerif");
|
||||
public final Setting<Integer> fontSize = num("FontSize", 10, 6, 24);
|
||||
public final Setting<Float> guiScale = num("GUIScale", 1.0f, 0.5f, 2.0f);
|
||||
public final Setting<String> prefix = str("Prefix", ".");
|
||||
public final Setting<Color> color = color("Color", 0, 0, 255, 180);
|
||||
public final Setting<Color> topColor = color("TopColor", 0, 0, 150, 240);
|
||||
@@ -27,6 +30,7 @@ public class ClickGuiModule extends Module {
|
||||
public ClickGuiModule() {
|
||||
super("ClickGui", "Opens the ClickGui", Module.Category.CLIENT);
|
||||
setBind(GLFW.GLFW_KEY_RIGHT_SHIFT);
|
||||
fontSize.setVisibility(v -> !font.getValue().isEmpty());
|
||||
rainbowHue.setVisibility(v -> rainbow.getValue());
|
||||
rainbowBrightness.setVisibility(v -> rainbow.getValue());
|
||||
rainbowSaturation.setVisibility(v -> rainbow.getValue());
|
||||
@@ -70,4 +74,9 @@ public class ClickGuiModule extends Module {
|
||||
public static ClickGuiModule getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/** ui scale factor applied to the client's own hud and clickgui */
|
||||
public static float scale() {
|
||||
return INSTANCE == null ? 1f : Math.max(0.5f, INSTANCE.guiScale.getValue());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package cc.fros.features.modules.client;
|
||||
|
||||
import cc.fros.util.render.FrosFont;
|
||||
|
||||
import cc.fros.Fros;
|
||||
import cc.fros.event.impl.input.MouseInputEvent;
|
||||
import cc.fros.event.impl.render.Render2DEvent;
|
||||
@@ -11,9 +13,16 @@ import cc.fros.util.render.RenderUtil;
|
||||
import net.minecraft.client.gui.screens.ChatScreen;
|
||||
import org.joml.Vector2f;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class HudModule extends Module {
|
||||
public final Setting<Vector2f> pos = vec2f("Position", 0.5f, 0.5f);
|
||||
|
||||
/** grid snap lines (gui units) for dragged hud elements */
|
||||
private static final float SNAP_GRID = 4f;
|
||||
/** how close a dragged element gets to another's edge/center before it attracts */
|
||||
private static final float SNAP_RANGE = 4f;
|
||||
|
||||
private float dragX, dragY, width, height;
|
||||
private boolean dragging, button;
|
||||
|
||||
@@ -28,19 +37,36 @@ public abstract class HudModule extends Module {
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
float scaledHeight = getHeight() * ClickGuiModule.scale();
|
||||
float heightWithChat = mc.getWindow().getGuiScaledHeight() - 14;
|
||||
float baseY = mc.getWindow().getGuiScaledHeight() * pos.getValue().y();
|
||||
float combined = baseY + getHeight();
|
||||
float combined = baseY + scaledHeight;
|
||||
|
||||
if (mc.screen instanceof ChatScreen) {
|
||||
baseY = Math.min(combined, heightWithChat) - getHeight();
|
||||
baseY = Math.min(combined, heightWithChat) - scaledHeight;
|
||||
}
|
||||
return baseY;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onRender2DHud(Render2DEvent e) {
|
||||
render(e);
|
||||
float scale = ClickGuiModule.scale();
|
||||
if (scale == 1f) {
|
||||
render(e);
|
||||
return;
|
||||
}
|
||||
// scale the element about its own position anchor so content,
|
||||
// editor box and hover label all grow together
|
||||
float anchorX = getX();
|
||||
float anchorY = getY();
|
||||
e.getContext().pose().pushMatrix();
|
||||
e.getContext().pose().translate(anchorX * (1f - scale), anchorY * (1f - scale));
|
||||
e.getContext().pose().scale(scale, scale);
|
||||
try {
|
||||
render(e);
|
||||
} finally {
|
||||
e.getContext().pose().popMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -73,12 +99,16 @@ public abstract class HudModule extends Module {
|
||||
}
|
||||
|
||||
if (dragging) {
|
||||
float finalX = Math.min(Math.max(getMouseX() - dragX, 0),
|
||||
mc.getWindow().getGuiScaledWidth() - width);
|
||||
float finalY = Math.min(Math.max(getMouseY() - dragY, 0),
|
||||
mc.getWindow().getGuiScaledHeight() - height);
|
||||
float scale = ClickGuiModule.scale();
|
||||
float maxX = mc.getWindow().getGuiScaledWidth() - width * scale;
|
||||
float maxY = mc.getWindow().getGuiScaledHeight() - height * scale;
|
||||
float rawX = Math.min(Math.max(getMouseX() - dragX, 0), maxX);
|
||||
float rawY = Math.min(Math.max(getMouseY() - dragY, 0), maxY);
|
||||
// snap to other elements first, grid otherwise, then re-clamp
|
||||
float snappedX = Math.min(Math.max(snapAxis(rawX, width * scale, true, scale), 0), maxX);
|
||||
float snappedY = Math.min(Math.max(snapAxis(rawY, height * scale, false, scale), 0), maxY);
|
||||
|
||||
pos.setValue(new Vector2f(finalX, finalY)
|
||||
pos.setValue(new Vector2f(snappedX, snappedY)
|
||||
.div(mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight()));
|
||||
}
|
||||
} else {
|
||||
@@ -91,13 +121,15 @@ public abstract class HudModule extends Module {
|
||||
}
|
||||
|
||||
if (shouldDrawDescription) {
|
||||
int textWidth = mc.font.width(getName());
|
||||
int textHeight = mc.font.lineHeight;
|
||||
int textWidth = FrosFont.width(getName());
|
||||
int textHeight = FrosFont.lineHeight();
|
||||
float scale = ClickGuiModule.scale();
|
||||
float textX = x + width + 5;
|
||||
if (textX + textWidth > mc.getWindow().getGuiScaledWidth()) {
|
||||
// the label is drawn inside the element's scale transform
|
||||
if (x + (textX + textWidth - x) * scale > mc.getWindow().getGuiScaledWidth()) {
|
||||
textX = x - 5 - textWidth;
|
||||
}
|
||||
e.getContext().drawString(mc.font, getName(),
|
||||
FrosFont.draw(e.getContext(), getName(),
|
||||
(int) textX, (int) (y + height / 2f - textHeight / 2f), -1);
|
||||
HudEditorScreen.getInstance().anyHover = true;
|
||||
}
|
||||
@@ -127,9 +159,47 @@ public abstract class HudModule extends Module {
|
||||
float y = getY();
|
||||
int mouseX = getMouseX();
|
||||
int mouseY = getMouseY();
|
||||
float scale = ClickGuiModule.scale();
|
||||
|
||||
return mouseX >= x - 1 && mouseX <= x + width + 1 &&
|
||||
mouseY >= y - 1 && mouseY <= y + height + 1;
|
||||
return mouseX >= x - 1 && mouseX <= x + width * scale + 1 &&
|
||||
mouseY >= y - 1 && mouseY <= y + height * scale + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drags on to another visible hud element's left/center/right (or
|
||||
* top/middle/bottom) when within {@link #SNAP_RANGE}; otherwise rounds
|
||||
* onto the {@link #SNAP_GRID} grid so everything stays tidy.
|
||||
*/
|
||||
private float snapAxis(float position, float size, boolean horizontal, float scale) {
|
||||
float bestDelta = 0f;
|
||||
boolean aligned = false;
|
||||
|
||||
for (HudModule other : hudModules()) {
|
||||
if (other == this || !other.isEnabled()) continue;
|
||||
float otherPos = horizontal ? other.getX() : other.getY();
|
||||
float otherSize = horizontal ? other.getWidth() * scale : other.getHeight() * scale;
|
||||
float[] mine = {position, position + size / 2f, position + size};
|
||||
float[] theirs = {otherPos, otherPos + otherSize / 2f, otherPos + otherSize};
|
||||
for (float mineAnchor : mine) {
|
||||
for (float theirAnchor : theirs) {
|
||||
float delta = theirAnchor - mineAnchor;
|
||||
if (Math.abs(delta) <= SNAP_RANGE && (!aligned || Math.abs(delta) < Math.abs(bestDelta))) {
|
||||
bestDelta = delta;
|
||||
aligned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (aligned) return position + bestDelta;
|
||||
return Math.round(position / SNAP_GRID) * SNAP_GRID;
|
||||
}
|
||||
|
||||
private static List<HudModule> hudModules() {
|
||||
return Fros.moduleManager.stream()
|
||||
.filter(module -> module instanceof HudModule)
|
||||
.map(module -> (HudModule) module)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
package cc.fros.features.modules.client;
|
||||
|
||||
import cc.fros.Fros;
|
||||
import cc.fros.event.impl.ClientEvent;
|
||||
import cc.fros.event.system.Subscribe;
|
||||
import cc.fros.features.modules.Module;
|
||||
import cc.fros.features.modules.hud.RadarHudModule;
|
||||
import cc.fros.features.settings.Setting;
|
||||
import cc.fros.util.TextUtil;
|
||||
import cc.fros.util.chat.ChatUtil;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static cc.fros.util.chat.SimpleSignature.from;
|
||||
|
||||
public class NotificationsModule extends Module {
|
||||
private static final String MODULE_FORMAT = "{gray} %s %s%s";
|
||||
private static final String TOGGLE_FORMAT = "%s%s {gray}%s";
|
||||
|
||||
public final Setting<Boolean> moduleToggle = bool("ModuleToggle", true);
|
||||
public final Setting<Boolean> visualRange = bool("VisualRange", true);
|
||||
|
||||
private final Map<UUID, String> inRange = new HashMap<>();
|
||||
private ClientLevel lastLevel;
|
||||
private RadarHudModule radar;
|
||||
private boolean tracked;
|
||||
|
||||
public NotificationsModule() {
|
||||
super("Notifications", "Displays notifications for various client events", Category.CLIENT);
|
||||
@@ -31,9 +46,62 @@ public class NotificationsModule extends Module {
|
||||
? module.getDisplayName()
|
||||
: event.getFeature().getName();
|
||||
|
||||
ChatUtil.sendMessage(TextUtil.text(TOGGLE_FORMAT,
|
||||
moduleState ? "{green}" : "{red}",
|
||||
moduleState ? "[+]" : "[-]",
|
||||
name), from(event.getFeature().getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTick() {
|
||||
if (!visualRange.getValue() || mc.player == null || mc.level == null) {
|
||||
inRange.clear();
|
||||
tracked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mc.level != lastLevel) {
|
||||
// world change: re-baseline silently instead of notifying everyone present
|
||||
lastLevel = mc.level;
|
||||
tracked = false;
|
||||
inRange.clear();
|
||||
}
|
||||
|
||||
double range = getRadarRange();
|
||||
Map<UUID, String> now = new HashMap<>();
|
||||
for (Player player : mc.level.players()) {
|
||||
if (player == mc.player) continue;
|
||||
if (mc.player.distanceTo(player) <= range) {
|
||||
now.put(player.getUUID(), player.getScoreboardName());
|
||||
}
|
||||
}
|
||||
|
||||
if (tracked) {
|
||||
for (Map.Entry<UUID, String> entry : now.entrySet()) {
|
||||
if (inRange.containsKey(entry.getKey())) continue;
|
||||
notify(entry.getValue(), "{green}", "entered your visual range");
|
||||
}
|
||||
for (Map.Entry<UUID, String> entry : inRange.entrySet()) {
|
||||
if (now.containsKey(entry.getKey())) continue;
|
||||
notify(entry.getValue(), "{red}", "left your visual range");
|
||||
}
|
||||
}
|
||||
|
||||
inRange.clear();
|
||||
inRange.putAll(now);
|
||||
tracked = true;
|
||||
}
|
||||
|
||||
private void notify(String name, String color, String message) {
|
||||
ChatUtil.sendMessage(TextUtil.text(MODULE_FORMAT,
|
||||
name,
|
||||
moduleState ? "{green}" : "{red}",
|
||||
moduleState ? "on" : "off"), from(event.getFeature().getName()));
|
||||
color,
|
||||
message), from(getName() + ":" + name));
|
||||
}
|
||||
|
||||
private double getRadarRange() {
|
||||
// stay in sync with what the radar hud actually shows
|
||||
if (radar == null) radar = Fros.moduleManager.getModuleByClass(RadarHudModule.class);
|
||||
return radar != null ? radar.range.getValue() : 64.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,21 +19,25 @@ public class KillAuraModule extends Module {
|
||||
private final Setting<Boolean> onlyWeapon = bool("OnlyWeapon", false);
|
||||
private final Setting<Boolean> sync = bool("MovementSync", false);
|
||||
|
||||
private String targetName;
|
||||
|
||||
public KillAuraModule() {
|
||||
super("KillAura", "Attacks nearby players.", Category.COMBAT);
|
||||
}
|
||||
|
||||
@Subscribe public void onPreTick(TickEvent.Pre event) {
|
||||
Player target = findPlayerTarget();
|
||||
this.targetName = target == null ? null : target.getScoreboardName();
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
if (isOnCooldown())
|
||||
return;
|
||||
|
||||
if (onlyWeapon.getValue() && !isHoldingWeapon())
|
||||
return;
|
||||
|
||||
Player target = findPlayerTarget();
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
Angles angles = Angles.entity(target);
|
||||
int priority = 0;
|
||||
|
||||
@@ -74,4 +78,14 @@ public class KillAuraModule extends Module {
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
targetName = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayInfo() {
|
||||
return targetName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package cc.fros.features.modules.hud;
|
||||
|
||||
import cc.fros.util.render.FrosFont;
|
||||
|
||||
import cc.fros.Fros;
|
||||
import cc.fros.event.impl.render.Render2DEvent;
|
||||
import cc.fros.features.modules.client.HudModule;
|
||||
import cc.fros.features.modules.Module;
|
||||
import cc.fros.features.settings.Setting;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ArrayListHudModule extends HudModule {
|
||||
private static final int DISABLED_COLOR = 0xFF555555;
|
||||
private static final int BRACKET_COLOR = 0xFFAAAAAA;
|
||||
|
||||
public final Setting<Align> alignment = mode("Alignment", Align.RIGHT);
|
||||
public final Setting<Sort> sort = mode("Sort", Sort.LENGTH);
|
||||
public final Setting<Boolean> disabled = bool("Disabled", false);
|
||||
public final Setting<Boolean> hudModules = bool("HudModules", false);
|
||||
|
||||
public ArrayListHudModule() {
|
||||
super("ArrayList", "Display modules and their state", 100, 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(Render2DEvent e) {
|
||||
super.render(e);
|
||||
|
||||
List<Module> shown = Fros.moduleManager.getModules().stream()
|
||||
.filter(module -> !module.hidden)
|
||||
.filter(module -> module.getCategory() != Module.Category.HUD || hudModules.getValue())
|
||||
.filter(module -> module.isEnabled() || disabled.getValue())
|
||||
.sorted(comparator())
|
||||
.toList();
|
||||
|
||||
if (shown.isEmpty()) return;
|
||||
|
||||
float x = getX();
|
||||
float y = getY();
|
||||
int lineHeight = FrosFont.lineHeight();
|
||||
int color = Fros.colorManager.getColorAsInt();
|
||||
boolean right = alignment.getValue() == Align.RIGHT;
|
||||
int nameBlock = 0;
|
||||
int content = 0;
|
||||
|
||||
for (Module module : shown) {
|
||||
int nameWidth = FrosFont.width(module.getDisplayName());
|
||||
nameBlock = Math.max(nameBlock, nameWidth);
|
||||
|
||||
int rowWidth = nameWidth;
|
||||
String info = module.getDisplayInfo();
|
||||
if (info != null) {
|
||||
rowWidth += FrosFont.width(" ") + infoWidth(info);
|
||||
}
|
||||
content = Math.max(content, rowWidth);
|
||||
}
|
||||
|
||||
for (int i = 0; i < shown.size(); i++) {
|
||||
Module module = shown.get(i);
|
||||
String name = module.getDisplayName();
|
||||
String info = module.getDisplayInfo();
|
||||
boolean on = module.isEnabled();
|
||||
|
||||
int rowY = (int) (y + i * lineHeight);
|
||||
int baseColor = on ? color : DISABLED_COLOR;
|
||||
int nameWidth = FrosFont.width(name);
|
||||
|
||||
// name is anchored to the alignment side using names only, so it
|
||||
// never moves when dynamic module info appears or changes
|
||||
int nameX = (int) (right ? x + nameBlock - nameWidth : x);
|
||||
FrosFont.draw(e.getContext(), name, nameX, rowY, baseColor);
|
||||
|
||||
if (info != null) {
|
||||
// info sits directly on the opposite side of the name,
|
||||
// separated by a single space instead of a gap
|
||||
int blockWidth = infoWidth(info);
|
||||
int space = FrosFont.width(" ");
|
||||
int infoX = right ? nameX - space - blockWidth : nameX + nameWidth + space;
|
||||
int bracketColor = on ? BRACKET_COLOR : DISABLED_COLOR;
|
||||
|
||||
FrosFont.draw(e.getContext(), "[", infoX, rowY, bracketColor);
|
||||
infoX += FrosFont.width("[");
|
||||
FrosFont.draw(e.getContext(), info, infoX, rowY, on ? -1 : DISABLED_COLOR);
|
||||
infoX += FrosFont.width(info);
|
||||
FrosFont.draw(e.getContext(), "]", infoX, rowY, bracketColor);
|
||||
}
|
||||
}
|
||||
|
||||
setWidth(Math.max(content, nameBlock));
|
||||
setHeight(lineHeight * shown.size());
|
||||
}
|
||||
|
||||
private int infoWidth(String info) {
|
||||
return FrosFont.width("[") + FrosFont.width(info) + FrosFont.width("]");
|
||||
}
|
||||
|
||||
private Comparator<Module> comparator() {
|
||||
return switch (sort.getValue()) {
|
||||
case LENGTH -> Comparator.comparingInt((Module module) ->
|
||||
module.getDisplayName().length()).reversed();
|
||||
case ALPHABETICAL -> Comparator.comparing(module ->
|
||||
module.getDisplayName().toLowerCase(Locale.ROOT));
|
||||
case NONE -> (first, second) -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
public enum Align {
|
||||
LEFT,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
public enum Sort {
|
||||
LENGTH,
|
||||
ALPHABETICAL,
|
||||
NONE
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package cc.fros.features.modules.hud;
|
||||
|
||||
import cc.fros.util.render.FrosFont;
|
||||
|
||||
import cc.fros.event.impl.render.Render2DEvent;
|
||||
import cc.fros.features.modules.client.HudModule;
|
||||
import cc.fros.features.settings.Setting;
|
||||
@@ -33,11 +35,11 @@ public class CoordinatesHudModule extends HudModule {
|
||||
}
|
||||
}
|
||||
|
||||
e.getContext().drawString(mc.font, coordsStr,
|
||||
FrosFont.draw(e.getContext(), coordsStr,
|
||||
(int) getX(), (int) getY(), -1);
|
||||
|
||||
setWidth(mc.font.width(coordsStr));
|
||||
setHeight(mc.font.lineHeight);
|
||||
setWidth(FrosFont.width(coordsStr));
|
||||
setHeight(FrosFont.lineHeight());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cc.fros.features.modules.hud;
|
||||
|
||||
import cc.fros.util.render.FrosFont;
|
||||
|
||||
import cc.fros.event.impl.render.Render2DEvent;
|
||||
import cc.fros.features.modules.client.HudModule;
|
||||
import cc.fros.features.settings.Setting;
|
||||
@@ -18,10 +20,10 @@ public class FpsHudModule extends HudModule {
|
||||
int fps = mc.getFps();
|
||||
String fpsStr = label.getValue() ? "FPS: " + fps : String.valueOf(fps);
|
||||
|
||||
e.getContext().drawString(mc.font, fpsStr,
|
||||
FrosFont.draw(e.getContext(), fpsStr,
|
||||
(int) getX(), (int) getY(), -1);
|
||||
|
||||
setWidth(mc.font.width(fpsStr));
|
||||
setHeight(mc.font.lineHeight);
|
||||
setWidth(FrosFont.width(fpsStr));
|
||||
setHeight(FrosFont.lineHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cc.fros.features.modules.hud;
|
||||
|
||||
import cc.fros.util.render.FrosFont;
|
||||
|
||||
import cc.fros.event.impl.render.Render2DEvent;
|
||||
import cc.fros.features.modules.client.HudModule;
|
||||
import cc.fros.features.settings.Setting;
|
||||
@@ -36,11 +38,11 @@ public class PingHudModule extends HudModule {
|
||||
}
|
||||
|
||||
private void draw(Render2DEvent e, String text, int color) {
|
||||
e.getContext().drawString(mc.font, text,
|
||||
FrosFont.draw(e.getContext(), text,
|
||||
(int) getX(), (int) getY(), color);
|
||||
|
||||
setWidth(mc.font.width(text));
|
||||
setHeight(mc.font.lineHeight);
|
||||
setWidth(FrosFont.width(text));
|
||||
setHeight(FrosFont.lineHeight());
|
||||
}
|
||||
|
||||
private static int colorForPing(int ping) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package cc.fros.features.modules.hud;
|
||||
|
||||
import cc.fros.util.render.FrosFont;
|
||||
|
||||
import cc.fros.Fros;
|
||||
import cc.fros.event.impl.render.Render2DEvent;
|
||||
import cc.fros.features.modules.client.HudModule;
|
||||
import cc.fros.features.settings.Setting;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class RadarHudModule extends HudModule {
|
||||
public final Setting<Double> range = num("Range", 64.0, 8.0, 256.0);
|
||||
public final Setting<Integer> maxPlayers = num("MaxPlayers", 8, 1, 32);
|
||||
public final Setting<Boolean> showDistance = bool("Distance", true);
|
||||
|
||||
private record Entry(String name, double distance, boolean friend) {
|
||||
}
|
||||
|
||||
public RadarHudModule() {
|
||||
super("Radar", "Display players near you", 100, 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(Render2DEvent e) {
|
||||
super.render(e);
|
||||
|
||||
if (nullCheck()) return;
|
||||
|
||||
List<Entry> nearby = new ArrayList<>();
|
||||
for (Player player : mc.level.players()) {
|
||||
if (player == mc.player) continue;
|
||||
|
||||
double distance = mc.player.distanceTo(player);
|
||||
if (distance > range.getValue()) continue;
|
||||
|
||||
nearby.add(new Entry(player.getScoreboardName(), distance,
|
||||
Fros.friendManager.isFriend(player)));
|
||||
}
|
||||
|
||||
if (nearby.isEmpty()) return;
|
||||
|
||||
nearby.sort(Comparator.comparingDouble(Entry::distance));
|
||||
int limit = Math.min(nearby.size(), maxPlayers.getValue());
|
||||
nearby = nearby.subList(0, limit);
|
||||
|
||||
float x = getX();
|
||||
float y = getY();
|
||||
int lineHeight = FrosFont.lineHeight();
|
||||
int widest = 0;
|
||||
|
||||
for (int i = 0; i < nearby.size(); i++) {
|
||||
Entry entry = nearby.get(i);
|
||||
String text = showDistance.getValue()
|
||||
? entry.name() + " " + (int) Math.round(entry.distance()) + "m"
|
||||
: entry.name();
|
||||
int color = entry.friend() ? 0xFF55FF55 : -1;
|
||||
|
||||
FrosFont.draw(e.getContext(), text, (int) x, (int) (y + i * lineHeight), color);
|
||||
widest = Math.max(widest, FrosFont.width(text));
|
||||
}
|
||||
|
||||
setWidth(widest);
|
||||
setHeight(lineHeight * nearby.size());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package cc.fros.features.modules.hud;
|
||||
|
||||
import cc.fros.util.render.FrosFont;
|
||||
|
||||
import cc.fros.event.impl.render.Render2DEvent;
|
||||
import cc.fros.features.modules.client.HudModule;
|
||||
import cc.fros.features.settings.Setting;
|
||||
@@ -24,10 +26,10 @@ public class TimeHudModule extends HudModule {
|
||||
DateTimeFormatter formatter = twentyFourHour.getValue() ? TWENTY_FOUR_HOUR : TWELVE_HOUR;
|
||||
String timeStr = LocalTime.now().format(formatter);
|
||||
|
||||
e.getContext().drawString(mc.font, timeStr,
|
||||
FrosFont.draw(e.getContext(), timeStr,
|
||||
(int) getX(), (int) getY(), -1);
|
||||
|
||||
setWidth(mc.font.width(timeStr));
|
||||
setHeight(mc.font.lineHeight);
|
||||
setWidth(FrosFont.width(timeStr));
|
||||
setHeight(FrosFont.lineHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cc.fros.features.modules.hud;
|
||||
|
||||
import cc.fros.util.render.FrosFont;
|
||||
|
||||
import cc.fros.event.impl.render.Render2DEvent;
|
||||
import cc.fros.features.modules.client.HudModule;
|
||||
import cc.fros.features.settings.Setting;
|
||||
@@ -27,10 +29,10 @@ public class WatermarkHudModule extends HudModule {
|
||||
}
|
||||
|
||||
Component component = TextUtil.text(watermarkString, text.getValue(), BuildConfig.VERSION);
|
||||
e.getContext().drawString(mc.font, component, (int) getX(), (int) getY(), -1);
|
||||
FrosFont.draw(e.getContext(), component, (int) getX(), (int) getY(), -1);
|
||||
|
||||
setWidth(mc.font.width(component));
|
||||
setHeight(mc.font.lineHeight);
|
||||
setWidth(FrosFont.width(component));
|
||||
setHeight(FrosFont.lineHeight());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cc.fros.features.modules.hud;
|
||||
|
||||
import cc.fros.util.render.FrosFont;
|
||||
|
||||
import cc.fros.event.impl.render.Render2DEvent;
|
||||
import cc.fros.features.modules.client.HudModule;
|
||||
import cc.fros.features.settings.Setting;
|
||||
@@ -17,10 +19,10 @@ public class WelcomerHudModule extends HudModule {
|
||||
|
||||
String welcomeStr = greeting.getValue() + ", " + mc.getUser().getName();
|
||||
|
||||
e.getContext().drawString(mc.font, welcomeStr,
|
||||
FrosFont.draw(e.getContext(), welcomeStr,
|
||||
(int) getX(), (int) getY(), -1);
|
||||
|
||||
setWidth(mc.font.width(welcomeStr));
|
||||
setHeight(mc.font.lineHeight);
|
||||
setWidth(FrosFont.width(welcomeStr));
|
||||
setHeight(FrosFont.lineHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public class FullbrightModule extends Module {
|
||||
|
||||
private Mode activeMode = Mode.GAMMA;
|
||||
private double previousGamma;
|
||||
private boolean gammaCaptured;
|
||||
private boolean hadNightVision;
|
||||
|
||||
public FullbrightModule() {
|
||||
@@ -52,16 +53,28 @@ public class FullbrightModule extends Module {
|
||||
|
||||
private void prepare(Mode newMode) {
|
||||
if (newMode == Mode.GAMMA) {
|
||||
previousGamma = mc.options.gamma().get();
|
||||
gammaCaptured = false;
|
||||
// during config load mc.options is still null, so this can be a no-op;
|
||||
// applyGamma() captures lazily on the first real tick instead
|
||||
captureGamma();
|
||||
return;
|
||||
}
|
||||
hadNightVision = !nullCheck() && mc.player.hasEffect(MobEffects.NIGHT_VISION);
|
||||
}
|
||||
|
||||
private void captureGamma() {
|
||||
if (gammaCaptured || mc.options == null) return;
|
||||
previousGamma = mc.options.gamma().get();
|
||||
gammaCaptured = true;
|
||||
}
|
||||
|
||||
private void restore(Mode oldMode) {
|
||||
if (oldMode == Mode.GAMMA) {
|
||||
// bypass OptionInstance#set, which rejects values outside [0.0, 1.0]
|
||||
mc.options.gamma().value = previousGamma;
|
||||
if (gammaCaptured && mc.options != null) {
|
||||
// bypass OptionInstance#set, which rejects values outside [0.0, 1.0]
|
||||
mc.options.gamma().value = previousGamma;
|
||||
}
|
||||
gammaCaptured = false;
|
||||
return;
|
||||
}
|
||||
if (!nullCheck() && !hadNightVision) {
|
||||
@@ -70,6 +83,8 @@ public class FullbrightModule extends Module {
|
||||
}
|
||||
|
||||
private void applyGamma() {
|
||||
if (mc.options == null) return;
|
||||
captureGamma();
|
||||
mc.options.gamma().value = gamma.getValue();
|
||||
}
|
||||
|
||||
|
||||
@@ -111,4 +111,9 @@ public class CommandManager extends Feature implements Jsonable {
|
||||
setCommandPrefix(object.get("prefix").getAsString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return "commands.json";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ public class ConfigManager {
|
||||
mkdirs();
|
||||
for (Jsonable jsonable : jsonables) {
|
||||
Path path = FROS_PATH.resolve(jsonable.getFileName());
|
||||
if (!Files.exists(path)) continue;
|
||||
// isRegularFile, not exists: a Jsonable with no file name resolves to the
|
||||
// config directory itself, and readString on a directory throws IOException
|
||||
if (!Files.isRegularFile(path)) continue;
|
||||
try {
|
||||
String read = Files.readString(path);
|
||||
jsonable.fromJson(JsonParser.parseString(read));
|
||||
@@ -51,6 +53,7 @@ public class ConfigManager {
|
||||
try {
|
||||
JsonElement json = jsonable.toJson();
|
||||
Path path = FROS_PATH.resolve(jsonable.getFileName());
|
||||
if (Files.isDirectory(path)) continue;
|
||||
Files.writeString(path, GSON.toJson(json));
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error("Failed to write to file", e);
|
||||
|
||||
@@ -17,9 +17,11 @@ import cc.fros.features.modules.client.NotificationsModule;
|
||||
import cc.fros.features.modules.combat.KillAuraModule;
|
||||
import cc.fros.features.modules.combat.CriticalsModule;
|
||||
import cc.fros.features.modules.combat.KeyPearlModule;
|
||||
import cc.fros.features.modules.hud.ArrayListHudModule;
|
||||
import cc.fros.features.modules.hud.CoordinatesHudModule;
|
||||
import cc.fros.features.modules.hud.FpsHudModule;
|
||||
import cc.fros.features.modules.hud.PingHudModule;
|
||||
import cc.fros.features.modules.hud.RadarHudModule;
|
||||
import cc.fros.features.modules.hud.TimeHudModule;
|
||||
import cc.fros.features.modules.hud.WelcomerHudModule;
|
||||
import cc.fros.features.modules.hud.WatermarkHudModule;
|
||||
@@ -47,10 +49,12 @@ public class ModuleManager implements Jsonable, Util {
|
||||
private final List<Module> modules = new ArrayList<>();
|
||||
|
||||
public void init() {
|
||||
register(new ArrayListHudModule());
|
||||
register(new WatermarkHudModule());
|
||||
register(new CoordinatesHudModule());
|
||||
register(new FpsHudModule());
|
||||
register(new PingHudModule());
|
||||
register(new RadarHudModule());
|
||||
register(new WelcomerHudModule());
|
||||
register(new TimeHudModule());
|
||||
register(new HudEditorModule());
|
||||
@@ -163,8 +167,14 @@ public class ModuleManager implements Jsonable, Util {
|
||||
|
||||
@Override
|
||||
public void fromJson(JsonElement element) {
|
||||
if (element == null || !element.isJsonObject()) return;
|
||||
for (Module module : getModules()) {
|
||||
module.fromJson(element.getAsJsonObject().get(module.getName()));
|
||||
// one broken module must never abort loading the rest
|
||||
try {
|
||||
module.fromJson(element.getAsJsonObject().get(module.getName()));
|
||||
} catch (Throwable throwable) {
|
||||
LOGGER.error("Failed to load module {}", module.getName(), throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,480 @@
|
||||
package cc.fros.util.render;
|
||||
|
||||
import cc.fros.features.modules.client.ClickGuiModule;
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.network.chat.TextColor;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static cc.fros.util.traits.Util.mc;
|
||||
|
||||
/**
|
||||
* Renders client UI text with a user supplied ttf/otf font.
|
||||
* Text is rasterized with AWT and cached as textures; when no font
|
||||
* is configured everything falls through to the vanilla font.
|
||||
*/
|
||||
public final class FrosFont {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("FrosFont");
|
||||
private static final Path FONT_DIR = FabricLoader.getInstance().getGameDir().resolve("fros").resolve("fonts");
|
||||
private static final int MAX_CACHE = 400;
|
||||
|
||||
private static final int[] PALETTE = {
|
||||
0x000000, 0x0000AA, 0x00AA00, 0x00AAAA,
|
||||
0xAA0000, 0xAA00AA, 0xFFAA00, 0xAAAAAA,
|
||||
0x555555, 0x5555FF, 0x55FF55, 0x55FFFF,
|
||||
0xFF5555, 0xFF55FF, 0xFFFF55, 0xFFFFFF
|
||||
};
|
||||
private static final char[] HEX = "0123456789abcdef".toCharArray();
|
||||
|
||||
// java guarantees these logical fonts on every os
|
||||
private static final Map<String, String> LOGICAL_FONTS = Map.of(
|
||||
"sansserif", "SansSerif",
|
||||
"serif", "Serif",
|
||||
"monospaced", "Monospaced");
|
||||
|
||||
private static String[] installedFamilies;
|
||||
|
||||
private static final Map<Key, Glyph> GLYPHS = new LinkedHashMap<>(128, 0.75f, true) {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<Key, Glyph> eldest) {
|
||||
if (size() <= MAX_CACHE) return false;
|
||||
queueRelease(eldest.getValue().id());
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// textures still referenced by this frame's queued draws are only
|
||||
// released a moment later, once the frame has been flushed
|
||||
private static final List<Identifier> PENDING_RELEASE = new ArrayList<>();
|
||||
private static long lastQueuedAt;
|
||||
|
||||
private record Key(int color, String text, boolean shadow) {
|
||||
}
|
||||
|
||||
private record Glyph(Identifier id, int width, int height) {
|
||||
}
|
||||
|
||||
private static java.awt.Font awtFont;
|
||||
private static FontMetrics metrics;
|
||||
private static String loadedName;
|
||||
private static float loadedSize = Float.NaN;
|
||||
private static float loadedScale = 1;
|
||||
/** raster pixels from the top of a line box to the middle of the cap band */
|
||||
private static float visualCenterRaster = 4f;
|
||||
private static long lastAttempt;
|
||||
private static int glyphCounter;
|
||||
|
||||
private FrosFont() {
|
||||
}
|
||||
|
||||
public static int width(String text) {
|
||||
if (text == null || text.isEmpty()) return 0;
|
||||
sync();
|
||||
if (awtFont == null) return mc.font.width(text);
|
||||
return toGui(metrics.stringWidth(stripCodes(text)));
|
||||
}
|
||||
|
||||
public static int width(Component component) {
|
||||
if (component == null) return 0;
|
||||
sync();
|
||||
if (awtFont == null) return mc.font.width(component);
|
||||
return width(toCodeString(component));
|
||||
}
|
||||
|
||||
public static int lineHeight() {
|
||||
sync();
|
||||
return awtFont == null ? mc.font.lineHeight : toGui(metrics.getHeight());
|
||||
}
|
||||
|
||||
/** converts a raster pixel measurement to gui units */
|
||||
private static int toGui(int pixels) {
|
||||
return (int) Math.ceil(pixels / (double) loadedScale);
|
||||
}
|
||||
|
||||
private static float currentScale() {
|
||||
try {
|
||||
// one raster pixel per screen pixel, following both mc's gui scale
|
||||
// and the client's own ui scale slider
|
||||
return Math.max(0.5f, (float) (mc.getWindow().getGuiScale() * ClickGuiModule.scale()));
|
||||
} catch (Exception e) {
|
||||
return 1f;
|
||||
}
|
||||
}
|
||||
|
||||
public static void draw(GuiGraphics context, String text, int x, int y, int color) {
|
||||
draw(context, text, x, y, color, true);
|
||||
}
|
||||
|
||||
public static void draw(GuiGraphics context, String text, int x, int y, int color, boolean shadow) {
|
||||
if (text == null || text.isEmpty()) return;
|
||||
sync();
|
||||
if (awtFont == null) {
|
||||
context.drawString(mc.font, text, x, y, color, shadow);
|
||||
return;
|
||||
}
|
||||
if (shadow) drawGlyphs(context, text, x + 1, y + 1, color, true);
|
||||
drawGlyphs(context, text, x, y, color, false);
|
||||
}
|
||||
|
||||
public static void draw(GuiGraphics context, Component component, int x, int y, int color) {
|
||||
if (component == null) return;
|
||||
sync();
|
||||
if (awtFont == null) {
|
||||
context.drawString(mc.font, component, x, y, color, true);
|
||||
return;
|
||||
}
|
||||
draw(context, toCodeString(component), x, y, color);
|
||||
}
|
||||
|
||||
// --- loading ---
|
||||
|
||||
private static void sync() {
|
||||
ClickGuiModule gui = ClickGuiModule.getInstance();
|
||||
String name = gui == null ? "" : gui.font.getValue();
|
||||
int size = gui == null ? 10 : gui.fontSize.getValue();
|
||||
if (name == null) name = "";
|
||||
float scale = currentScale();
|
||||
|
||||
boolean changed = !Objects.equals(name, loadedName) || size != loadedSize || scale != loadedScale;
|
||||
if (!changed) {
|
||||
boolean missing = awtFont == null && !name.isBlank();
|
||||
// retry a missing file every couple of seconds so dropping one
|
||||
// into the folder loads without touching the setting again
|
||||
if (!missing || System.currentTimeMillis() - lastAttempt < 2000) return;
|
||||
}
|
||||
reset(name, size, scale, changed);
|
||||
}
|
||||
|
||||
private static void reset(String name, float size, float scale, boolean verbose) {
|
||||
loadedName = name;
|
||||
loadedSize = size;
|
||||
loadedScale = scale;
|
||||
lastAttempt = System.currentTimeMillis();
|
||||
awtFont = null;
|
||||
metrics = null;
|
||||
clearGlyphs();
|
||||
|
||||
try {
|
||||
Files.createDirectories(FONT_DIR);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Could not create font directory {}", FONT_DIR, e);
|
||||
return;
|
||||
}
|
||||
if (name.isBlank()) return;
|
||||
try {
|
||||
Path file = resolveFontFile(name);
|
||||
if (file != null) {
|
||||
applyFont(java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, file.toFile()),
|
||||
size * scale, "Loaded font " + file.getFileName(), verbose);
|
||||
return;
|
||||
}
|
||||
|
||||
java.awt.Font installed = installedFont(name);
|
||||
if (installed != null) {
|
||||
applyFont(installed, size * scale, "Using system font '" + name + "'", verbose);
|
||||
return;
|
||||
}
|
||||
|
||||
if (verbose) LOGGER.warn("Font '{}' not found in {}", name, FONT_DIR);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load font '{}'", name, e);
|
||||
awtFont = null;
|
||||
metrics = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyFont(java.awt.Font font, float size, String source, boolean verbose) {
|
||||
awtFont = font.deriveFont(java.awt.Font.PLAIN, size);
|
||||
BufferedImage probe = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = probe.createGraphics();
|
||||
graphics.setFont(awtFont);
|
||||
metrics = graphics.getFontMetrics();
|
||||
// distance from the top of the line box to the middle of the cap band:
|
||||
// drawString puts the baseline at metrics.getAscent(), and "A" spans
|
||||
// from the baseline up to the cap line
|
||||
java.awt.geom.Rectangle2D capBounds = awtFont
|
||||
.createGlyphVector(graphics.getFontRenderContext(), "A")
|
||||
.getVisualBounds();
|
||||
float capHeight = (float) capBounds.getHeight();
|
||||
if (capHeight <= 0f || capHeight > metrics.getAscent()) {
|
||||
capHeight = metrics.getAscent();
|
||||
}
|
||||
visualCenterRaster = metrics.getAscent() - capHeight / 2f;
|
||||
graphics.dispose();
|
||||
if (verbose) LOGGER.info("{} ({}pt)", source, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Distance from the top of a line box down to the visual center of its
|
||||
* text (middle of the cap band), in gui units. lets callers center labels
|
||||
* by what the eye sees instead of the line box, and behaves the same for
|
||||
* the custom font and the vanilla fallback.
|
||||
*/
|
||||
public static float visualCenter() {
|
||||
sync();
|
||||
if (awtFont == null) return mc.font.lineHeight / 2f - 1f;
|
||||
return visualCenterRaster / loadedScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves java's logical fonts (guaranteed on every os) and any font
|
||||
* family already installed on the system, without needing a file.
|
||||
*/
|
||||
private static java.awt.Font installedFont(String name) {
|
||||
String logical = LOGICAL_FONTS.get(name.toLowerCase(Locale.ROOT));
|
||||
if (logical != null) return new java.awt.Font(logical, java.awt.Font.PLAIN, 1);
|
||||
|
||||
if (installedFamilies == null) {
|
||||
installedFamilies = java.awt.GraphicsEnvironment
|
||||
.getLocalGraphicsEnvironment()
|
||||
.getAvailableFontFamilyNames();
|
||||
}
|
||||
for (String family : installedFamilies) {
|
||||
if (family.equalsIgnoreCase(name)) return new java.awt.Font(family, java.awt.Font.PLAIN, 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Path resolveFontFile(String name) {
|
||||
try {
|
||||
Files.createDirectories(FONT_DIR);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Could not create font directory {}", FONT_DIR, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
String lower = name.toLowerCase(Locale.ROOT);
|
||||
if (lower.endsWith(".ttf") || lower.endsWith(".otf")) {
|
||||
Path direct = FONT_DIR.resolve(name);
|
||||
return Files.isRegularFile(direct) ? direct : null;
|
||||
}
|
||||
|
||||
try (Stream<Path> files = Files.list(FONT_DIR)) {
|
||||
return files.filter(Files::isRegularFile)
|
||||
.filter(path -> {
|
||||
String fileName = path.getFileName().toString().toLowerCase(Locale.ROOT);
|
||||
return fileName.equals(lower)
|
||||
|| fileName.equals(lower + ".ttf")
|
||||
|| fileName.equals(lower + ".otf");
|
||||
})
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Could not list font directory {}", FONT_DIR, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearGlyphs() {
|
||||
for (Glyph glyph : new ArrayList<>(GLYPHS.values())) {
|
||||
queueRelease(glyph.id());
|
||||
}
|
||||
GLYPHS.clear();
|
||||
}
|
||||
|
||||
private static void queueRelease(Identifier id) {
|
||||
lastQueuedAt = System.currentTimeMillis();
|
||||
PENDING_RELEASE.add(id);
|
||||
}
|
||||
|
||||
private static void flushReleases() {
|
||||
if (PENDING_RELEASE.isEmpty()) return;
|
||||
if (System.currentTimeMillis() - lastQueuedAt < 250) return;
|
||||
for (Identifier id : PENDING_RELEASE) {
|
||||
mc.getTextureManager().release(id);
|
||||
}
|
||||
PENDING_RELEASE.clear();
|
||||
}
|
||||
|
||||
// --- rasterizing ---
|
||||
|
||||
private static void drawGlyphs(GuiGraphics context, String text, int x, int y, int color, boolean shadow) {
|
||||
flushReleases();
|
||||
Key key = new Key(color, text, shadow);
|
||||
Glyph glyph = GLYPHS.get(key);
|
||||
if (glyph == null) {
|
||||
glyph = createGlyph(text, color, shadow);
|
||||
if (glyph == null) return;
|
||||
GLYPHS.put(key, glyph);
|
||||
}
|
||||
|
||||
// the bitmap is rasterized at device resolution (one texel per screen
|
||||
// pixel); scale the pose down into gui space so the gpu never has to
|
||||
// magnify it with nearest-neighbour sampling
|
||||
float scale = loadedScale;
|
||||
context.pose().pushMatrix();
|
||||
context.pose().scale(1f / scale, 1f / scale);
|
||||
context.pose().translate((x - 1) * scale, (y - 1) * scale);
|
||||
context.blit(RenderPipelines.GUI_TEXTURED, glyph.id(),
|
||||
0, 0, 0.0f, 0.0f, glyph.width(), glyph.height(), glyph.width(), glyph.height());
|
||||
context.pose().popMatrix();
|
||||
}
|
||||
|
||||
private static Glyph createGlyph(String text, int baseColor, boolean shadow) {
|
||||
String plain = stripCodes(text);
|
||||
if (plain.isEmpty()) return null;
|
||||
|
||||
int pad = Math.max(1, Math.round(loadedScale));
|
||||
int width = Math.max(1, metrics.stringWidth(plain));
|
||||
int height = metrics.getHeight();
|
||||
int imageWidth = width + 2 * pad;
|
||||
int imageHeight = height + 2 * pad;
|
||||
|
||||
BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
graphics.setFont(awtFont);
|
||||
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
drawSegments(graphics, text, baseColor, shadow);
|
||||
graphics.dispose();
|
||||
|
||||
NativeImage nativeImage = new NativeImage(imageWidth, imageHeight, false);
|
||||
for (int py = 0; py < imageHeight; py++) {
|
||||
for (int px = 0; px < imageWidth; px++) {
|
||||
int argb = image.getRGB(px, py);
|
||||
int abgr = (argb & 0xFF000000)
|
||||
| ((argb & 0x000000FF) << 16)
|
||||
| (argb & 0x0000FF00)
|
||||
| ((argb & 0x00FF0000) >> 16);
|
||||
nativeImage.setPixelABGR(px, py, abgr);
|
||||
}
|
||||
}
|
||||
|
||||
Identifier id = Identifier.fromNamespaceAndPath("fros", "font/glyph_" + glyphCounter++);
|
||||
DynamicTexture texture = new DynamicTexture(() -> id.toString(), nativeImage);
|
||||
texture.upload();
|
||||
mc.getTextureManager().register(id, texture);
|
||||
return new Glyph(id, imageWidth, imageHeight);
|
||||
}
|
||||
|
||||
private static void drawSegments(Graphics2D graphics, String text, int baseColor, boolean shadow) {
|
||||
int baseline = metrics.getAscent() + Math.round(loadedScale);
|
||||
int cursor = 1;
|
||||
int current = baseColor;
|
||||
int segmentStart = 0;
|
||||
int i = 0;
|
||||
|
||||
while (i < text.length()) {
|
||||
if (text.charAt(i) == '\u00A7' && i + 1 < text.length()) {
|
||||
// colored segments darken like vanilla's shadows do, instead
|
||||
// of punching through at full brightness
|
||||
cursor = flush(graphics, text, segmentStart, i, cursor,
|
||||
shadow ? shadowColor(current) : current, baseline);
|
||||
|
||||
char code = Character.toLowerCase(text.charAt(i + 1));
|
||||
i += 2;
|
||||
segmentStart = i;
|
||||
|
||||
if (code == 'x') {
|
||||
int rgb = 0;
|
||||
int nibbles = 0;
|
||||
while (nibbles < 6 && i + 1 < text.length() && text.charAt(i) == '\u00A7') {
|
||||
int digit = Character.digit(Character.toLowerCase(text.charAt(i + 1)), 16);
|
||||
if (digit < 0) break;
|
||||
rgb = (rgb << 4) | digit;
|
||||
i += 2;
|
||||
nibbles++;
|
||||
}
|
||||
current = 0xFF000000 | rgb;
|
||||
segmentStart = i;
|
||||
} else if (code == 'r') {
|
||||
current = baseColor;
|
||||
} else {
|
||||
int index = Character.digit(code, 16);
|
||||
if (index >= 0) current = 0xFF000000 | PALETTE[index];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
flush(graphics, text, segmentStart, text.length(), cursor,
|
||||
shadow ? shadowColor(current) : current, baseline);
|
||||
}
|
||||
|
||||
private static int flush(Graphics2D graphics, String text, int start, int end, int cursor, int color, int baseline) {
|
||||
if (end <= start) return cursor;
|
||||
String segment = text.substring(start, end);
|
||||
graphics.setColor(new Color(color, true));
|
||||
graphics.drawString(segment, cursor, baseline);
|
||||
return cursor + metrics.stringWidth(segment);
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
private static String stripCodes(String text) {
|
||||
if (text.indexOf('\u00A7') < 0) return text;
|
||||
StringBuilder builder = new StringBuilder(text.length());
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
if (c == '\u00A7' && i + 1 < text.length()) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String toCodeString(Component component) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
TextColor[] previous = {null};
|
||||
|
||||
component.visit((style, text) -> {
|
||||
TextColor color = style.getColor();
|
||||
if (Objects.equals(color, previous[0])) {
|
||||
builder.append(text);
|
||||
return Optional.empty();
|
||||
}
|
||||
if (color == null) {
|
||||
builder.append('\u00A7').append('r');
|
||||
} else {
|
||||
appendColorCode(builder, color.getValue());
|
||||
}
|
||||
previous[0] = color;
|
||||
builder.append(text);
|
||||
return Optional.empty();
|
||||
}, Style.EMPTY);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static void appendColorCode(StringBuilder builder, int rgb) {
|
||||
for (int i = 0; i < PALETTE.length; i++) {
|
||||
if (PALETTE[i] == (rgb & 0xFFFFFF)) {
|
||||
builder.append('\u00A7').append(HEX[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
builder.append('\u00A7').append('x');
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
builder.append('\u00A7').append(HEX[(rgb >> (i * 4)) & 0xF]);
|
||||
}
|
||||
}
|
||||
|
||||
private static int shadowColor(int color) {
|
||||
return ((color & 0xFCFCFC) >> 2) | (color & 0xFF000000);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user