added files.

This commit is contained in:
nitrogenoliquido committed 2026-10-14 22:15:53 -05:00
commit e908ab2355
353 files changed
+130515

No files matched your search

@@ -0,0 +1,91 @@
package dev.slinky;
import dev.slinky.client.command.SlinkyCommand;
import dev.slinky.client.event.ClientEventBridge;
import dev.slinky.client.gui.GuiAppearance;
import dev.slinky.client.gui.profile.ProfileServices;
import dev.slinky.client.hook.GameHooks;
import dev.slinky.config.ConfigStore;
import dev.slinky.config.InitialSettings;
import dev.slinky.inject.ReloadState;
import dev.slinky.module.CoreModules;
import dev.slinky.module.ModuleManager;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import net.minecraft.client.Minecraft;
import net.minecraft.client.settings.KeyBinding;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public final class SlinkyMod {
public static final String MOD_ID = "slinkysemantic";
public static final String NAME = "Slinky Semantic";
public static final String VERSION = "0.5.0-settings";
private ModuleManager modules;
private ConfigStore config;
private final Logger logger = LogManager.getLogger("Slinky Semantic");
public void preInitialize() {
this.modules = new ModuleManager();
CoreModules.register(this.modules);
GuiAppearance.configureMissing(this.modules.find("GUI"));
InitialSettings.applyMissing(this.modules);
GameHooks.install(this.modules);
Path base = slinkyDirectory();
Path configPath = base.resolve("slinky-semantic.json");
try {
Files.createDirectories(base);
} catch (IOException var5) {
this.logger.error("Could not create the configuration directory", var5);
}
this.config = new ConfigStore(configPath);
this.modules.input().profiles(this.config, new ProfileServices.FilesHost(this.modules, base.resolve("profiles"), this.config));
this.modules.setPersistence(new Runnable() {
@Override
public void run() {
SlinkyMod.this.saveConfiguration();
}
});
try {
this.config.load(this.modules);
} catch (IOException var4) {
this.logger.error("Could not load Slinky configuration", var4);
}
}
public void initialize() {
GuiAppearance.configureMissing(this.modules.find("GUI"));
KeyBinding openGui = new KeyBinding("key.slinkysemantic.open", 54, "key.categories.slinkysemantic");
ClientEventBridge.install(new ClientEventBridge(this.modules, openGui, new SlinkyCommand(this.modules)));
this.modules.persist();
Thread hook = new Thread(new Runnable() {
@Override
public void run() {
SlinkyMod.this.modules.persistOnExit();
}
}, "slinky-config-save");
ReloadState.replaceHook(hook);
}
public ModuleManager modules() {
return this.modules;
}
public static Path slinkyDirectory() {
String home = System.getProperty("user.home");
return home != null && !home.isEmpty() ? Paths.get(home).resolve(".slinky") : Minecraft.getMinecraft().mcDataDir.toPath().resolve("config");
}
private void saveConfiguration() {
try {
this.config.save(this.modules);
} catch (IOException var2) {
this.logger.error("Could not save Slinky configuration", var2);
}
}
}
@@ -0,0 +1,39 @@
package dev.slinky.client;
import java.util.function.BooleanSupplier;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.multiplayer.WorldClient;
public final class ClientContext {
private final Minecraft minecraft;
private BooleanSupplier guiEnabled = () -> false;
public ClientContext(Minecraft minecraft) {
this.minecraft = minecraft;
}
public Minecraft minecraft() {
return this.minecraft;
}
public EntityPlayerSP player() {
return this.minecraft.thePlayer;
}
public WorldClient world() {
return this.minecraft.theWorld;
}
public boolean ready() {
return this.player() != null && this.world() != null;
}
public void setGuiEnabled(BooleanSupplier guiEnabled) {
this.guiEnabled = guiEnabled;
}
public boolean inputReady() {
return this.ready() && this.minecraft.inGameHasFocus && !this.guiEnabled.getAsBoolean();
}
}
@@ -0,0 +1,112 @@
package dev.slinky.client;
import dev.slinky.inject.ReflectionHelper;
import java.lang.reflect.Field;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.PlayerControllerMP;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.Timer;
public final class MinecraftFields {
private static final Field TIMER = field(Minecraft.class, "timer", "field_71428_T");
private static final Field JUMP_TICKS = field(EntityLivingBase.class, "jumpTicks", "field_70773_bE");
private static final Field POTION_DURATION = field(PotionEffect.class, "duration", "field_76460_b");
private static final Field LEFT_CLICK_COUNTER = field(Minecraft.class, "leftClickCounter", "field_71429_W");
private static final Field BLOCK_HIT_DELAY = field(PlayerControllerMP.class, "blockHitDelay", "field_78781_i");
private static final Field BLOCK_DAMAGE = field(PlayerControllerMP.class, "curBlockDamageMP", "field_78770_f");
private static final Field BREAKING = field(PlayerControllerMP.class, "isHittingBlock", "field_78778_j");
public static boolean breaking(PlayerControllerMP controller) {
return (Boolean)get(BREAKING, controller);
}
public static void setBreaking(PlayerControllerMP controller, boolean value) {
try {
BREAKING.setBoolean(controller, value);
} catch (IllegalAccessException var3) {
throw new IllegalStateException(var3);
}
}
private MinecraftFields() {
}
public static Timer timer(Minecraft minecraft) {
return (Timer)get(TIMER, minecraft);
}
public static void setJumpTicks(EntityLivingBase entity, int ticks) {
setInt(JUMP_TICKS, entity, ticks);
}
public static int jumpTicks(EntityLivingBase entity) {
return getInt(JUMP_TICKS, entity);
}
public static void setPotionDuration(PotionEffect effect, int ticks) {
setInt(POTION_DURATION, effect, ticks);
}
public static int leftClickCounter(Minecraft minecraft) {
return getInt(LEFT_CLICK_COUNTER, minecraft);
}
public static void setLeftClickCounter(Minecraft minecraft, int ticks) {
setInt(LEFT_CLICK_COUNTER, minecraft, ticks);
}
public static int blockHitDelay(PlayerControllerMP controller) {
return getInt(BLOCK_HIT_DELAY, controller);
}
public static void setBlockHitDelay(PlayerControllerMP controller, int ticks) {
setInt(BLOCK_HIT_DELAY, controller, ticks);
}
public static float blockDamage(PlayerControllerMP controller) {
try {
return BLOCK_DAMAGE.getFloat(controller);
} catch (IllegalAccessException var2) {
throw new IllegalStateException("cannot read " + BLOCK_DAMAGE.getName(), var2);
}
}
public static void setBlockDamage(PlayerControllerMP controller, float value) {
try {
BLOCK_DAMAGE.setFloat(controller, value);
} catch (IllegalAccessException var3) {
throw new IllegalStateException("cannot write " + BLOCK_DAMAGE.getName(), var3);
}
}
private static Field field(Class<?> type, String mcpName, String srgName) {
Field field = ReflectionHelper.findField(type, mcpName, srgName);
field.setAccessible(true);
return field;
}
private static Object get(Field field, Object target) {
try {
return field.get(target);
} catch (IllegalAccessException var3) {
throw new IllegalStateException("cannot read " + field.getName(), var3);
}
}
private static void setInt(Field field, Object target, int value) {
try {
field.setInt(target, value);
} catch (IllegalAccessException var4) {
throw new IllegalStateException("cannot write " + field.getName(), var4);
}
}
private static int getInt(Field field, Object target) {
try {
return field.getInt(target);
} catch (IllegalAccessException var3) {
throw new IllegalStateException("cannot read " + field.getName(), var3);
}
}
}
@@ -0,0 +1,143 @@
package dev.slinky.client.action;
import dev.slinky.client.ClientContext;
import dev.slinky.client.inventory.InventoryActions;
import dev.slinky.inject.ReflectionHelper;
import java.lang.reflect.Field;
import java.util.Objects;
import net.minecraft.block.Block;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.BlockPos;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.MovingObjectPosition.MovingObjectType;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.Vec3;
import net.minecraft.world.IBlockAccess;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
public final class ActionAccess {
private static final Field PRESSES = ReflectionHelper.findField(KeyBinding.class, "pressTime", "field_151474_i");
private static final Field HOVER = ReflectionHelper.findField(GuiContainer.class, "theSlot", "field_147006_u");
private static final Field USE_COUNT = ReflectionHelper.findField(EntityPlayer.class, "itemInUseCount", "field_71072_f");
private static volatile ActionAccess.MouseHost mouse = new ActionAccess.MouseHost() {
@Override
public boolean physical(int button) {
return Mouse.isButtonDown(button == 1 ? 0 : 1);
}
@Override
public boolean shift() {
return Keyboard.isKeyDown(42) || Keyboard.isKeyDown(54);
}
@Override
public void emit(ClientContext c, int button, boolean down) {
if (c.minecraft().currentScreen instanceof GuiContainer) {
if (down) {
GuiContainer screen = (GuiContainer)c.minecraft().currentScreen;
Slot slot = ActionAccess.hovered(screen);
if (slot != null) {
InventoryActions.click(screen, slot.slotNumber, button == 1 ? 0 : 1, this.shift() ? 1 : 0);
}
}
} else {
int code = button == 1 ? -100 : -99;
KeyBinding.setKeyBindState(code, down);
if (down) {
KeyBinding.onTick(code);
}
}
}
};
private ActionAccess() {
}
public static int presses(KeyBinding key) {
try {
return PRESSES.getInt(key);
} catch (IllegalAccessException var2) {
throw new IllegalStateException(var2);
}
}
public static void increment(KeyBinding key) {
InventoryActions.pressCount(key, presses(key) + 1);
}
public static Slot hovered(GuiContainer screen) {
try {
return (Slot)HOVER.get(screen);
} catch (IllegalAccessException var2) {
throw new IllegalStateException(var2);
}
}
public static void useCount(EntityPlayer p, int value) {
try {
USE_COUNT.setInt(p, value);
} catch (IllegalAccessException var3) {
throw new IllegalStateException(var3);
}
}
public static boolean canBlock(ItemStack stack) {
int id = InventoryActions.type(stack) - 267;
return Integer.compareUnsigned(id, 16) <= 0 && (66083 >>> (id & 31) & 1) != 0;
}
public static boolean isBlock(ItemStack stack) {
if (stack == null) {
return false;
} else {
Item item = stack.getItem();
if (item == null) {
return false;
} else {
Block block = Block.getBlockFromItem(item);
return block != null && Block.getIdFromBlock(block) != 0;
}
}
}
public static boolean registeredAir(IBlockAccess world, BlockPos position) {
return Block.getIdFromBlock(world.getBlockState(position).getBlock()) == 0;
}
public static Vec3 adjacentPosition(MovingObjectPosition hit) {
BlockPos block = hit.getBlockPos();
double x = (double)block.getX();
double y = (double)block.getY();
double z = (double)block.getZ();
EnumFacing face = hit.sideHit;
return new Vec3(x + (double)face.getFrontOffsetX(), y + (double)face.getFrontOffsetY(), (double)face.getFrontOffsetZ() + z);
}
public static int hitKind(MovingObjectPosition hit) {
return hit != null && hit.typeOfHit != MovingObjectType.MISS ? (hit.typeOfHit == MovingObjectType.BLOCK ? 1 : 2) : 0;
}
public static ActionAccess.MouseHost mouse() {
return mouse;
}
public static ActionAccess.MouseHost bindMouse(ActionAccess.MouseHost endpoint) {
ActionAccess.MouseHost previous = mouse;
mouse = Objects.requireNonNull(endpoint);
return previous;
}
public interface MouseHost {
boolean physical(int var1);
boolean shift();
void emit(ClientContext var1, int var2, boolean var3);
}
}
@@ -0,0 +1,73 @@
package dev.slinky.client.action;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import dev.slinky.inject.Platform;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
public final class ActionConstants {
private static final Map<Integer, Byte> BYTES = new HashMap<>();
private ActionConstants() {
}
private static long word(int address, int length) {
long result = 0L;
for (int i = 0; i < length; i++) {
Byte b = BYTES.get(address + i);
if (b == null) {
throw new IllegalArgumentException("Unrecovered action operand " + Integer.toHexString(address + i));
}
result |= (long)(b & 255) << 8 * i;
}
return result;
}
public static byte byteAt(int a) {
return (byte)((int)word(a, 1));
}
public static int shortAt(int a) {
return (int)word(a, 2);
}
public static int intAt(int a) {
return (int)word(a, 4);
}
public static long longAt(int a) {
return word(a, 8);
}
public static float f(int a) {
return Float.intBitsToFloat(intAt(a));
}
public static double d(int a) {
return Double.longBitsToDouble(longAt(a));
}
static {
try (Reader reader = new InputStreamReader(Platform.resource("/assets/slinkysemantic/ActionConstants.json"), "UTF-8")) {
for (JsonElement element : new JsonParser().parse(reader).getAsJsonArray()) {
JsonObject row = element.getAsJsonObject();
int address = row.get("address").getAsInt();
String hex = row.get("bytes").getAsString();
for (int i = 0; i < hex.length() / 2; i++) {
BYTES.put(address + i, (byte)Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16));
}
}
} catch (IOException var18) {
throw new ExceptionInInitializerError(var18);
}
}
}
@@ -0,0 +1,37 @@
package dev.slinky.client.action;
public final class ModuleActionService {
private static Object owner;
private ModuleActionService() {
}
public static synchronized boolean claim(Object token) {
if (owner == token) {
return true;
} else if (owner != null) {
return false;
} else {
owner = token;
return true;
}
}
public static synchronized void release(Object token) {
if (owner == token) {
owner = null;
}
}
public static synchronized Object owner() {
return owner;
}
public static synchronized boolean busy() {
return owner != null;
}
public static synchronized void clear() {
owner = null;
}
}
@@ -0,0 +1,38 @@
package dev.slinky.client.command;
import dev.slinky.inject.ReflectionHelper;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiChat;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiTextField;
public final class GuiMenuCommand {
public static boolean matches(String text) {
return text != null && text.trim().equals("!gui");
}
public static boolean handle(GuiScreen screen, int key, boolean pressed, ModuleManager modules) {
if (screen instanceof GuiChat && pressed && (key == 28 || key == 156)) {
GuiTextField input = ReflectionHelper.getPrivateValue(GuiChat.class, (GuiChat)screen, "inputField", "field_146415_a");
if (!matches(input.getText())) {
return false;
} else {
Module gui = modules.find("GUI");
if (gui == null) {
return false;
} else {
Minecraft.getMinecraft().displayGuiScreen(null);
modules.requestEnabled(gui, true);
return true;
}
}
} else {
return false;
}
}
private GuiMenuCommand() {
}
}
@@ -0,0 +1,189 @@
package dev.slinky.client.command;
import dev.slinky.SlinkyMod;
import dev.slinky.config.ConfigStore;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import dev.slinky.module.utility.FriendsModule;
import dev.slinky.setting.Setting;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.command.CommandBase;
import net.minecraft.command.ICommandSender;
import net.minecraft.util.BlockPos;
import net.minecraft.util.ChatComponentText;
public final class SlinkyCommand extends CommandBase {
private final ModuleManager modules;
public SlinkyCommand(ModuleManager modules) {
this.modules = modules;
}
public String getCommandName() {
return "slinky";
}
public String getCommandUsage(ICommandSender sender) {
return "/slinky <list|toggle|settings|set|save|import>";
}
public int getRequiredPermissionLevel() {
return 0;
}
public void processCommand(ICommandSender sender, String[] args) {
try {
if (args.length != 0 && !"list".equalsIgnoreCase(args[0])) {
if ("import".equalsIgnoreCase(args[0])) {
this.importConfiguration(sender, args);
} else if ("save".equalsIgnoreCase(args[0])) {
this.modules.setEnabled(this.requireModule(args, 1), this.requireModule(args, 1).isEnabled(), true);
reply(sender, "configuration saved");
} else {
Module module = this.requireModule(args, 1);
if ("friend".equalsIgnoreCase(args[0]) && module instanceof FriendsModule) {
if (!this.modules.context().ready()) {
throw new IllegalArgumentException("Enter a world first");
} else if (args.length < 3) {
throw new IllegalArgumentException("usage: /slinky friend friends <add|remove|near|clear> [name|radius]");
} else {
reply(sender, ((FriendsModule)module).command(this.modules.context(), args[2], args.length > 3 ? args[3] : ""));
this.modules.setEnabled(module, module.isEnabled(), true);
}
} else if ("toggle".equalsIgnoreCase(args[0])) {
this.modules.toggle(module);
reply(sender, module.getName() + " is " + (module.isEnabled() ? "enabled" : "disabled"));
} else if ("settings".equalsIgnoreCase(args[0])) {
for (Setting<?> setting : module.getSettings()) {
reply(sender, setting.getKey() + " = " + setting.format());
}
} else if ("set".equalsIgnoreCase(args[0])) {
if (args.length < 4) {
throw new IllegalArgumentException("usage: /slinky set <module> <key> <value>");
} else {
Setting<?> setting = module.findSetting(args[2]);
if (setting == null) {
throw new IllegalArgumentException("unknown setting " + args[2]);
} else {
setting.parseAndSet(join(args, 3));
this.modules.setEnabled(module, module.isEnabled(), true);
reply(sender, module.getName() + "." + setting.getKey() + " = " + setting.format());
}
}
} else {
throw new IllegalArgumentException(this.getCommandUsage(sender));
}
}
} else {
for (Module module : this.modules.all()) {
reply(sender, (module.isEnabled() ? "[on] " : "[off] ") + module.getId() + " - " + module.getDescription());
}
}
} catch (IllegalArgumentException var6) {
reply(sender, var6.getMessage());
}
}
public List<String> addTabCompletionOptions(ICommandSender sender, String[] args, BlockPos pos) {
if (args.length == 1) {
return getListOfStringsMatchingLastWord(args, new String[]{"list", "toggle", "settings", "set", "save", "import"});
} else if (args.length == 2) {
List<String> ids = new ArrayList<>();
for (Module module : this.modules.all()) {
ids.add(module.getId());
}
return getListOfStringsMatchingLastWord(args, ids.toArray(new String[ids.size()]));
} else {
if (args.length == 3 && "set".equalsIgnoreCase(args[0])) {
Module module = this.modules.find(args[1]);
if (module != null) {
List<String> keys = new ArrayList<>();
for (Setting<?> setting : module.getSettings()) {
keys.add(setting.getKey());
}
return getListOfStringsMatchingLastWord(args, keys.toArray(new String[keys.size()]));
}
}
return Collections.emptyList();
}
}
private void importConfiguration(ICommandSender sender, String[] args) {
try {
byte[] text;
String source;
if (args.length < 2) {
String clipboard = GuiScreen.getClipboardString();
if (clipboard == null || clipboard.trim().isEmpty()) {
throw new IllegalArgumentException("the clipboard is empty");
}
text = clipboard.trim().getBytes(StandardCharsets.UTF_8);
source = "the clipboard";
} else {
String name = join(args, 1);
Path file = SlinkyMod.slinkyDirectory().resolve("profiles").resolve(name);
if (!Files.isRegularFile(file)) {
file = Paths.get(name);
}
if (!Files.isRegularFile(file)) {
throw new IllegalArgumentException("no file " + name);
}
text = Files.readAllBytes(file);
source = file.getFileName().toString();
}
int records = ConfigStore.importText(this.modules, text);
this.modules.persist();
reply(sender, "imported " + records + " modules from " + source);
} catch (IOException var7) {
throw new IllegalArgumentException("import failed: " + var7.getMessage());
}
}
private Module requireModule(String[] args, int index) {
if (args.length <= index) {
throw new IllegalArgumentException("module id is required");
} else {
Module module = this.modules.find(args[index]);
if (module == null) {
throw new IllegalArgumentException("unknown module " + args[index]);
} else {
return module;
}
}
}
private static String join(String[] values, int start) {
StringBuilder joined = new StringBuilder();
for (int i = start; i < values.length; i++) {
if (joined.length() > 0) {
joined.append(' ');
}
joined.append(values[i]);
}
return joined.toString();
}
private static void reply(ICommandSender sender, String text) {
sender.addChatMessage(new ChatComponentText("[Slinky] " + text));
}
}
@@ -0,0 +1,265 @@
package dev.slinky.client.event;
import dev.slinky.client.command.GuiMenuCommand;
import dev.slinky.client.gui.ClickGuiScreen;
import dev.slinky.client.gui.GuiModuleList;
import dev.slinky.client.gui.settings.GuiModuleSettings;
import dev.slinky.client.hook.GameHooks;
import dev.slinky.client.input.MinecraftInputAdapter;
import dev.slinky.client.packet.PacketPipeline;
import dev.slinky.inject.ReflectionHelper;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import dev.slinky.module.combat.AutoBlockModule;
import dev.slinky.setting.Setting;
import io.netty.channel.Channel;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.network.NetworkManager;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.EnumChatFormatting;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
public final class ClientEventBridge {
private static ClientEventBridge instance;
private final ModuleManager modules;
private final KeyBinding openGui;
private final CommandBase command;
private boolean keyPending;
private boolean mousePending;
private Channel attachedChannel;
private static Field channelField;
public ClientEventBridge(ModuleManager modules, KeyBinding openGui, CommandBase command) {
this.modules = modules;
this.openGui = openGui;
this.command = command;
}
public static void install(ClientEventBridge bridge) {
synchronized (ClientEventBridge.class) {
ClientEventBridge previous = instance;
instance = bridge;
if (previous != null && previous != bridge) {
Channel channel = previous.attachedChannel;
previous.attachedChannel = null;
previous.keyPending = false;
previous.mousePending = false;
if (channel != null && channel.pipeline().get("slinky_semantic_packets") != null) {
channel.pipeline().remove("slinky_semantic_packets");
}
}
}
}
public static void clientTick(boolean start) {
ClientEventBridge bridge = instance;
if (bridge != null) {
if (start) {
bridge.attachPipeline();
}
bridge.modules.clientTick(start);
}
}
public static void renderTick(boolean start) {
ClientEventBridge bridge = instance;
if (bridge != null) {
bridge.modules.renderTick(start);
if (!start) {
GameHooks.afterFrameRender();
}
}
}
public static boolean keyboardNext() {
ClientEventBridge bridge = instance;
if (bridge != null && bridge.keyPending) {
bridge.keyPending = false;
bridge.onKeyInput();
}
boolean next = Keyboard.next();
if (bridge != null) {
bridge.keyPending = next;
}
return next;
}
public static boolean mouseNext() {
ClientEventBridge bridge = instance;
if (bridge != null && bridge.mousePending) {
bridge.mousePending = false;
MinecraftInputAdapter.mouse(bridge.modules);
}
boolean next = Mouse.next();
while (next && bridge != null && bridge.swallowMouseEvent()) {
next = Mouse.next();
}
if (bridge != null) {
bridge.mousePending = next;
}
return next;
}
private boolean swallowMouseEvent() {
Module module = this.modules.find("autoblock");
return module instanceof AutoBlockModule && ((AutoBlockModule)module).swallowMouseEvent(this.modules.context());
}
private void onKeyInput() {
MinecraftInputAdapter.keyboard(this.modules);
if (this.openGui.isPressed() && this.needsConfiguration()) {
Minecraft.getMinecraft().displayGuiScreen(new GuiModuleList(this.modules));
}
}
private boolean needsConfiguration() {
Module gui = this.modules.find("GUI");
if (gui == null) {
return true;
} else {
for (Setting<?> setting : gui.getSettings()) {
if (!setting.isConfigured()) {
return true;
}
}
return false;
}
}
public static void handleKeyboardInput(GuiScreen screen) throws IOException {
ClientEventBridge bridge = instance;
if (bridge == null || !bridge.screenKeyInput(screen)) {
screen.handleKeyboardInput();
}
}
public static void handleMouseInput(GuiScreen screen) throws IOException {
ClientEventBridge bridge = instance;
if (bridge == null || !bridge.screenMouseInput(screen)) {
screen.handleMouseInput();
}
}
private boolean screenKeyInput(GuiScreen screen) {
if (GuiMenuCommand.handle(screen, Keyboard.getEventKey(), Keyboard.getEventKeyState(), this.modules)) {
return true;
} else {
return isConfigurationScreen(screen) ? false : (MinecraftInputAdapter.keyboard(this.modules) & 0xFF) != 0;
}
}
private boolean screenMouseInput(GuiScreen screen) {
return isConfigurationScreen(screen) ? false : (MinecraftInputAdapter.mouse(this.modules) & 0xFF) != 0;
}
private static boolean isConfigurationScreen(GuiScreen screen) {
return screen instanceof ClickGuiScreen || screen instanceof GuiModuleList || screen instanceof GuiModuleSettings;
}
public static void loadWorld(Minecraft minecraft) {
ClientEventBridge bridge = instance;
if (bridge != null && minecraft.theWorld != null) {
bridge.modules.worldUnload();
}
}
public static void disconnect() {
final ClientEventBridge bridge = instance;
if (bridge != null) {
bridge.attachedChannel = null;
Minecraft.getMinecraft().addScheduledTask(new Runnable() {
@Override
public void run() {
bridge.modules.worldUnload();
}
});
}
}
private void attachPipeline() {
NetHandlerPlayClient handler = Minecraft.getMinecraft().getNetHandler();
final Channel channel = handler == null ? null : channel(handler.getNetworkManager());
if (channel != null && channel != this.attachedChannel && channel.isOpen()) {
this.attachedChannel = channel;
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
synchronized (ClientEventBridge.class) {
try {
// Keep the existing Java 8 synthetic accessor schema for live replacement.
Field active = ClientEventBridge.class.getDeclaredField("instance");
active.setAccessible(true);
if (active.get(null) == ClientEventBridge.this && channel.isOpen()
&& channel.pipeline().get("slinky_semantic_packets") == null
&& channel.pipeline().get("packet_handler") != null) {
channel.pipeline().addBefore("packet_handler", "slinky_semantic_packets", new PacketPipeline(ClientEventBridge.this.modules));
}
} catch (ReflectiveOperationException failure) {
throw new IllegalStateException("Could not check the active Slinky session", failure);
}
}
}
});
}
}
private static Channel channel(NetworkManager manager) {
if (channelField == null) {
channelField = ReflectionHelper.findField(NetworkManager.class, "channel", "field_150746_k");
}
try {
return (Channel)channelField.get(manager);
} catch (IllegalAccessException var2) {
throw new IllegalStateException(var2);
}
}
public static boolean sendChatMessage(String message) {
ClientEventBridge bridge = instance;
if (bridge != null && message != null && message.startsWith("/")) {
String[] words = message.substring(1).trim().split(" ");
String name = words[0];
if (name.equalsIgnoreCase(bridge.command.getCommandName())
|| bridge.command.getCommandAliases() != null && bridge.command.getCommandAliases().contains(name)) {
Minecraft minecraft = Minecraft.getMinecraft();
try {
bridge.command.processCommand(minecraft.thePlayer, Arrays.copyOfRange(words, 1, words.length));
} catch (CommandException var7) {
ChatComponentTranslation error = new ChatComponentTranslation(var7.getMessage(), var7.getErrorObjects());
error.getChatStyle().setColor(EnumChatFormatting.RED);
minecraft.thePlayer.addChatMessage(error);
} catch (Throwable var8) {
ChatComponentText errorx = new ChatComponentText("Slinky command failed: " + var8);
errorx.getChatStyle().setColor(EnumChatFormatting.RED);
minecraft.thePlayer.addChatMessage(errorx);
}
return true;
} else {
return false;
}
} else {
return false;
}
}
}
@@ -0,0 +1,45 @@
package dev.slinky.client.event;
import dev.slinky.module.Module;
import net.minecraft.entity.Entity;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.Packet;
public final class SemanticEvent {
public final int type;
public boolean cancelled;
public boolean active = true;
public boolean enabled;
public boolean jump;
public boolean sneaking;
public boolean overrideRotation;
public boolean remapMovement;
public boolean reducing;
public boolean queryOverride;
public float sideways;
public float forward;
public float factor;
public float scale = 1.0F;
public float yaw;
public float pitch;
public float timerRenderPartialTicks;
public double elapsedSeconds;
public double distance;
public double expand;
public Entity entity;
public Packet<?> packet;
public NetworkManager receiver;
public int dispatchMode;
public long packetAgeMillis;
public int packetSequence;
public Module module;
public double cameraX;
public double cameraY;
public double cameraZ;
public float renderWidth;
public float renderHeight;
public SemanticEvent(int type) {
this.type = type;
}
}
@@ -0,0 +1,752 @@
package dev.slinky.client.gui;
import dev.slinky.client.event.SemanticEvent;
import dev.slinky.client.gui.profile.ProfileIcons;
import dev.slinky.client.gui.profile.ProfileServices;
import dev.slinky.client.gui.settings.InlineModuleSettings;
import dev.slinky.client.input.MinecraftInputAdapter;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.client.numeric.NumericServices;
import dev.slinky.client.render.DrawPlan;
import dev.slinky.client.render.RenderAccess;
import dev.slinky.client.render.RenderConstants;
import dev.slinky.client.render.RenderEventPayload;
import dev.slinky.client.render.RenderMath;
import dev.slinky.client.render.font.CanvasFonts;
import dev.slinky.client.render.hook.RenderHooks;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import dev.slinky.module.render.GuiModule;
import dev.slinky.setting.BooleanSetting;
import dev.slinky.setting.ColorSetting;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import net.minecraft.client.gui.GuiScreen;
import org.lwjgl.input.Mouse;
public final class ClickGuiScreen extends GuiScreen implements RenderHooks.GuiFrame {
private final ModuleManager manager;
private final GuiModule module;
private final GuiMath.Runtime state;
private final ProfileServices.Host profiles;
private List<ProfileServices.Record> profileRows = Collections.emptyList();
private float profileHeight;
private final Object newProfile = new Object();
private final Object hexEdit = new Object();
private final Map<Module, Float> heights = new IdentityHashMap<>();
private final Map<Module, GuiMath.Toggle> toggles = new IdentityHashMap<>();
private final Map<Module, GuiPresentation.Expansion> expansions = new IdentityHashMap<>();
private final Map<Module, GuiPresentation.Binding> bindings = new IdentityHashMap<>();
private final Map<String, GuiPresentation.Binding> profileBindings = new HashMap<>();
private final GuiPresentation presentation = new GuiPresentation(CanvasFonts::width, true);
private final InlineModuleSettings inline;
private int lastMouseX;
private int lastMouseY;
private float lastPartialTicks;
private final GuiPresentation.Tooltip tooltip = new GuiPresentation.Tooltip();
private float drawScale;
private float drawOffsetX;
private float drawOffsetY;
private final HoldActivation heldAction = new HoldActivation();
private final Object unloadAction = new Object();
private static final float BORDER = 0.33F;
private static final float TRACK_EDGE = 0.2509804F;
private static final float TRACK_RADIUS = 5.0F;
private static final float KNOB_RADIUS = 10.0F;
private static final float EDGE_WIDTH = 2.0F;
private static final float LABEL_ROW = 19.0F;
private static final float LABEL_OFFSET = 9.0F;
public ClickGuiScreen(ModuleManager manager, GuiModule module) {
this(manager, module, manager.input().profiles());
}
public ClickGuiScreen(ModuleManager manager, GuiModule module, ProfileServices.Host profiles) {
this.manager = manager;
this.module = module;
this.state = module.runtime();
this.profiles = profiles;
this.inline = new InlineModuleSettings(manager, this.presentation, this.tooltip);
}
private static float c(int at) {
return RenderConstants.f(at);
}
private static double d(int at) {
return RenderConstants.d(at);
}
public boolean doesGuiPauseGame() {
return false;
}
public void drawScreen(int x, int y, float partial) {
this.lastMouseX = x;
this.lastMouseY = y;
this.lastPartialTicks = partial;
}
public void initGui() {
this.allowUserInput = this.module.findSetting("allow_input").isConfigured() && ((BooleanSetting)this.module.findSetting("allow_input")).isEnabled();
}
public void onGuiClosed() {
this.heldAction.reset();
this.module.screenClosed();
}
@Override
public void drawRecoveredFrame(float partial) {
this.allowUserInput = ((BooleanSetting)this.module.findSetting("allow_input")).isEnabled();
DrawPlan plan = this.framePlan(
this.mc.displayWidth,
this.mc.displayHeight,
this.module.scale(),
(float)Mouse.getX(),
(float)(this.mc.displayHeight - Mouse.getY() - 1),
RenderAccess.now()
);
RenderAccess.submit(new SemanticEvent(10), plan);
}
public DrawPlan framePlan(int displayWidth, int displayHeight, float scale, float mouseX, float mouseY, double now) {
DrawPlan var10;
try (CanvasFonts.GuiFaceScope fonts = CanvasFonts.guiFace()) {
var10 = this.buildFramePlan(displayWidth, displayHeight, scale, mouseX, mouseY, now);
}
return var10;
}
private DrawPlan buildFramePlan(int displayWidth, int displayHeight, float scale, float mouseX, float mouseY, double now) {
float elapsed = this.state.elapsed(now);
float previousFade = this.state.fade;
this.heldAction.beginFrame();
this.state.rawX = mouseX;
this.state.rawY = mouseY;
float w = (float)displayWidth / scale;
float h = (float)displayHeight / scale;
RenderMath.Rect panel = this.state.beginFrame(w, h, scale, this.module.wide(), now);
float opacity = fade(previousFade);
this.drawScale = scale;
this.drawOffsetX = 0.0F;
this.drawOffsetY = c(1313236) + opacity * c(1313240) - this.state.scroll;
DrawPlan plan = new DrawPlan()
.canvas()
.push()
.scale(scale, scale, 1.0F)
.opacity(opacity)
.color(c(1313228), c(1313228), c(1313228), c(1313232))
.rect(0.0F, 0.0F, w, h, 0.0F)
.translate(0.0F, this.drawOffsetY, 0.0F);
GuiMath.Layout layout = this.state.layouts.peek();
RenderMath.Rect tabs = layout.next(c(1312896));
plan.color(c(1312900), c(1312900), c(1312900), 1.0F).rect(tabs.x, tabs.y, tabs.width, tabs.height, c(1312904));
float tabWidth = tabs.width / (float)GuiAppearance.TABS.length;
this.state.selectedAnimation = GuiMath.limited(
this.state.selectedAnimation, (float)GuiAppearance.tab(this.state.selected), (float)((double)elapsed / d(1312912))
);
RenderMath.Rect highlight = new RenderMath.Rect(tabs.x + this.state.selectedAnimation * tabWidth, tabs.y, tabWidth, tabs.height).inset(c(1312920));
plan.color(c(1312924), c(1312924), c(1312924), 1.0F).rect(highlight.x, highlight.y, highlight.width, highlight.height, c(1312928));
String[] labels = GuiAppearance.TABS;
for (int i = 0; i < labels.length; i++) {
RenderMath.Rect r = new RenderMath.Rect(tabs.x + (float)i * tabWidth, tabs.y, tabWidth, tabs.height);
if (i == labels.length - 1) {
if (this.heldAction.update(this.unloadAction, this.state.hover(r), this.state.down, this.state.modal == 0, !this.state.consumed, now)) {
this.state.closeRequested = true;
this.manager.shutdown();
}
float held = this.heldAction.progress(this.unloadAction);
if (held > 0.0F) {
plan.color(0.8F, 0.25F, 0.27F, 0.25F).rect(r.x + 6.0F, r.y + 6.0F, (r.width - 12.0F) * held, r.height - 12.0F, 14.0F);
}
} else if (this.state.click(r)) {
this.state.selected = GuiAppearance.category(i);
this.state.mode = 0;
}
RenderMath.Rect inner = r.inset(c(1312920));
boolean hover = this.state.hover(inner);
int color = i == labels.length - 1
? rgb(c(hover ? 1312948 : 1312936), c(hover ? 1312952 : 1312944), c(hover ? 1312952 : 1312944))
: this.gray(hover ? 1312940 : 1312936);
this.presentation.text(plan, labels[i], inner, 1, 0, c(1312932), color);
}
float difference = (float)GuiAppearance.tab(this.state.selected) - this.state.selectedAnimation;
plan.push();
if (difference != 0.0F) {
float magnitude = Float.intBitsToFloat(Float.floatToRawIntBits(difference) & RenderConstants.i(1313024));
float alpha = c(1312976) - (magnitude + magnitude);
if (magnitude > c(1312972)) {
alpha = 0.0F;
}
this.drawOffsetX = (float)((double)difference * ((double)w * d(1313040)));
plan.opacity(alpha).translate(this.drawOffsetX, 0.0F, 0.0F);
}
if (this.state.selected < 5) {
this.renderCategory(plan, layout, now, elapsed, w, h);
} else {
this.renderProfiles(plan, layout, now, w, h);
}
plan.unclip().pop();
this.state.pop();
int categoryBottom = this.state.contentBottom;
this.drawOffsetX = 0.0F;
this.presentation.tooltip(plan, this.tooltip, elapsed);
plan.unclip().pop();
if (this.state.modal != 0) {
float modalOpacity = fade(this.state.modalProgress);
plan.push().scale(scale, scale, 1.0F).opacity(modalOpacity).color(c(1313228), c(1313228), c(1313228), c(1313232)).rect(0.0F, 0.0F, w, h, 0.0F);
this.drawOffsetY = c(1313236) + modalOpacity * c(1313240);
plan.translate(0.0F, this.drawOffsetY, 0.0F);
this.state.mouseY = this.state.mouseY - this.state.scroll;
this.state.resetLayout();
RenderMath.Rect modalPanel = panel;
if (panel.width > c(1313172)) {
modalPanel = new RenderMath.Rect((float)((double)panel.x + (double)(panel.width + c(1313176)) * d(1313080)), panel.y, c(1313172), panel.height);
}
GuiMath.Layout modalLayout = this.state.push(modalPanel, c(1313016), c(1313020), false);
this.renderModal(plan, modalLayout, now);
this.state.pop();
plan.unclip().pop();
}
this.state.contentBottom = categoryBottom;
this.state.endFrame(h, now);
this.heldAction.endFrame();
return plan;
}
public void expanded(Module entry, boolean open) {
GuiPresentation.Expansion expansion = new GuiPresentation.Expansion();
expansion.open = open;
this.expansions.put(entry, expansion);
}
private static float fade(float progress) {
return c(1313220) - NumericServices.powf(c(1313220) - progress, c(1313224));
}
private static int rgb(float r, float g, float b) {
return 0xFF000000 | ((int)(r * 255.0F) & 0xFF) << 16 | ((int)(g * 255.0F) & 0xFF) << 8 | (int)(b * 255.0F) & 0xFF;
}
private void clip(DrawPlan plan, GuiMath.Layout layout) {
RenderMath.Rect r = layout.clip;
plan.clip((r.x + this.drawOffsetX) * this.drawScale, (r.y + this.drawOffsetY) * this.drawScale, r.width * this.drawScale, r.height * this.drawScale);
}
private void renderCategory(DrawPlan plan, GuiMath.Layout layout, double now, float elapsed, float width, float height) {
ColorSetting.Value accent = this.module.accent();
layout.autoColumns(c(1313248));
for (Module entry : GuiAppearance.entries(this.manager, this.state.selected)) {
float old = this.heights.containsKey(entry) ? this.heights.get(entry) : 0.0F;
RenderMath.Rect outer = layout.next(old);
if (GuiMath.intersects(new RenderMath.Rect(0.0F, this.state.scroll, width, height), outer)) {
plan.color(c(1313264), c(1313264), c(1313264), 1.0F).rect(outer.x, outer.y, outer.width, outer.height, c(1313268));
GuiMath.Layout inside = this.state.push(outer, c(1313272), 12.0F, true);
this.clip(plan, inside);
inside.columns(1);
RenderMath.Rect title = inside.next(c(1313268));
GuiPresentation.Expansion expansion = this.expansions.get(entry);
if (expansion == null) {
expansion = new GuiPresentation.Expansion();
this.expansions.put(entry, expansion);
}
float advance;
if (this.module.compact()) {
advance = this.presentation
.moduleText(
this.state, plan, expansion, !entry.getSettings().isEmpty(), entry.getName(), title, 16.0F, this.gray(1313280), this.gray(1313284)
);
} else {
advance = this.presentation.text(plan, entry.getName(), title, 0, 0, c(1313276), this.gray(1313280));
}
int flags = RenderModuleAccess.observedFlags(entry);
if ((flags & 1) == 0) {
GuiPresentation.Binding record = this.bindings.get(entry);
if (record == null) {
record = new GuiPresentation.Binding(entry.getBinding(), entry.isHoldBinding(), entry, key -> {
this.manager.setBinding(entry, key);
this.manager.persist();
});
this.bindings.put(entry, record);
}
if (this.state.mode != 1 || this.state.identity != entry) {
record.key = entry.getBinding();
record.hold = entry.isHoldBinding();
}
this.presentation
.binding(
this.state,
plan,
record,
new RenderMath.Rect(advance + c(1313288), title.y, title.width - advance + title.x, title.height),
new float[]{accent.red, accent.green, accent.blue, 1.0F},
(flags & 8) == 0
);
if (entry.isHoldBinding() != record.hold) {
entry.setHoldBinding(record.hold);
this.manager.persist();
}
}
if ((flags & 2) == 0) {
RenderMath.Rect toggle = new RenderMath.Rect(title.x + title.width + c(1313292), title.y, c(1313296), title.height);
GuiMath.Toggle animation = this.toggles.get(entry);
if (animation == null) {
animation = new GuiMath.Toggle();
this.toggles.put(entry, animation);
}
boolean enabled = GuiWidgets.toggle(
this.state, plan, animation, toggle, entry.isEnabled(), new float[]{accent.red, accent.green, accent.blue, 1.0F}, now
);
if (enabled != entry.isEnabled()) {
try {
this.manager.toggle(entry);
} catch (IllegalArgumentException var22) {
expansion.open = true;
this.state.beginPrompt(var22.getMessage(), () -> {
}, now);
}
}
}
if (!this.module.compact()) {
RenderMath.Rect description = inside.next(c(1313268));
this.presentation
.moduleText(
this.state,
plan,
expansion,
!entry.getSettings().isEmpty(),
GuiAppearance.description(entry),
description,
14.0F,
this.gray(1313300),
this.gray(1313304)
);
}
if (expansion.open) {
this.inline.render(entry, this.state, plan, inside, new float[]{accent.red, accent.green, accent.blue, 1.0F}, now);
}
float target = GuiPresentation.categoryHeight(this.state.pop());
this.heights.put(entry, old == 0.0F ? target : GuiMath.limited(old, target, (float)((double)elapsed / d(1313256))));
plan.unclip();
}
}
}
private void center(DrawPlan p, String text, RenderMath.Rect r, float font, int color) {
float measured = CanvasFonts.width(text, 0, font);
float x = (float)((double)r.width * d(1312824) + (double)r.x) - measured * 0.5F;
float y = (float)((double)r.height * d(1312824) + (double)(r.y + c(1312832)));
p.canvasText(text, x, y, color, font, 17);
}
public static float[] colorBlend(float[] a, float[] b, float fraction) {
return GuiWidgets.blend(a, b, fraction);
}
private int gray(int at) {
int g = (int)(c(at) * 255.0F);
return 0xFF000000 | g << 16 | g << 8 | g;
}
private void actionEvent(int action, String name) {
SemanticEvent event = new SemanticEvent(11);
RenderEventPayload payload = RenderEventPayload.attach(event);
payload.action = action;
payload.profileName = name;
try {
this.manager.dispatch(event);
} finally {
RenderEventPayload.detach(event);
}
this.state.profileStamp = -1.0;
}
private void error(IOException error) {
SemanticEvent event = new SemanticEvent(12);
RenderEventPayload payload = RenderEventPayload.attach(event);
payload.errorCode = error instanceof ProfileServices.Failure ? ((ProfileServices.Failure)error).code : 1;
payload.message = error.getMessage();
try {
this.manager.dispatch(event);
} finally {
RenderEventPayload.detach(event);
}
}
private void perform(int action, String name, boolean overwrite) {
try {
if (action == 0) {
this.profiles.load(name);
} else if (action == 1) {
this.profiles.save(name, overwrite);
} else if (action == 2) {
this.profiles.explore();
} else if (action == 3) {
this.profiles.delete(name);
}
this.actionEvent(action, name);
} catch (IOException var5) {
this.error(var5);
}
}
private void profileAction(int action, String name, double now) {
if (action == 1 && name == null) {
this.error(new ProfileServices.Failure(6003, RenderConstants.text(1313671)));
} else if (action == 3 && name == null) {
this.error(new ProfileServices.Failure(6004, RenderConstants.text(1313637)));
} else if (this.state.profileActionsConfirmed) {
this.perform(action, name, true);
} else {
int at = 1313840 + RenderConstants.i(1313840 + action * 4);
this.state.beginPrompt(RenderConstants.text(at).replace("%s", name == null ? RenderConstants.text(1313482) : name), () -> {
this.state.profileActionsConfirmed = true;
this.perform(action, name, true);
}, now);
}
}
private void renderProfiles(DrawPlan plan, GuiMath.Layout layout, double now, float width, float height) {
if (this.state.mode == 0 && now > this.state.profileStamp + d(1313048)) {
try {
this.profileRows = this.profiles.records();
} catch (IOException var21) {
this.profileRows = this.profiles.snapshot();
this.error(var21);
}
this.state.profileStamp = now;
}
layout.autoColumns(c(1313056));
float radius = c(1313064);
float font = c(1313068);
for (int row = 0; row <= this.profileRows.size(); row++) {
ProfileServices.Record record = row == 0 ? null : this.profileRows.get(row - 1);
RenderMath.Rect outer = layout.next(this.profileHeight);
if (GuiMath.intersects(new RenderMath.Rect(0.0F, this.state.scroll, width, height), outer)) {
plan.color(c(1313060), c(1313060), c(1313060), 1.0F).rect(outer.x, outer.y, outer.width, outer.height, radius);
GuiMath.Layout inside = this.state.push(outer, c(1313016), 12.0F, true);
this.clip(plan, inside);
inside.columns(1);
RenderMath.Rect head = inside.next(radius);
float advance = this.presentation.text(plan, record == null ? RenderConstants.text(1313482) : record.name, head, 0, 0, font, this.gray(1313072));
if (record != null) {
GuiPresentation.Binding binding = this.profileBindings.get(record.name);
if (binding == null) {
binding = new GuiPresentation.Binding(record.binding, false, record.name, key -> {
record.binding = key;
this.profiles.bind(record.name, key);
});
this.profileBindings.put(record.name, binding);
}
if (this.state.mode != 1 || this.state.identity != binding.identity) {
binding.key = record.binding;
}
ColorSetting.Value accent = this.module.accent();
this.presentation
.binding(
this.state,
plan,
binding,
new RenderMath.Rect(advance + c(1313076), head.y, head.width - advance + head.x, head.height),
new float[]{accent.red, accent.green, accent.blue, 1.0F},
false
);
}
for (int slot = 0; slot < 4; slot++) {
RenderMath.Rect button = new RenderMath.Rect(head.x + head.width - (float)(80 - 20 * slot) - (float)(36 - 12 * slot), head.y, radius, 20.0F);
boolean enabled = (slot & (record == null ? 1 : 0)) == 0;
int at = 1313492 + RenderConstants.i(1313492 + slot * 4);
String label = RenderConstants.text(at);
ProfileIcons.draw(plan, button, slot, enabled, enabled && this.state.hover(button));
if (enabled && this.state.hover(button)) {
this.tooltip
.request(label, NumericMath.truncateInt((double)button.x + (double)button.width * d(1313080)), NumericMath.truncateInt(button.y));
}
if (enabled && this.state.click(button)) {
this.profileAction(slot, record == null ? null : record.name, now);
}
}
if (!this.module.compact()) {
RenderMath.Rect date = inside.next(radius);
this.presentation.text(plan, record == null ? RenderConstants.text(1313536) : record.date, date, 0, 0, c(1313088), this.gray(1313092));
}
this.profileHeight = this.state.pop();
plan.unclip();
}
}
RenderMath.Rect create = layout.next(this.profileHeight);
plan.color(c(1313060), c(1313060), c(1313060), c(1312972)).rect(create.x, create.y, create.width, create.height, radius);
if (this.state.click(create)) {
this.state.activateText(this.newProfile, GuiMath::profileNameFilter);
}
if (this.state.textActive(this.newProfile)) {
RenderMath.Rect input = new RenderMath.Rect(
create.x, (float)((double)create.y + (double)create.height * d(1313080) + d(1313104)), create.width, c(1313124)
);
this.presentation.textEntry(this.state, plan, input, 1, font, now);
String name = this.state.takeText();
if (name != null) {
this.perform(1, name, false);
}
} else {
this.center(plan, RenderConstants.text(1313821), create, c(1313096), this.gray(this.state.hover(create) ? 1312976 : 1313072));
}
}
private void renderModal(DrawPlan plan, GuiMath.Layout parent, double now) {
this.state.modalDrawing = true;
this.state.modalProgress = RenderMath.min(c(1314448), (float)((double)this.state.modalProgress + (now - this.state.modalStamp) / d(1314440)));
parent.columns(1);
RenderMath.Rect outer = parent.next(this.state.modalHeight);
plan.color(c(1314452), c(1314452), c(1314452), 1.0F).rect(outer.x, outer.y, outer.width, outer.height, c(1314456));
GuiMath.Layout inner = this.state.push(outer, c(1314460), 12.0F, true);
this.clip(plan, inner);
inner.columns(1);
RenderMath.Rect heading = inner.next(c(1314456));
this.presentation.text(plan, this.state.title, heading, 0, 0, c(1314464), this.gray(1314468));
RenderMath.Rect close = new RenderMath.Rect(heading.width - heading.height + heading.x, heading.y, heading.height, heading.height);
float inset = (float)((double)close.width * d(1314488));
boolean hover = this.state.hover(close);
plan.color(c(hover ? 1314476 : 1314472), c(hover ? 1314480 : 1314472), c(hover ? 1314480 : 1314472), 1.0F)
.lineWidth(c(1314484))
.line(close.x + inset, close.y + inset, close.x + close.width - inset, close.y + close.height - inset)
.line(close.x + close.width - inset, close.y + inset, close.x + inset, close.y + close.height - inset);
boolean closePressed = this.state.click(close);
int mode = this.state.modal;
if (mode == 1 || mode == 4) {
RenderMath.Rect input = inner.next(c(1314496));
plan.color(c(1314504), c(1314504), c(1314504), 1.0F).lineWidth(c(1314484)).roundedStroke(input.x, input.y, input.width, input.height, c(1314460));
if (!this.state.textActive(this.state.identity)) {
this.state.finishModal(true);
} else {
this.presentation.textEntry(this.state, plan, input.inset(c(1314508)), 1, c(1314500), now);
if (mode == 1) {
this.state.submitModalText();
} else {
this.state.filterOptions(now);
RenderMath.Rect pair = null;
Integer chosen = null;
for (int i = 0; i < this.state.matches.size(); i++) {
if ((i & 1) == 0) {
pair = inner.next(c(1314520));
}
RenderMath.Rect button = new RenderMath.Rect(pair.x + (float)(i & 1) * pair.width * c(1314524), pair.y, pair.width * c(1314524), pair.height);
int index = this.state.matches.get(i);
this.presentation
.text(plan, this.state.options.get(index), button, 0, 0, c(1314464), this.gray(this.state.hover(button) ? 1314448 : 1314468));
if (this.state.click(button)) {
chosen = index;
}
}
if (this.state.overflow != 0) {
RenderMath.Rect note = inner.next(c(1314520));
this.presentation
.text(
plan,
RenderConstants.text(1314705).replace("%u", Integer.toString(Math.min(this.state.overflow, 9999))),
note,
0,
0,
c(1314500),
this.gray(1314472)
);
}
if (chosen != null) {
this.state.choose(chosen);
}
}
}
} else if (mode == 3) {
RenderMath.Rect prompt = inner.next(c(1314496));
this.presentation.text(plan, this.state.prompt, prompt, 0, 0, c(1314500), this.gray(1314468));
RenderMath.Rect button = inner.next(c(1314496));
boolean over = this.state.hover(button);
plan.color(c(over ? 1314476 : 1314468), c(over ? 1314480 : 1314528), c(over ? 1314480 : 1314528), 1.0F)
.rect(button.x, button.y, button.width, button.height, c(1314532));
this.center(plan, RenderConstants.text(1314696), button, c(1314500), this.gray(1314448));
if (this.state.click(button)) {
this.state.confirmPrompt();
}
} else if (mode == 2) {
this.renderColor(plan, inner);
}
this.state.modalHeight = this.state.pop();
plan.unclip();
this.state.modalDrawing = false;
this.state.modalStamp = now;
if (closePressed && this.state.modal != 0) {
this.state.finishModal(true);
}
}
private static float[] sample(float[][] stops, float t) {
if (stops.length == 1) {
return stops[0];
} else {
float position = RenderMath.clamp(0.0F, 1.0F, t) * (float)(stops.length - 1);
int index = Math.min(stops.length - 2, (int)position);
float f = position - (float)index;
float[] color = new float[4];
for (int c = 0; c < 4; c++) {
color[c] = stops[index][c] + (stops[index + 1][c] - stops[index][c]) * f;
}
return color;
}
}
private void renderColor(DrawPlan plan, GuiMath.Layout layout) {
RenderMath.Rect row = layout.next(c(1314536));
plan.color(this.state.rgb[0], this.state.rgb[1], this.state.rgb[2], 1.0F).rect(row.x, row.y, row.height, row.height, c(1314532));
plan.color(0.33F, 0.33F, 0.33F, 1.0F).lineWidth(1.0F).roundedStroke(row.x, row.y, row.height, row.height, c(1314532));
String hex = String.format(
Locale.ROOT,
"%02x%02x%02x",
(int)(NumericMath.truncateLong(this.state.rgb[0] * c(1314540)) & 255L),
(int)(NumericMath.truncateLong(this.state.rgb[1] * c(1314540)) & 255L),
(int)(NumericMath.truncateLong(this.state.rgb[2] * c(1314540)) & 255L)
);
plan.canvasText(
RenderConstants.text(1314681) + (this.state.textActive(this.hexEdit) ? this.state.text.toString() : hex),
row.x + row.height + c(1314460),
row.y,
this.gray(1314472),
c(1314456),
9
);
if (this.state.click(row)) {
this.state.activateText(this.hexEdit, GuiMath::hexFilter);
}
if (this.state.textActive(this.hexEdit)) {
String value = this.state.takeText();
if (value != null) {
this.state.hex(value);
}
}
for (int component = 0; component < 3; component++) {
RenderMath.Rect label = layout.next(19.0F);
plan.canvasText(
RenderConstants.text(component == 0 ? 1314642 : (component == 1 ? 1314646 : 1314657)), label.x, label.y + 9.0F, this.gray(1314468), c(1314500), 9
);
RenderMath.Rect slider = layout.next(c(1314500));
float edge = 1.0F;
RenderMath.Rect track = new RenderMath.Rect(slider.x + edge, slider.y, slider.width - 2.0F, slider.height);
float[][] stops;
if (component == 0) {
int steps = (int)c(1314560);
stops = new float[steps + 1][];
for (int i = 0; i <= steps; i++) {
stops[i] = GuiMath.hsl((float)i / (float)steps, this.state.hsl[1], this.state.hsl[2], 255);
}
} else {
stops = new float[][]{
GuiMath.hsl(this.state.hsl[0], component == 1 ? 0.0F : this.state.hsl[1], component == 2 ? 0.0F : this.state.hsl[2], 255),
GuiMath.hsl(this.state.hsl[0], component == 1 ? c(1314448) : this.state.hsl[1], component == 2 ? c(1314448) : this.state.hsl[2], 255)
};
}
plan.pillGradient(track, 5.0F, stops);
plan.color(0.2509804F, 0.2509804F, 0.2509804F, 1.0F).lineWidth(2.0F).roundedStroke(track.x, track.y, track.width, track.height, 5.0F);
float outer = 10.0F + edge;
float px = (slider.width - 2.0F * outer) * this.state.hsl[component] + slider.x + outer;
float py = (float)((double)slider.height * d(1314616) + (double)slider.y);
float[] under = sample(stops, this.state.hsl[component]);
plan.color(under[0], under[1], under[2], 1.0F).op("circle", px, py, 10.0F);
plan.color(0.2509804F, 0.2509804F, 0.2509804F, 1.0F).lineWidth(2.0F).circleStroke(px, py, 10.0F);
this.state.colorSlider(slider, component, outer);
}
RenderMath.Rect label = layout.next(19.0F);
plan.canvasText(RenderConstants.text(1314667), label.x, label.y + 9.0F, this.gray(1314468), c(1314500), 9);
row = layout.next(c(1314520));
float x = row.x;
float width = (row.width + c(1314564)) * c(1314568);
float spacing = c(1314460) + width;
for (int back = 8; back > 0; back--) {
int index = this.state.historyIndex >= back ? this.state.historyIndex - back : this.state.historyIndex + 8 - back;
float[] color = this.state.history[index];
RenderMath.Rect button = new RenderMath.Rect(x, row.y, width, row.height);
plan.color(color[0], color[1], color[2], 1.0F).rect(button.x, button.y, button.width, button.height, 5.0F);
boolean over = this.state.hover(button);
float edge = over ? 0.55F : 0.33F;
plan.color(edge, edge, edge, 1.0F).lineWidth(1.0F).roundedStroke(button.x, button.y, button.width, button.height, 5.0F);
if (this.state.click(button)) {
this.state.selectHistory(index);
}
x += spacing;
}
}
public void handleMouseInput() throws IOException {
MinecraftInputAdapter.mouse(this.manager);
super.handleMouseInput();
}
public void handleKeyboardInput() throws IOException {
if ((MinecraftInputAdapter.keyboard(this.manager) & 0xFF) == 0) {
super.handleKeyboardInput();
} else {
this.mc.dispatchKeypresses();
}
}
protected void keyTyped(char character, int key) throws IOException {
if (key == 1) {
this.manager.requestEnabled(this.module, false);
} else {
super.keyTyped(character, key);
}
}
}
@@ -0,0 +1,83 @@
package dev.slinky.client.gui;
import com.google.gson.JsonArray;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import dev.slinky.module.catalog.ModuleCatalog;
import dev.slinky.setting.Setting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public final class GuiAppearance {
public static final String[] TABS = new String[]{"Combat", "Move", "Visual", "Block", "Utility", "Profiles", "Unload"};
private static final int[] CATEGORIES = new int[]{0, 1, 2, 3, 4, 5};
public static int category(int tab) {
return CATEGORIES[tab];
}
public static int tab(int category) {
for (int i = 0; i < CATEGORIES.length; i++) {
if (CATEGORIES[i] == category) {
return i;
}
}
return tab(4);
}
public static boolean includes(int page, int category) {
return page == category;
}
public static List<Module> entries(ModuleManager manager, int page) {
List<Module> entries = new ArrayList<>();
for (Module module : manager.all()) {
if (includes(page, module.metadataCategory())) {
entries.add(module);
}
}
Collections.sort(entries, Comparator.comparing(Module::getName, String.CASE_INSENSITIVE_ORDER));
return entries;
}
public static String description(Module module) {
JsonArray observations = ModuleCatalog.module(module.getName()).getAsJsonArray("descriptor_observations");
return observations.size() == 0 ? module.getDescription() : observations.get(0).getAsJsonObject().get("description").getAsString();
}
public static void configureMissing(Module gui) {
if (gui != null) {
boolean fresh = true;
for (Setting<?> setting : gui.getSettings()) {
fresh &= !setting.isConfigured();
}
if (fresh && gui.getBinding() == 0) {
gui.setBinding(99);
}
configure(gui, "scale", "Medium (100%)");
configure(gui, "accent_color", "1.0,0.35,0.0");
configure(gui, "allow_input", "false");
configure(gui, "compact", "false");
configure(gui, "wide", "false");
}
}
private static void configure(Module gui, String key, String value) {
Setting<?> setting = gui.findSetting(key);
if (!setting.isConfigured()) {
setting.parseAndSet(value);
setting.saveCurrent();
}
}
private GuiAppearance() {
}
}
@@ -0,0 +1,909 @@
package dev.slinky.client.gui;
import dev.slinky.client.numeric.FloatNumeric;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.client.render.RenderConstants;
import dev.slinky.client.render.RenderMath;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
public final strictfp class GuiMath {
private static strictfp float c(int at) {
return RenderConstants.f(at);
}
private static strictfp double d(int at) {
return RenderConstants.d(at);
}
public static strictfp RenderMath.Rect rounded(RenderMath.Rect r) {
return new RenderMath.Rect(FloatNumeric.round(r.x), FloatNumeric.round(r.y), FloatNumeric.round(r.width), FloatNumeric.round(r.height));
}
public static strictfp boolean intersects(RenderMath.Rect a, RenderMath.Rect b) {
return !(a.x > b.x + b.width) && !(b.x > a.x + a.width) && !(a.y > b.y + b.height) && !(b.y > a.y + a.height);
}
public static strictfp float[] range(
GuiMath.Runtime runtime,
Object lowId,
Object highId,
GuiMath.Slider low,
GuiMath.Slider high,
RenderMath.Rect r,
float lower,
float upper,
float min,
float max,
float step,
float displayScale,
double now
) {
float difference = max - min;
float ratio = (float)((double)((float)(now - low.stamp)) / d(1314080));
low.animation = limited(low.animation, RenderMath.clamp(0.0F, c(1314076), (lower - min) / difference), ratio);
high.animation = limited(high.animation, RenderMath.clamp(0.0F, c(1314076), (upper - min) / difference), ratio);
low.stamp = high.stamp = now;
float lowX = RenderMath.lerp(r.x, r.x + r.width, low.animation);
float highX = RenderMath.lerp(r.x, r.x + r.width, high.animation);
RenderMath.Rect[] hits = GuiWidgets.rangeHits(r, r, lowX, highX);
float a = low.action(runtime, lowId, r, hits[0], lower, min, max, step, displayScale);
float b = upper;
if (low.active) {
if (a > upper) {
b = a;
}
} else {
b = high.action(runtime, highId, r, hits[1], upper, min, max, step, displayScale);
if (a > b) {
a = b;
}
}
return new float[]{a, b};
}
public static strictfp float limited(float a, float b, float t) {
return RenderMath.lerp(a, b, RenderMath.min(c(1304784), t));
}
public static strictfp float parseNumber(String text) {
byte[] input = bytes(text);
int i = 0;
int sign = 1;
int integer = 0;
if (input.length > 0 && input[0] == 45) {
i = 1;
sign = -1;
}
while (i < input.length && (input[i] & 255) - 48 < 10 && (input[i] & 255) >= 48) {
integer = integer * 10 + (input[i] & 255) - 48;
i++;
}
if (i < input.length && (input[i] & 255 | 2) == 46) {
float fraction = 0.0F;
float multiplier = c(1314008);
i++;
while (i < input.length && (input[i] & 255) - 48 < 10 && (input[i] & 255) >= 48) {
fraction += (float)((input[i] & 255) - 48) * multiplier;
multiplier = (float)((double)multiplier * d(1314016));
i++;
}
return ((float)integer + fraction) * (float)sign;
} else {
return (float)(integer * sign);
}
}
public static strictfp void numericFilter(StringBuilder text, int character) {
int b = character & 0xFF;
if (text.length() != 100) {
if (b >= 48 && b <= 57 || b == 45 || b == 46 || b == 44) {
text.append((char)b);
}
}
}
public static strictfp void catalogFilter(StringBuilder text, int character) {
if (text.length() <= 99 && RenderMath.catalogCharacter(character)) {
text.append((char)(character & 0xFF));
}
}
public static strictfp void profileNameFilter(StringBuilder text, int character) {
int b = character & 0xFF;
if (text.length() < 15 && (b - 32 & 0xFF) < 95 && b != 47 && b != 92) {
text.append((char)b);
}
}
public static strictfp void hexFilter(StringBuilder text, int character) {
if (text.length() <= 5) {
int b = character & 0xFF;
if ((b - 65 & 0xFF) <= 5) {
b |= 32;
} else if ((b - 97 & 0xFF) >= 6 && (b - 48 & 0xFF) >= 10) {
return;
}
text.append((char)b);
}
}
private static strictfp byte[] bytes(String s) {
return s.getBytes(StandardCharsets.UTF_8);
}
private static strictfp String clip(String s, int n) {
byte[] a = bytes(s);
return new String(a, 0, Math.min(n, a.length), StandardCharsets.UTF_8);
}
public static strictfp boolean substring(byte[] candidate, byte[] search) {
if (search.length == 0) {
return true;
} else {
for (int start = 0; start <= candidate.length - search.length; start++) {
int matched = 0;
while (lower(candidate[start + matched] & 255) == lower(search[matched] & 255)) {
if (++matched == search.length) {
return true;
}
}
}
return false;
}
}
private static strictfp int lower(int b) {
return (b - 65 & 0xFF) < 26 ? b | 32 : b;
}
public static strictfp float[] rgbToHsl(float[] rgb, boolean history, boolean modal) {
float r = rgb[0];
float g = rgb[1];
float b = rgb[2];
float max = b;
float min = b;
if (!Float.isNaN(g)) {
max = RenderMath.max(b, g);
min = RenderMath.min(b, g);
}
if (!Float.isNaN(r)) {
max = RenderMath.max(max, r);
min = RenderMath.min(min, r);
}
float half = c(modal ? 1314524 : 1314400);
float one = c(modal ? 1314484 : 1314404);
float six = c(modal ? 1314560 : 1314412);
float sum = max + min;
float light = half * sum;
float hue = 0.0F;
float saturation = 0.0F;
if (max != min) {
float diff = max - min;
float den = half < light ? one - max - min : sum;
if (max == r) {
hue = (g - b) / diff + (g < b ? six : 0.0F);
} else if (max == g) {
hue = (b - r) / diff + c(modal ? 1314484 : 1314404);
} else if (max == b) {
hue = (r - g) / diff + c(modal ? 1314508 : 1314408);
} else {
hue = light;
}
saturation = diff / den;
hue /= six;
}
return new float[]{hue, saturation, light};
}
private static strictfp float clamp(float v) {
return v < 0.0F ? 0.0F : RenderMath.min(c(2838936), v);
}
private static strictfp float wrap(float v) {
float one = c(2838936);
if (v < 0.0F) {
v += one;
}
if (one < v) {
v += c(2838948);
}
return v;
}
private static strictfp float channel(float h, float high, float low) {
if (c(2838952) > h) {
return (high - low) * h * c(2838960) + low;
} else if (c(2838940) > h) {
return high;
} else {
return c(2838956) > h ? (c(2838956) - h) * (high - low) * c(2838960) + low : low;
}
}
public static strictfp float[] hsl(float hue, float saturation, float light, int alpha) {
hue = FloatNumeric.remainder(hue, c(2838936));
if (hue < 0.0F) {
hue += c(2838936);
}
saturation = clamp(saturation);
light = clamp(light);
float high = c(2838940) >= light ? (saturation + c(2838936)) * light : saturation + light - light * saturation;
float low = light + light - high;
return new float[]{
clamp(channel(wrap(c(2838944) + hue), high, low)),
clamp(channel(wrap(hue), high, low)),
clamp(channel(wrap(hue + c(2838964)), high, low)),
(float)(alpha & 0xFF) / c(2838968)
};
}
public static strictfp float floor(float value) {
int bits = Float.floatToRawIntBits(value);
int exponent = bits >>> 23 & 0xFF;
if (exponent >= 150) {
return value;
} else if (exponent >= 127) {
int keep = -1 << (22 - exponent & 31);
int discard = ~keep;
return (bits & discard) == 0 ? value : Float.intBitsToFloat(bits + (-(bits >>> 31) & discard) & keep);
} else if (bits >= 0) {
return 0.0F;
} else {
return value == 0.0F ? value : c(2868916);
}
}
public interface Filter {
void append(StringBuilder var1, int var2);
}
public static final strictfp class Layout {
public RenderMath.Rect rect;
public RenderMath.Rect clip;
public float spacing;
public float inset;
public float y;
public float x;
public int columns = 1;
public int count;
public boolean scissor;
final RenderMath.Rect[] cache = new RenderMath.Rect[4];
Layout(RenderMath.Rect r, float spacing, float inset) {
this.rect = r;
this.spacing = spacing;
this.inset = inset;
this.x = r.x;
this.y = r.y;
}
private strictfp void flush(float initial) {
if (this.count != 0) {
int last = this.count - 1;
RenderMath.Rect p = this.cache[last & 3];
float bottom = p.y + p.height;
float result = initial;
for (int i = last - last % this.columns; i <= last; i++) {
result = RenderMath.max(bottom, result);
}
if (result != initial) {
this.y = result + this.spacing;
}
}
}
public strictfp void columns(int requested) {
this.flush(GuiMath.c(1312804));
this.columns = requested == 0 ? 1 : Math.min(4, requested);
this.count = 0;
}
public strictfp void autoColumns(float width) {
this.flush(GuiMath.c(1312808));
this.columns = NumericMath.truncateInt((this.rect.width + this.spacing) / (width + this.spacing));
this.columns = this.columns == 0 ? 1 : Math.min(4, this.columns);
this.count = 0;
}
public strictfp RenderMath.Rect next(float height) {
int current = this.count++;
RenderMath.Rect previous = current >= this.columns ? this.cache[current - this.columns & 3] : null;
float nx;
float ny;
float w;
if (current % this.columns != 0) {
RenderMath.Rect left = this.cache[current - 1 & 3];
nx = left.x + left.width + this.spacing;
w = left.width;
ny = current >= this.columns ? this.spacing + (previous.y + previous.height) : this.y;
} else {
nx = this.x;
if (current < this.columns) {
ny = this.y;
w = (this.rect.width - (float)(this.columns - 1) * this.spacing) / (float)this.columns;
} else {
ny = previous.y + previous.height + this.spacing;
w = previous.width;
}
}
RenderMath.Rect raw = new RenderMath.Rect(nx, ny, w, height);
this.cache[current & 3] = raw;
return GuiMath.rounded(raw);
}
public strictfp float end() {
this.flush(GuiMath.c(1312800));
return this.spacing + this.spacing + (this.y - this.rect.y);
}
}
public static final strictfp class Runtime {
public int mode;
public int modal;
public int selected;
public boolean down;
public boolean consumed;
public boolean modifier;
public boolean closeRequested;
public boolean modalDrawing;
public boolean submitted;
public float mouseX;
public float mouseY;
public float rawX;
public float rawY;
public float scroll;
public float scrollMaximum;
public float fade;
public float selectedAnimation;
public float modalProgress;
public float modalHeight;
public float tooltipProgress;
public double stamp;
public double modalStamp;
public double profileStamp;
public Object identity;
public GuiMath.Filter filter;
public IntConsumer binding;
public Consumer<String> textCompletion;
public Runnable completion;
public final StringBuilder text = new StringBuilder();
public String title;
public String prompt;
public float[] rgb;
public float[] hsl;
public final float[][] history = new float[8][3];
public int historyIndex;
public List<String> options = Collections.emptyList();
public final List<Integer> matches = new ArrayList<>();
public int overflow;
public double searchStamp;
public final Deque<GuiMath.Layout> layouts = new ArrayDeque<>();
public int contentBottom;
public boolean[] sliderActive = new boolean[3];
public boolean profileActionsConfirmed;
private Consumer<Integer> selection;
public strictfp void start(double now) {
this.fade = 0.0F;
this.stamp = now;
}
public strictfp GuiMath.Layout push(RenderMath.Rect input, float spacing, float inset, boolean scissor) {
RenderMath.Rect r = input;
if (!this.layouts.isEmpty()) {
RenderMath.Rect parent = this.layouts.peek().rect;
float x = parent.x > input.x ? parent.x : input.x;
float y = parent.y > input.y ? parent.y : input.y;
float w = input.width;
float h = input.height;
if (w + x > parent.x + parent.width) {
w = parent.x + parent.width - x;
if (0.0F > w) {
w = 0.0F;
}
}
if (h + y > parent.y + parent.height) {
h = parent.y + parent.height - y;
if (0.0F > h) {
h = 0.0F;
}
}
r = new RenderMath.Rect(x, y, w, h);
}
GuiMath.Layout l = new GuiMath.Layout(r.inset(inset), spacing, inset);
l.scissor = scissor;
if (scissor) {
l.clip = GuiMath.rounded(input).inset(spacing);
}
this.layouts.push(l);
return l;
}
public strictfp float pop() {
GuiMath.Layout l = this.layouts.pop();
float height = l.end();
if (l.y > (float)this.contentBottom) {
this.contentBottom = NumericMath.truncateInt(l.y);
}
return height;
}
public strictfp void resetLayout() {
this.layouts.clear();
this.contentBottom = 0;
}
public strictfp boolean hover(RenderMath.Rect r) {
return (this.modal == 0 || this.modalDrawing) && r.contains(this.mouseX, this.mouseY) && (this.mode == 0 || this.mode == 3);
}
public strictfp boolean click(RenderMath.Rect r) {
if (this.hover(r) && this.down && !this.consumed) {
this.consumed = true;
return true;
} else {
return false;
}
}
public strictfp void finishFrame() {
if (this.down && !this.consumed) {
this.consumed = true;
}
this.consumed = this.down;
}
public strictfp void activateBinding(Object id, IntConsumer callback) {
this.mode = 1;
this.identity = id;
this.binding = callback;
}
public strictfp void activateText(Object id, GuiMath.Filter callback) {
if ((this.mode & -2) != 2 || this.identity != id) {
this.mode = 2;
this.text.setLength(0);
this.submitted = false;
this.identity = id;
this.filter = callback;
}
}
public strictfp boolean textActive(Object id) {
return (this.mode & -2) == 2 && this.identity == id;
}
public strictfp void releaseText() {
if (this.mode == 2) {
this.mode = 3;
}
}
public strictfp String takeText() {
if ((this.mode & -2) == 2 && this.submitted) {
this.mode = 0;
this.submitted = false;
return this.text.toString();
} else {
return null;
}
}
public strictfp void clearInteraction() {
this.mode = 0;
this.down = false;
}
public strictfp boolean input(int kind, int key, int x, int y, float wheel, boolean pressed, byte[] utf8) {
if (kind == 1) {
this.rawX = (float)x;
this.rawY = (float)y;
return false;
} else if (kind == 3) {
if (this.modal == 0) {
this.scroll = wheel * GuiMath.c(1312880) + this.scroll;
}
return false;
} else if (kind != 2) {
return false;
} else {
if (pressed) {
if ((this.mode & -2) == 2) {
if (key == 1 && this.mode != 3) {
this.mode = 0;
return true;
}
if (key == 83) {
if (this.text.length() > 0) {
this.text.setLength(this.text.length() - 1);
}
return true;
}
if (key == 85) {
if (this.text.length() > 0) {
this.submitted = true;
}
return true;
}
if (key == 87) {
this.mode = 0;
return true;
}
if (key != 1) {
for (int i = 0; i < Math.min(4, utf8.length) && utf8[i] != 0; i++) {
this.filter.append(this.text, utf8[i] & 255);
}
return true;
}
} else {
if (this.mode == 1) {
this.binding.accept(key != 1 && key != 87 ? key : 0);
this.mode = 0;
return true;
}
if (this.mode == 0 && this.modal != 0 && key == 87) {
this.finishModal(true);
return true;
}
}
}
if (key == 100 || key == 101) {
this.modifier = pressed;
} else if (key == 1) {
this.down = pressed;
}
return false;
}
}
public strictfp long inputWord(long incomingSavedRsi, long callerStateWord, int kind, int key, int x, int y, float wheel, boolean pressed, byte[] utf8) {
long saved = incomingSavedRsi;
if (kind == 2 && pressed && Integer.compareUnsigned(this.mode, 1) <= 0) {
saved = callerStateWord;
}
return this.input(kind, key, x, y, wheel, pressed, utf8) ? saved & 4294967040L | 1L : 0L;
}
public strictfp void beginText(String title, Object user, GuiMath.Filter filter, Consumer<String> callback, double now) {
this.finishModal(true);
this.modal = 1;
this.title = title;
this.modalStamp = now;
this.modalProgress = this.modalHeight = 0.0F;
this.textCompletion = callback;
this.completion = () -> callback.accept(null);
this.activateText(user, filter);
}
public strictfp void beginPrompt(String message, Runnable callback, double now) {
this.finishModal(true);
this.modal = 3;
this.title = RenderConstants.text(1314628);
this.prompt = GuiMath.clip(message, 100);
this.completion = callback;
this.modalStamp = now;
this.modalProgress = this.modalHeight = 0.0F;
}
public strictfp void beginColor(float[] color, Consumer<float[]> callback, double now) {
this.beginColor("Color", color, callback, now);
}
public strictfp void beginColor(String label, float[] color, Consumer<float[]> callback, double now) {
this.finishModal(true);
this.modal = 2;
this.title = label;
this.rgb = (float[])color.clone();
this.hsl = GuiMath.rgbToHsl(this.rgb, false, false);
this.completion = () -> callback.accept((float[])this.rgb.clone());
this.modalStamp = now;
this.modalProgress = this.modalHeight = 0.0F;
Arrays.fill(this.sliderActive, false);
}
public strictfp void beginOptions(String title, List<String> candidates, Consumer<Integer> callback, double now) {
this.finishModal(true);
this.modal = 4;
this.title = title;
this.options = new ArrayList<>(candidates);
this.completion = () -> callback.accept(null);
this.selection = callback;
this.modalStamp = now;
this.modalProgress = this.modalHeight = 0.0F;
this.matches.clear();
this.overflow = 0;
this.searchStamp = 0.0;
this.activateText(this, GuiMath::catalogFilter);
this.releaseText();
}
public strictfp void choose(int index) {
this.selection.accept(index);
this.finishModal(false);
}
public strictfp void finishModal(boolean apply) {
this.mode = 0;
int old = this.modal;
if ((old == 1 || old == 2 || old == 4) && apply && this.completion != null) {
this.completion.run();
}
if (old == 2 && this.rgb != null) {
boolean found = false;
for (int back = 8; back > 0; back--) {
int index = this.historyIndex >= back ? this.historyIndex - back : this.historyIndex + 8 - back;
float[] p = this.history[index];
if (this.rgb[0] == p[0] && this.rgb[1] == p[1] && this.rgb[2] == p[2]) {
found = true;
break;
}
}
if (!found) {
this.historyIndex = this.historyIndex >= 1 ? this.historyIndex - 1 : 7;
this.history[this.historyIndex] = (float[])this.rgb.clone();
}
}
if (old == 4) {
this.matches.clear();
this.overflow = 0;
}
if (old != 0) {
this.modal = 0;
}
}
public strictfp void confirmPrompt() {
if (this.completion != null) {
this.completion.run();
}
this.finishModal(false);
}
public strictfp void submitModalText() {
String s = this.takeText();
if (s != null) {
this.textCompletion.accept(s);
this.finishModal(false);
}
}
public strictfp void filterOptions(double now) {
if (!(GuiMath.d(1314512) > now - this.searchStamp)) {
this.matches.clear();
this.overflow = 0;
byte[] search = GuiMath.bytes(this.text.toString());
for (int row = 0; row < this.options.size(); row++) {
byte[] candidate = GuiMath.bytes(this.options.get(row));
if (candidate.length >= search.length && GuiMath.substring(candidate, search)) {
if (this.matches.size() >= 30) {
this.overflow++;
} else {
this.matches.add(row);
}
}
}
this.searchStamp = now;
}
}
public strictfp boolean colorSlider(RenderMath.Rect r, int component, float inset) {
if (this.click(r)) {
this.sliderActive[component] = true;
} else {
if (!this.down) {
this.sliderActive[component] = false;
return false;
}
if (!this.sliderActive[component]) {
return false;
}
}
this.hsl[component] = RenderMath.clamp(0.0F, GuiMath.c(1314624), (this.mouseX - r.x - inset) / (r.width - 2.0F * inset));
this.rgb = GuiMath.hsl(this.hsl[0], this.hsl[1], this.hsl[2], 255);
return true;
}
public strictfp void hex(String hex) {
if (hex.length() > 5) {
for (int i = 0; i < 3; i++) {
int a = hex.charAt(2 * i) & 255;
int b = hex.charAt(2 * i + 1) & 255;
int high = a + ((byte)a >= 97 ? 169 : 208) & 0xFF;
int low = b + ((byte)b >= 97 ? 169 : 208) & 0xFF;
this.rgb[i] = (float)((double)(high << 4 | low) / GuiMath.d(1314552));
}
}
this.hsl = GuiMath.rgbToHsl(this.rgb, false, true);
}
public strictfp void selectHistory(int index) {
this.rgb = (float[])this.history[index].clone();
this.hsl = GuiMath.rgbToHsl(this.rgb, true, true);
}
public strictfp float elapsed(double now) {
return (float)(now - this.stamp);
}
public strictfp RenderMath.Rect beginFrame(float width, float height, float scale, boolean wide, double now) {
if (0.0F > this.scroll) {
this.scroll = 0.0F;
} else if (this.scroll > this.scrollMaximum) {
this.scroll = this.scrollMaximum;
}
this.mouseX = this.rawX * (GuiMath.c(1312976) / scale);
this.mouseY = this.rawY * (GuiMath.c(1312976) / scale) + this.scroll;
float elapsed = this.elapsed(now);
this.fade = RenderMath.min(GuiMath.c(1312976), (float)((double)this.fade + (double)elapsed / GuiMath.d(1313008)));
float panelWidth = RenderMath.min(width, GuiMath.c(wide ? 1312964 : 1312968));
float left = (width - panelWidth) * GuiMath.c(1312972);
this.resetLayout();
RenderMath.Rect panel = new RenderMath.Rect(left, 0.0F, panelWidth, height);
this.push(panel, GuiMath.c(1313016), GuiMath.c(1313020), false).columns(1);
return panel;
}
public strictfp void endFrame(float height, double now) {
this.scrollMaximum = RenderMath.max(0.0F, (float)this.contentBottom - height + GuiMath.c(1313020));
this.finishFrame();
this.stamp = now;
}
}
public static final strictfp class Slider {
public boolean active;
public float animation;
public double stamp;
public strictfp float update(
GuiMath.Runtime runtime,
Object id,
RenderMath.Rect track,
float value,
float min,
float max,
float step,
float displayScale,
boolean decimal,
double now
) {
return this.update(runtime, id, track, track, value, min, max, step, displayScale, now);
}
public strictfp float update(
GuiMath.Runtime runtime,
Object id,
RenderMath.Rect track,
RenderMath.Rect hit,
float value,
float min,
float max,
float step,
float displayScale,
double now
) {
float fraction = RenderMath.clamp(0.0F, GuiMath.c(1313988), (value - min) / (max - min));
this.animation = GuiMath.limited(this.animation, fraction, (float)((double)((float)(now - this.stamp)) / GuiMath.d(1313992)));
this.stamp = now;
return this.action(runtime, id, track, hit, value, min, max, step, displayScale);
}
public strictfp float action(
GuiMath.Runtime runtime, Object id, RenderMath.Rect track, RenderMath.Rect hit, float value, float min, float max, float step, float displayScale
) {
if (runtime.click(hit)) {
this.active = true;
if (runtime.modifier) {
runtime.activateText(id, GuiMath::numericFilter);
}
} else if (!runtime.down && !runtime.textActive(id)) {
this.active = false;
}
if (!this.active) {
return value;
} else {
if (runtime.textActive(id)) {
String input = runtime.takeText();
if (input == null) {
return value;
}
value = GuiMath.parseNumber(input);
if (displayScale != 0.0F) {
value /= displayScale;
}
} else {
value = (max - min) * ((runtime.mouseX - track.x) / track.width) + min;
}
if (step != 0.0F) {
value = FloatNumeric.round(value / step) * step;
}
return min >= value ? min : (value >= max ? max : value);
}
}
}
public static final strictfp class Toggle {
public float progress;
public double stamp;
public strictfp boolean animate(boolean value, double now) {
double elapsed = (double)((float)(now - this.stamp));
if (elapsed > GuiMath.d(1313856)) {
this.progress = value ? 1.0F : 0.0F;
} else if (value) {
if (GuiMath.c(1313872) > this.progress) {
this.progress = RenderMath.min((float)((double)this.progress + elapsed / GuiMath.d(1313856)), GuiMath.c(1313872));
}
} else if (this.progress > 0.0F) {
this.progress = RenderMath.max((float)((double)this.progress + elapsed / GuiMath.d(1313864)), 0.0F);
}
this.stamp = now;
return value;
}
public strictfp float smooth() {
return RenderMath.smooth(this.progress);
}
}
}
@@ -0,0 +1,86 @@
package dev.slinky.client.gui;
import dev.slinky.client.gui.settings.GuiModuleSettings;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleCategory;
import dev.slinky.module.ModuleManager;
import java.io.IOException;
import java.util.List;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
public final class GuiModuleList extends GuiScreen {
private final ModuleManager modules;
private ModuleCategory selected = ModuleCategory.COMBAT;
public GuiModuleList(ModuleManager modules) {
this.modules = modules;
}
public void initGui() {
this.buttonList.clear();
int categoryWidth = Math.max(62, this.width / ModuleCategory.values().length);
int categoryId = 1000;
for (ModuleCategory category : ModuleCategory.values()) {
this.buttonList.add(new GuiButton(categoryId++, category.ordinal() * categoryWidth, 20, categoryWidth, 20, category.getDisplayName()));
}
List<Module> visible = this.modules.inCategory(this.selected);
int y = 58;
for (int index = 0; index < visible.size(); index++) {
Module module = visible.get(index);
String state = module.isEnabled() ? "ON " : "OFF ";
this.buttonList.add(new GuiButton(2000 + index, this.width / 2 - 120, y, 240, 20, state + module.getName()));
y += 24;
}
}
protected void actionPerformed(GuiButton button) throws IOException {
if (button.id >= 1000 && button.id < 1000 + ModuleCategory.values().length) {
this.selected = ModuleCategory.values()[button.id - 1000];
this.initGui();
} else {
if (button.id >= 2000) {
List<Module> visible = this.modules.inCategory(this.selected);
int index = button.id - 2000;
if (index >= 0 && index < visible.size()) {
try {
this.modules.toggle(visible.get(index));
} catch (IllegalArgumentException var5) {
this.mc.displayGuiScreen(new GuiModuleSettings(this.modules, visible.get(index)));
return;
}
this.initGui();
}
}
}
}
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
this.drawDefaultBackground();
this.drawCenteredString(this.fontRendererObj, "Slinky Semantic 1.8.9", this.width / 2, 6, 16777215);
super.drawScreen(mouseX, mouseY, partialTicks);
this.drawCenteredString(this.fontRendererObj, "Right-click a module to edit its settings", this.width / 2, this.height - 18, 10526880);
}
public boolean doesGuiPauseGame() {
return false;
}
protected void mouseClicked(int x, int y, int button) throws IOException {
if (button == 1) {
for (GuiButton entry : this.buttonList) {
if (entry.id >= 2000 && entry.mousePressed(this.mc, x, y)) {
int index = entry.id - 2000;
this.mc.displayGuiScreen(new GuiModuleSettings(this.modules, this.modules.inCategory(this.selected).get(index)));
return;
}
}
}
super.mouseClicked(x, y, button);
}
}
@@ -0,0 +1,284 @@
package dev.slinky.client.gui;
import dev.slinky.client.numeric.FloatTrigonometry;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.client.render.DrawPlan;
import dev.slinky.client.render.RenderConstants;
import dev.slinky.client.render.RenderMath;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.IntConsumer;
public final strictfp class GuiPresentation {
private final GuiPresentation.Metrics metrics;
private final boolean canvas;
private final Map<Integer, Float> keyWidths = new HashMap<>();
public GuiPresentation(GuiPresentation.Metrics metrics) {
this(metrics, false);
}
public GuiPresentation(GuiPresentation.Metrics metrics, boolean canvas) {
this.metrics = Objects.requireNonNull(metrics);
this.canvas = canvas;
}
private static strictfp float c(int at) {
return RenderConstants.f(at);
}
private static strictfp double d(int at) {
return RenderConstants.d(at);
}
private static strictfp int rgba(float r, float g, float b, float a) {
return (int)(a * 255.0F) << 24 | ((int)(r * 255.0F) & 0xFF) << 16 | ((int)(g * 255.0F) & 0xFF) << 8 | (int)(b * 255.0F) & 0xFF;
}
private static strictfp int gray(float value) {
return rgba(value, value, value, 1.0F);
}
public static strictfp float[] alignedAnchor(RenderMath.Rect r, int alignment) {
float x = r.x;
if (alignment == 2) {
x += r.width;
} else if (alignment != 0) {
x = (float)((double)r.width * d(1312824) + (double)x);
}
float y = r.y + c(1312832);
y = (float)((double)r.height * d(1312824) + (double)y);
return new float[]{x, y};
}
public strictfp float text(DrawPlan plan, String text, RenderMath.Rect rect, int alignment, int style, float size, int color) {
float[] anchor = alignedAnchor(rect, alignment);
float advance = this.metrics.width(text, style, size);
float x = anchor[0];
if (alignment == 2) {
x -= advance;
} else if (alignment != 0) {
x -= advance * (float)d(1312824);
}
if (this.canvas) {
return plan.canvasText(text, anchor[0], anchor[1], color, size, (alignment == 0 ? 1 : (alignment == 2 ? 4 : 2)) | 16, style);
} else {
plan.text(text, x, anchor[1] - size * 0.5F, color, false, size);
return x + advance;
}
}
public strictfp float moduleText(
GuiMath.Runtime state,
DrawPlan plan,
GuiPresentation.Expansion expansion,
boolean hasCallback,
String text,
RenderMath.Rect rect,
float font,
int normal,
int highlight
) {
if (!hasCallback) {
return this.text(plan, text, rect, 0, 0, font, normal);
} else {
RenderMath.Rect expander = new RenderMath.Rect(rect.x, rect.y, rect.height, rect.height);
float spacing = c(1313312);
float textX = expander.x + expander.width + spacing;
float advance = this.metrics.width(text, 0, font);
RenderMath.Rect label = new RenderMath.Rect(textX, rect.y, advance, rect.height);
RenderMath.Rect combined = new RenderMath.Rect(rect.x, rect.y, expander.width + label.width + spacing, rect.height);
if (state.click(combined)) {
expansion.open = !expansion.open;
}
boolean hovering = state.hover(combined);
this.text(plan, text, label, 0, 0, font, hovering ? highlight : normal);
float background = c(hovering ? 1313320 : 1313316);
plan.color(background, background, background, 1.0F).rect(expander.x, expander.y, expander.width, expander.height, c(1313324));
RenderMath.Rect indicator = expander.inset(c(1313328));
float foreground = c(hovering ? 1313336 : 1313332);
float half = c(1313340);
plan.color(foreground, foreground, foreground, 1.0F)
.lineWidth(c(1313344))
.line(indicator.x, indicator.height * half + indicator.y, indicator.x + indicator.width, indicator.height * half + indicator.y);
if (!expansion.open) {
float cx = half * indicator.width + indicator.x;
plan.line(cx, indicator.y, cx, indicator.y + indicator.height);
}
return combined.x + combined.width;
}
}
public strictfp void binding(
GuiMath.Runtime state, DrawPlan plan, GuiPresentation.Binding record, RenderMath.Rect rectangle, float[] accent, boolean modeAllowed
) {
float font = c(1313348);
boolean active = state.mode == 1 && state.identity == record.identity;
String label = active ? RenderConstants.text(1313462) : RenderModuleAccess.keyName(record.key);
float width;
if (active) {
width = NumericMath.ceiling(this.metrics.width(label, 1, font));
} else {
Float cached = this.keyWidths.get(record.key);
width = cached == null ? 0.0F : cached;
if (width == 0.0F) {
width = NumericMath.ceiling(this.metrics.width(label, 1, font));
this.keyWidths.put(record.key, width);
}
}
RenderMath.Rect button = new RenderMath.Rect(rectangle.x, rectangle.y, width + font, rectangle.height);
float idle = c(1313356);
float[] base = record.key != 0 ? accent : new float[]{idle, idle, idle, 1.0F};
plan.color(base[0], base[1], base[2], base[3] * c(1313352)).rect(button.x, button.y, button.width, button.height, c(1313360));
int color = state.hover(button) ? gray(c(1313364)) : rgba(base[0], base[1], base[2], base[3]);
this.text(plan, label, button, 1, 1, font, color);
if (state.click(button)) {
state.activateBinding(record.identity, key -> {
record.key = key;
record.changed.accept(key);
});
}
if (record.key == 0) {
if (modeAllowed) {
record.hold = false;
}
} else if (modeAllowed) {
String toggleLabel = RenderConstants.text(1313477);
float toggleWidth = NumericMath.ceiling(this.metrics.width(toggleLabel, 1, font));
RenderMath.Rect toggle = new RenderMath.Rect(button.x + button.width + c(1313360), button.y, toggleWidth + font, button.height);
if (record.hold || state.hover(rectangle)) {
float value = c(record.hold ? 1313368 : 1313356);
plan.color(value, value, value, c(1313352)).rect(toggle.x, toggle.y, toggle.width, toggle.height, c(1313360));
this.text(plan, toggleLabel, toggle, 1, 1, font, gray(c(state.hover(toggle) ? 1313368 : 1313356)));
if (state.click(toggle)) {
record.hold = !record.hold;
}
}
}
}
public static strictfp GuiPresentation.Icon icon(RenderMath.Rect rect, float[] atlasOrigins, int id, float textureWidth, float textureHeight) {
float sx = rect.width / c(1312816);
float sy = rect.height / c(1312816);
return new GuiPresentation.Icon(rect.x - atlasOrigins[id * 2] * sx, rect.y - atlasOrigins[id * 2 + 1] * sy, sx * textureWidth, sy * textureHeight);
}
public static strictfp float caretAlpha(float frequency, double now) {
float phase = (float)((double)frequency * now);
float half = c(1312856);
return half + FloatTrigonometry.sine(phase) * half;
}
public strictfp void caret(DrawPlan plan, RenderMath.Rect r, float[] color, float frequency, float width, double now) {
plan.color(color[0], color[1], color[2], color[3] * caretAlpha(frequency, now)).lineWidth(width).line(r.x, r.y, r.x, r.y + r.height);
}
public strictfp void textEntry(GuiMath.Runtime state, DrawPlan plan, RenderMath.Rect r, int alignment, float font, double now) {
float advance = this.text(plan, state.text.toString(), r, alignment, 0, font, -1) + c(alignment == 1 ? 1314324 : 1314336);
float gray = c(alignment == 1 ? 1314328 : 1314340);
this.caret(
plan, new RenderMath.Rect(advance, r.y, r.width, r.height), new float[]{gray, gray, gray, 1.0F}, c(alignment == 1 ? 1314332 : 1314344), 1.0F, now
);
}
public strictfp void tooltip(DrawPlan plan, GuiPresentation.Tooltip state, float elapsed) {
if (!state.requested) {
state.progress = 0.0F;
} else {
state.progress = (float)((double)state.progress + (double)elapsed / d(1313136));
plan.push().translate(state.x, state.y, 0.0F);
if (state.progress > c(1312976)) {
state.progress = 1.0F;
} else {
float smooth = RenderMath.smooth(state.progress);
plan.opacity(smooth).scale(smooth, smooth, 1.0F);
}
float font = c(1313088);
float width = this.metrics.width(state.text, 0, font);
RenderMath.Rect toast = new RenderMath.Rect((float)((double)width * d(1313144) + d(1313152)), c(1313164), width + c(1313160), 20.0F);
float bg = c(1313168);
plan.color(bg, bg, bg, 1.0F).rect(toast.x, toast.y, toast.width, toast.height, c(1313076));
this.text(plan, state.text, toast, 1, 0, font, gray(c(1313072)));
plan.pop();
state.requested = false;
}
}
public static strictfp float unsignedFloat(long value) {
if (value >= 0L) {
return (float)value;
} else {
long half = value >>> 1 | value & 1L;
float converted = (float)half;
return converted + converted;
}
}
public static strictfp float categoryHeight(float height) {
long signed = NumericMath.truncateLong(height);
long adjusted = NumericMath.truncateLong(height - c(1313308));
long sign = signed >> 63;
return unsignedFloat(signed | adjusted & sign);
}
public static final strictfp class Binding {
public int key;
public boolean hold;
public final Object identity;
public final IntConsumer changed;
public Binding(int key, boolean hold, Object identity, IntConsumer changed) {
this.key = key;
this.hold = hold;
this.identity = identity;
this.changed = changed;
}
}
public static final class Expansion {
public boolean open;
}
public static final strictfp class Icon {
public final float x;
public final float y;
public final float width;
public final float height;
public Icon(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
}
public interface Metrics {
float width(String var1, int var2, float var3);
}
public static final class Tooltip {
public String text;
public float x;
public float y;
public float progress;
public boolean requested;
public strictfp void request(String text, int x, int y) {
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
this.text = new String(bytes, 0, Math.min(bytes.length, 100), StandardCharsets.UTF_8);
this.x = (float)x;
this.y = (float)y;
this.requested = true;
}
}
}
@@ -0,0 +1,491 @@
package dev.slinky.client.gui;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.client.render.DrawPlan;
import dev.slinky.client.render.RenderConstants;
import dev.slinky.client.render.RenderMath;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
public final strictfp class GuiWidgets {
private final GuiPresentation view;
private final GuiPresentation.Tooltip tooltip;
private final GuiWidgets.Formatter formatter;
public GuiWidgets(GuiPresentation view, GuiPresentation.Tooltip tooltip, GuiWidgets.Formatter formatter) {
this.view = Objects.requireNonNull(view);
this.tooltip = Objects.requireNonNull(tooltip);
this.formatter = Objects.requireNonNull(formatter);
}
private static strictfp float c(int at) {
return RenderConstants.f(at);
}
private static strictfp double d(int at) {
return RenderConstants.d(at);
}
private static strictfp int gray(float value) {
int v = (int)(value * 255.0F);
return 0xFF000000 | (v & 0xFF) << 16 | (v & 0xFF) << 8 | v & 0xFF;
}
private static strictfp int rgba(float[] color) {
return (int)(color[3] * 255.0F) << 24
| ((int)(color[0] * 255.0F) & 0xFF) << 16
| ((int)(color[1] * 255.0F) & 0xFF) << 8
| (int)(color[2] * 255.0F) & 0xFF;
}
private static strictfp RenderMath.Rect height(RenderMath.Rect rect, float h) {
return new RenderMath.Rect(rect.x, rect.y, rect.width, h);
}
private strictfp void label(DrawPlan plan, String text, RenderMath.Rect rect, int shade, int size) {
this.view.text(plan, text, rect, 0, 0, c(size), gray(c(shade)));
}
private static strictfp void solid(DrawPlan plan, float[] color) {
plan.color(color[0], color[1], color[2], color[3]);
}
private static strictfp void stroke(DrawPlan plan, RenderMath.Rect rect, int shade, int width, int radius) {
float v = c(shade);
plan.color(v, v, v, 1.0F).lineWidth(c(width)).roundedStroke(rect.x, rect.y, rect.width, rect.height, c(radius));
}
public static strictfp float[] blend(float[] a, float[] b, float fraction) {
float amount = fraction < 0.0F ? 0.0F : RenderMath.min(c(2838932), fraction);
float complement = c(2838932) - amount;
float[] out = new float[4];
for (int i = 0; i < 4; i++) {
out[i] = complement * a[i] + amount * b[i];
}
return out;
}
private static strictfp void circle(DrawPlan plan, float x, float y, float radius) {
plan.op("circle", x, y, radius);
}
private static strictfp void close(DrawPlan plan, RenderMath.Rect rect, boolean hover, float normal, int red, int other, int inset) {
float r = hover ? c(red) : normal;
float g = hover ? c(other) : normal;
float amount = (float)((double)rect.height * d(inset));
float left = rect.x + amount;
float top = rect.y + amount;
float right = rect.height + rect.x - amount;
float bottom = rect.y + rect.height - amount;
plan.color(r, g, g, 1.0F).line(left, top, right, bottom).line(right, top, left, bottom);
}
private strictfp String formatted(String format, int defaultAddress, float scale, float... values) {
double[] args = new double[values.length];
for (int i = 0; i < args.length; i++) {
args[i] = (double)(scale != 0.0F ? scale * values[i] : values[i]);
}
return this.formatter.format(format == null ? RenderConstants.text(defaultAddress) : format, args);
}
private strictfp void tip(String text, float x, float y) {
this.tooltip.request(text, NumericMath.truncateInt(x), NumericMath.truncateInt(y));
}
public static strictfp boolean toggle(
GuiMath.Runtime state, DrawPlan plan, GuiMath.Toggle animation, RenderMath.Rect rectangle, boolean value, float[] accent, double now
) {
animation.animate(value, now);
float amount = animation.smooth();
float shade = c(state.hover(rectangle) ? 1313880 : 1313876);
float[] color = blend(new float[]{shade, shade, shade, 1.0F}, accent, amount);
solid(plan, color);
plan.rect(rectangle.x, rectangle.y, rectangle.width, rectangle.height, c(1313884));
if (state.click(rectangle)) {
value = !value;
}
RenderMath.Rect inset = rectangle.inset(c(1313888));
float white = c(1313892);
float integerHeight = (float)NumericMath.truncateInt(inset.height);
float half = integerHeight * c(1313896);
float x = RenderMath.lerp(half + inset.x, inset.x + inset.width - half, amount);
float y = half + inset.y;
plan.color(white, white, white, 1.0F);
circle(plan, x, y, integerHeight);
return value;
}
public strictfp boolean checkbox(
GuiMath.Runtime state,
DrawPlan plan,
GuiMath.Layout layout,
String text,
GuiMath.Toggle animation,
boolean value,
float[] accent,
double now
) {
RenderMath.Rect rect = layout.next(c(1313900));
this.label(plan, text, rect, 1313904, 1313908);
return toggle(state, plan, animation, new RenderMath.Rect(rect.x + rect.width + c(1313912), rect.y, c(1313916), rect.height), value, accent, now);
}
public strictfp void section(DrawPlan plan, GuiMath.Layout layout, String text) {
RenderMath.Rect rect = layout.next(c(1313920));
this.label(plan, text, new RenderMath.Rect(rect.x, rect.y + c(1313924), rect.width, rect.height), 1313928, 1313932);
}
public strictfp float number(
GuiMath.Runtime state,
DrawPlan plan,
GuiMath.Layout layout,
String text,
String format,
Object id,
GuiMath.Slider animation,
float minimum,
float maximum,
float step,
float displayScale,
float value,
float[] accent,
double now
) {
RenderMath.Rect rect = layout.next(c(1313936));
this.label(plan, text, rect, 1313940, 1313944);
double half = (double)rect.width * d(1313952);
RenderMath.Rect track = new RenderMath.Rect((float)((double)rect.x + half), rect.y + c(1313948) + c(1313960), (float)(half + d(1313968)), 4.0F);
float shade = c(state.hover(rect) ? 1313980 : 1313976);
float radius = c(1313984);
plan.color(shade, shade, shade, 1.0F).rect(track.x, track.y, track.width, track.height, radius);
float fraction = RenderMath.clamp(0.0F, c(1313988), (value - minimum) / (maximum - minimum));
float ratio = (float)((double)((float)(now - animation.stamp)) / d(1313992));
animation.animation = GuiMath.limited(animation.animation, fraction, ratio);
animation.stamp = now;
float end = RenderMath.lerp(track.x, track.x + track.width, animation.animation);
solid(plan, accent);
plan.rect(track.x, track.y, end - track.x, 4.0F, radius);
circle(plan, end, radius + track.y, c(1314000));
value = animation.action(state, id, track, rect, value, minimum, maximum, step, displayScale);
if (animation.active || state.hover(rect) && !state.down) {
this.tip(state.textActive(id) ? state.text.toString() : this.formatted(format, 1314387, displayScale, value), end, track.y);
}
return value;
}
public strictfp float[] range(
GuiMath.Runtime state,
DrawPlan plan,
GuiMath.Layout layout,
String text,
String format,
String pairFormat,
Object lowId,
Object highId,
GuiWidgets.Range animation,
float minimum,
float maximum,
float step,
float displayScale,
float lower,
float upper,
float[] accent,
double now
) {
RenderMath.Rect rect = layout.next(c(1314024));
this.label(plan, text, rect, 1314028, 1314032);
double half = (double)rect.width * d(1314040);
RenderMath.Rect track = new RenderMath.Rect((float)((double)rect.x + half), rect.y + c(1314036) + c(1314048), (float)(half + d(1314056)), 4.0F);
float shade = c(state.hover(rect) ? 1314068 : 1314064);
float radius = c(1314072);
plan.color(shade, shade, shade, 1.0F).rect(track.x, track.y, track.width, 4.0F, radius);
float ratio = (float)((double)((float)(now - animation.stamp)) / d(1314080));
float difference = maximum - minimum;
animation.lower.animation = GuiMath.limited(animation.lower.animation, RenderMath.clamp(0.0F, c(1314076), (lower - minimum) / difference), ratio);
animation.upper.animation = GuiMath.limited(animation.upper.animation, RenderMath.clamp(0.0F, c(1314076), (upper - minimum) / difference), ratio);
animation.stamp = now;
float lowX = RenderMath.lerp(track.x, track.x + track.width, animation.lower.animation);
float highX = RenderMath.lerp(track.x, track.x + track.width, animation.upper.animation);
solid(plan, accent);
plan.rect(lowX, track.y, highX - lowX, 4.0F, radius);
circle(plan, lowX, radius + track.y, c(1314088));
circle(plan, highX, radius + track.y, c(1314088));
RenderMath.Rect[] hits = rangeHits(track, rect, lowX, highX);
lower = animation.lower.action(state, lowId, track, hits[0], lower, minimum, maximum, step, displayScale);
if (animation.lower.active) {
if (lower > upper) {
upper = lower;
}
this.tip(state.textActive(lowId) ? state.text.toString() : this.formatted(format, 1314387, displayScale, lower), lowX, track.y);
} else {
upper = animation.upper.action(state, highId, track, hits[1], upper, minimum, maximum, step, displayScale);
if (animation.upper.active) {
if (lower > upper) {
lower = upper;
}
this.tip(state.textActive(highId) ? state.text.toString() : this.formatted(format, 1314387, displayScale, upper), highX, track.y);
} else if (state.hover(rect) && (!state.down || animation.lower.active || animation.upper.active)) {
float sum = hits[0].x + hits[0].width + hits[1].x;
this.tooltip
.request(
this.formatted(pairFormat, 1314348, displayScale, lower, upper),
NumericMath.truncateInt((double)sum * d(1314040)),
NumericMath.truncateInt(track.y)
);
}
}
return new float[]{lower, upper};
}
public static strictfp RenderMath.Rect[] rangeHits(RenderMath.Rect track, RenderMath.Rect row, float lowX, float highX) {
float center = (float)NumericMath.truncateInt((double)(lowX + highX) * d(1314040));
float distance = center - track.x;
float extra = c(1314096);
return new RenderMath.Rect[]{
new RenderMath.Rect(c(1314092) + track.x, row.y, distance + extra, row.height),
new RenderMath.Rect(center, row.y, track.width - distance + extra, row.height)
};
}
public strictfp int choice(
GuiMath.Runtime state,
DrawPlan plan,
GuiMath.Layout layout,
String text,
GuiWidgets.Choice animation,
List<String> options,
int[] current,
int flag,
float[] accent,
double now
) {
return this.choice(state, plan, layout, text, animation, options, current, flag, accent, now, index -> current[0] = index);
}
public strictfp int choice(
GuiMath.Runtime state,
DrawPlan plan,
GuiMath.Layout layout,
String text,
GuiWidgets.Choice animation,
List<String> options,
int[] current,
int flag,
float[] accent,
double now,
IntConsumer accept
) {
int count = options.size();
float target = animation.open ? (float)Integer.toUnsignedLong(count * 28) : c(1314104);
float ratio = (float)((double)((float)(now - animation.stamp)) / d(1314112));
animation.height = GuiMath.limited(animation.height, target, ratio);
animation.stamp = now;
RenderMath.Rect rect = layout.next(animation.height);
this.label(plan, text, height(rect, 20.0F), 1314120, 1314124);
RenderMath.Rect selection = new RenderMath.Rect((float)((double)rect.width * d(1314128) + (double)rect.x), rect.y, rect.width * c(1314136), rect.height);
stroke(plan, selection, 1314140, 1314144, 1314148);
float shade = c(1314140);
if (animation.open) {
if (count == 0) {
return 0;
} else {
float rowHeight = selection.height / (float)Integer.toUnsignedLong(count);
boolean selected = false;
for (int index = 0; index < count; index++) {
RenderMath.Rect row = new RenderMath.Rect(selection.x, (float)index * rowHeight + selection.y, selection.width, rowHeight);
boolean hover = state.hover(row);
int inverted = (flag ^ 1) & 0xFF;
float[] color;
if (index == current[0]) {
color = accent;
} else {
int at = hover ? (((index == 0 ? 1 : 0) | inverted) != 0 ? 1314168 : 1314176) : (((index != 0 ? 1 : 0) | inverted) != 0 ? 1314120 : 1314172);
float value = c(at);
color = new float[]{value, value, value, 1.0F};
}
this.view.text(plan, options.get(index), row, 1, 0, c(1314124), rgba(color));
if (!selected && state.click(row)) {
current[0] = index;
animation.open = false;
selected = true;
}
if (index != 0) {
plan.color(shade, shade, shade, c(1314136)).line(selection.x, row.y, selection.x + selection.width, row.y);
}
}
return selected ? 1 : 0;
}
} else {
boolean hoverx = state.hover(selection);
if (((flag ^ 1 | (count < 6 ? 1 : 0)) & 0xFF) == 0 && current[0] != 0) {
RenderMath.Rect clear = new RenderMath.Rect(selection.width - selection.height + selection.x, selection.y, selection.height, selection.height);
boolean clearHover = state.hover(clear);
close(plan, clear, clearHover, shade, 1314152, 1314156, 1314160);
if (state.click(clear)) {
current[0] = 0;
}
hoverx = hoverx && !clearHover;
}
int at = flag != 0 && current[0] == 0 ? (hoverx ? 1314176 : 1314172) : (hoverx ? 1314168 : 1314120);
this.view.text(plan, options.get(current[0]), selection, 1, 0, c(1314124), gray(c(at)));
if (state.click(selection)) {
if (count < 6) {
animation.open = true;
} else {
state.beginOptions(RenderConstants.text(1314362), options.subList(1, count), index -> {
if (index != null) {
current[0] = index + 1;
accept.accept(current[0]);
}
}, now);
}
}
return 0;
}
}
public strictfp boolean color(
GuiMath.Runtime state, DrawPlan plan, GuiMath.Layout layout, String text, float[] current, Consumer<float[]> accept, double now
) {
RenderMath.Rect rect = layout.next(c(1314184));
this.label(plan, text, height(rect, 20.0F), 1314188, 1314192);
RenderMath.Rect preview = new RenderMath.Rect(rect.x + rect.width + c(1314196), rect.y, c(1314200), 20.0F);
float radius = preview.height * 0.5F;
plan.color(current[0], current[1], current[2], 1.0F).rect(preview.x, preview.y, preview.width, preview.height, radius);
plan.color(0.33F, 0.33F, 0.33F, 1.0F).lineWidth(1.0F).roundedStroke(preview.x, preview.y, preview.width, preview.height, radius);
RenderMath.Rect hit = new RenderMath.Rect((float)((double)rect.width * d(1314216) + (double)rect.x), rect.y, rect.width * c(1314224), rect.height);
boolean clicked = state.click(hit);
if (clicked) {
state.beginColor(text, current, accept, now);
}
return clicked;
}
public strictfp boolean list(
GuiMath.Runtime state,
DrawPlan plan,
GuiMath.Layout layout,
String text,
String title,
GuiMath.Filter filter,
int kind,
GuiWidgets.ListData list,
List<String> suggestions,
double now
) {
long count = list.size();
long rows = count + 1L;
float rowCount = GuiPresentation.unsignedFloat(rows);
RenderMath.Rect rect = layout.next(GuiPresentation.unsignedFloat(rows * 28L));
this.label(plan, text, height(rect, 20.0F), 1314232, 1314236);
RenderMath.Rect selection = new RenderMath.Rect((float)((double)rect.width * d(1314240) + (double)rect.x), rect.y, rect.width * c(1314248), rect.height);
stroke(plan, selection, 1314252, 1314256, 1314260);
float rowHeight = selection.height / rowCount;
float shade = c(1314252);
long removed = -1L;
boolean result = false;
for (long index = 0L; Long.compareUnsigned(index, count) < 0; index++) {
RenderMath.Rect row = new RenderMath.Rect(selection.x, GuiPresentation.unsignedFloat(index) * rowHeight + selection.y, selection.width, rowHeight);
String name = list.name(index);
byte[] bytes = name.getBytes(StandardCharsets.UTF_8);
String shown = new String(bytes, 0, Math.min(bytes.length, 20), StandardCharsets.UTF_8);
this.view.text(plan, shown, row, 1, 0, c(1314236), gray(c(1314232)));
if (kind == 1) {
RenderMath.Rect preview = new RenderMath.Rect(row.x, row.y, row.height, row.height).inset(c(1314264));
float[] rgb = list.color(index);
plan.color(rgb[0], rgb[1], rgb[2], 1.0F).rect(preview.x, preview.y, preview.width, preview.height, c(1314268));
if (state.click(preview)) {
long selected = index;
state.beginColor(name, rgb, value -> list.color(selected, value), now);
result = true;
}
}
RenderMath.Rect remove = new RenderMath.Rect(row.width - row.height + row.x, row.y, row.height, row.height);
close(plan, remove, state.hover(remove), shade, 1314272, 1314276, 1314280);
if (state.click(remove)) {
removed = index;
}
plan.color(shade, shade, shade, c(1314248)).line(row.x, row.y + row.height, row.x + row.width, row.y + row.height);
}
if (removed != -1L) {
list.remove(removed);
result = true;
}
RenderMath.Rect add = new RenderMath.Rect(selection.x, GuiPresentation.unsignedFloat(count) * rowHeight + selection.y, selection.width, rowHeight);
this.view.text(plan, RenderConstants.text(1314374), add, 1, 0, c(1314236), gray(c(state.hover(add) ? 1314292 : 1314288)));
if (state.click(add)) {
if (suggestions != null) {
state.beginOptions(title, suggestions, index -> {
if (index != null) {
list.add(suggestions.get(index), new float[]{c(1314296), 0.0F, 0.0F});
}
}, now);
} else {
state.beginText(title, list, filter, value -> {
if (value != null) {
list.add(value, new float[]{c(1314300), 0.0F, 0.0F});
}
}, now);
}
result = true;
}
return result;
}
public static final class Choice {
public boolean open;
public float height;
public double stamp;
}
public interface Formatter {
String format(String var1, double... var2);
}
public interface ListData {
long size();
String name(long var1);
float[] color(long var1);
void color(long var1, float[] var3);
void add(String var1, float[] var2);
void remove(long var1);
}
public static final class Range {
public final GuiMath.Slider lower = new GuiMath.Slider();
public final GuiMath.Slider upper = new GuiMath.Slider();
public double stamp;
}
}
@@ -0,0 +1,64 @@
package dev.slinky.client.gui;
public final class HoldActivation {
public static final double ACTION_SECONDS = 1.0;
private Object identity;
private double start;
private boolean fired;
private boolean seen;
private float progress;
public void beginFrame() {
this.seen = false;
}
public boolean update(Object target, boolean hovering, boolean down, boolean available, boolean canStart, double now) {
if (this.identity != null && this.identity.equals(target)) {
this.seen = true;
}
if (available && hovering && down) {
if (this.identity == null || !this.identity.equals(target)) {
if (!canStart) {
return false;
}
this.identity = target;
this.start = now;
this.fired = false;
}
this.seen = true;
this.progress = (float)Math.max(0.0, Math.min(1.0, (now - this.start) / 1.0));
if (!(this.progress < 1.0F) && !this.fired) {
this.fired = true;
return true;
} else {
return false;
}
} else {
if (this.identity != null && this.identity.equals(target)) {
this.reset();
}
return false;
}
}
public float progress(Object target) {
return this.identity != null && this.identity.equals(target) ? this.progress : 0.0F;
}
public void endFrame() {
if (!this.seen) {
this.reset();
}
}
public void reset() {
this.identity = null;
this.progress = 0.0F;
this.fired = false;
this.seen = false;
}
}
@@ -0,0 +1,45 @@
package dev.slinky.client.gui;
import dev.slinky.client.input.KeyState;
import dev.slinky.module.Module;
import java.util.Objects;
import java.util.function.BooleanSupplier;
import java.util.function.IntFunction;
public final class RenderModuleAccess {
private static volatile IntFunction<String> keys = KeyState::displayName;
private static volatile BooleanSupplier guiInteraction = () -> false;
public static int category(Module module) {
return module.metadataCategory();
}
public static int observedFlags(Module module) {
return module.descriptorFlags();
}
public static String status(Module module) {
return module.statusText();
}
public static String keyName(int key) {
return keys.apply(key);
}
public static IntFunction<String> bindKeyNames(IntFunction<String> adapter) {
IntFunction<String> previous = keys;
keys = Objects.requireNonNull(adapter);
return previous;
}
public static boolean guiInteractionRequested() {
return guiInteraction.getAsBoolean();
}
public static void bindGuiInteraction(BooleanSupplier query) {
guiInteraction = Objects.requireNonNull(query);
}
private RenderModuleAccess() {
}
}
@@ -0,0 +1,67 @@
package dev.slinky.client.gui.profile;
import dev.slinky.client.render.DrawPlan;
import dev.slinky.client.render.RenderMath;
import dev.slinky.client.render.font.CanvasFonts;
import dev.slinky.inject.Platform;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
public final class ProfileIcons {
private static final byte[] COVERAGE = coverage();
private static final int TEXTURE = CanvasFonts.allocateTextureIdentity();
private static byte[] coverage() {
byte[] result = new byte[6400];
try (InputStream input = Platform.resource("/assets/slinkysemantic/profile-icon-coverage.bin")) {
if (input == null) {
throw new IOException("Profile icon coverage is missing");
} else {
int position = 0;
while (position < result.length) {
int count = input.read(result, position, result.length - position);
if (count < 0) {
throw new IOException("Incomplete profile icon atlas");
}
position += count;
}
return result;
}
} catch (IOException var15) {
throw new IllegalStateException("Cannot load profile icons", var15);
}
}
public static void draw(DrawPlan plan, RenderMath.Rect button, int action, boolean enabled, boolean hover) {
float u0 = (float)action / 4.0F;
float u1 = (float)(action + 1) / 4.0F;
float x = button.x;
float y = button.y;
float right = x + button.width;
float bottom = y + button.height;
int color = action == 3 ? (!enabled ? -8901331 : (hover ? -1089697 : -2275513)) : (!enabled ? -11119018 : (hover ? -2368549 : -6710887));
plan.alphaSprite(
TEXTURE,
160,
40,
COVERAGE,
Arrays.asList(
new float[]{x, y, u0, 0.0F},
new float[]{right, bottom, u1, 1.0F},
new float[]{right, y, u1, 0.0F},
new float[]{x, y, u0, 0.0F},
new float[]{x, bottom, u0, 1.0F},
new float[]{right, bottom, u1, 1.0F}
),
color
);
}
private ProfileIcons() {
}
}
@@ -0,0 +1,127 @@
package dev.slinky.client.gui.profile;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import dev.slinky.client.numeric.FixedWidth;
import dev.slinky.inject.Platform;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public final class ProfileMath {
private static final Map<Integer, byte[]> LABELS = labels();
private static final ProfileMath.Formatter JAVA_FORMAT = (pattern, value, capacity) -> {
String format = new String(pattern, StandardCharsets.UTF_8).replace("%lld", "%d");
byte[] result = String.format(Locale.ROOT, format, value).getBytes(StandardCharsets.UTF_8);
return Arrays.copyOf(result, Math.min(result.length, capacity - 1));
};
private ProfileMath() {
}
private static Map<Integer, byte[]> labels() {
Map<Integer, byte[]> result = new HashMap<>();
InputStream stream = Platform.resource("/assets/slinkysemantic/ProfileLabels.json");
if (stream == null) {
throw new IllegalStateException("Missing immutable profile labels");
} else {
try (Reader reader = new InputStreamReader(stream, "UTF-8")) {
for (JsonElement element : new JsonParser().parse(reader).getAsJsonArray()) {
JsonObject row = element.getAsJsonObject();
String hex = row.get("bytes").getAsString();
byte[] bytes = new byte[hex.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte)Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
}
result.put(row.get("rva").getAsInt(), bytes);
}
return result;
} catch (IOException var20) {
throw new ExceptionInInitializerError(var20);
}
}
}
public static long ageSeconds(long nowFileTime, long modifiedFileTime) {
return Long.compareUnsigned(modifiedFileTime - 1L, nowFileTime) >= 0
? -1L
: FixedWidth.unsignedMultiplyHigh(nowFileTime - modifiedFileTime, -2972493582642298179L) >>> 23;
}
public static long days(long ageSeconds) {
long high = FixedWidth.signedMultiplyHigh(ageSeconds, 1749024623285053783L);
return (high >> 13) + (high >>> 63);
}
public static byte[] dateBytes(long ageSeconds, ProfileMath.Formatter formatter) {
byte[] buffer = new byte[32];
if (ageSeconds == -1L) {
System.arraycopy(LABELS.get(1310590), 0, buffer, 0, 16);
byte[] end = new byte[]{116, 105, 111, 110, 32, 100, 97, 116};
System.arraycopy(end, 0, buffer, 16, end.length);
} else {
long day = days(ageSeconds);
if (day == 1L) {
System.arraycopy(LABELS.get(1310641), 0, buffer, 0, 16);
buffer[16] = 103;
buffer[17] = 111;
buffer[18] = 46;
} else if (day == 0L) {
System.arraycopy(LABELS.get(1310665), 0, buffer, 0, 16);
} else {
byte[] text = formatter.format(cString(LABELS.get(1310617)), day, 25);
System.arraycopy(text, 0, buffer, 0, Math.min(text.length, 24));
}
}
return buffer;
}
public static String date(long ageSeconds) {
return new String(cString(dateBytes(ageSeconds, JAVA_FORMAT)), StandardCharsets.UTF_8);
}
private static byte[] cString(byte[] text) {
int length = 0;
while (length < text.length && text[length] != 0) {
length++;
}
return Arrays.copyOf(text, length);
}
public static int compareNames(String left, String right) {
byte[] a = left.getBytes(StandardCharsets.UTF_8);
byte[] b = right.getBytes(StandardCharsets.UTF_8);
int i = 0;
while (true) {
int x = i < a.length ? a[i] & 255 : 0;
int y = i < b.length ? b[i] & 255 : 0;
if (x != y) {
return x - y;
}
if (x == 0) {
return 0;
}
i++;
}
}
public interface Formatter {
byte[] format(byte[] var1, long var2, int var4);
}
}
@@ -0,0 +1,342 @@
package dev.slinky.client.gui.profile;
import dev.slinky.client.render.RenderConstants;
import dev.slinky.config.ConfigStore;
import dev.slinky.config.ConfigurationFiles;
import dev.slinky.config.ModuleConfiguration;
import dev.slinky.module.ModuleManager;
import java.awt.Desktop;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.LongSupplier;
public final class ProfileServices {
private ProfileServices() {
}
public interface DirectoryScanner {
void scan(Path var1, LongSupplier var2, ProfileServices.RecordReceiver var3) throws IOException;
}
public static final class Failure extends IOException {
public final int code;
public Failure(int code, String message) {
super(message);
this.code = code;
}
}
public static final class FilesHost implements ProfileServices.Host {
private static final long FILETIME_EPOCH = 116444736000000000L;
private final ModuleManager manager;
private final Path directory;
private final ConfigStore configuration;
private final LongSupplier fileTime;
private final ProfileServices.DirectoryScanner scanner;
private List<ProfileServices.Record> rows = Collections.emptyList();
public FilesHost(ModuleManager manager, Path directory, ConfigStore configuration) {
this(manager, directory, configuration, () -> System.currentTimeMillis() * 10000L + 116444736000000000L);
}
public FilesHost(ModuleManager manager, Path directory, ConfigStore configuration, LongSupplier fileTime) {
this(manager, directory, configuration, fileTime, ProfileServices.FilesHost::scanDirectory);
}
public FilesHost(ModuleManager manager, Path directory, ConfigStore configuration, LongSupplier fileTime, ProfileServices.DirectoryScanner scanner) {
this.manager = Objects.requireNonNull(manager);
this.directory = directory.toAbsolutePath().normalize();
this.configuration = Objects.requireNonNull(configuration);
this.fileTime = Objects.requireNonNull(fileTime);
this.scanner = Objects.requireNonNull(scanner);
}
private Path path(String name) throws IOException {
if (name != null && !name.isEmpty()) {
try {
Path target = this.directory.resolve(name).normalize();
if (!this.directory.equals(target.getParent())) {
throw new ProfileServices.Failure(1012, "Invalid profile name");
} else {
return target;
}
} catch (InvalidPathException var3) {
throw new ProfileServices.Failure(1006, "Invalid profile filename");
}
} else {
throw new ProfileServices.Failure(1012, "A profile name is required");
}
}
@Override
public synchronized List<ProfileServices.Record> records() throws IOException {
Map<String, Integer> retained = new TreeMap<>(ProfileMath::compareNames);
for (ModuleConfiguration.ProfileBinding binding : this.configuration.profileBindings()) {
if (binding.binding != 0 && !retained.containsKey(binding.name)) {
retained.put(binding.name, binding.binding);
}
}
for (ProfileServices.Record row : this.rows) {
if (row.binding != 0) {
retained.put(row.name, row.binding);
}
}
List<ProfileServices.Record> next = new ArrayList<>();
this.rows = next;
try {
ensure(this.directory.getParent());
ensure(this.directory);
this.scanner.scan(this.directory, this.fileTime, (name, age) -> {
if (name.getBytes(StandardCharsets.UTF_8).length >= 260) {
throw new ProfileServices.Failure(1006, "Profile filename exceeds the narrow host buffer");
} else {
Integer bindingx = retained.get(name);
next.add(new ProfileServices.Record(name, age, bindingx == null ? 0 : bindingx));
}
});
} finally {
next.sort((left, right) -> {
int order = Long.compare(left.ageSeconds, right.ageSeconds);
return order != 0 ? order : ProfileMath.compareNames(left.name, right.name);
});
this.saveBindings();
}
return this.snapshot();
}
@Override
public synchronized List<ProfileServices.Record> snapshot() {
return Collections.unmodifiableList(new ArrayList<>(this.rows));
}
private static void ensure(Path directory) throws IOException {
ConfigurationFiles.WidePath path = new ConfigurationFiles.WidePath();
path.text = ConfigurationFiles.wide(directory.toString());
try (ConfigurationFiles.NioHost host = new ConfigurationFiles.NioHost(null)) {
int code = ConfigurationFiles.ensureDirectory(path, host);
if (code != 0) {
throw new ProfileServices.Failure(code, "Could not create profile directory");
}
}
}
private static void scanDirectory(Path directory, LongSupplier fileTime, ProfileServices.RecordReceiver receiver) throws IOException {
DirectoryStream<Path> files;
try {
files = Files.newDirectoryStream(directory);
} catch (NoSuchFileException var22) {
return;
} catch (IOException var23) {
throw new ProfileServices.Failure(1013, "Could not begin profile enumeration");
}
try {
DirectoryStream<Path> opened = files;
Throwable var5 = null;
try {
long now = fileTime.getAsLong();
for (Path file : opened) {
if (!Files.isDirectory(file)) {
long modified = Files.getLastModifiedTime(file).toMillis() * 10000L + 116444736000000000L;
receiver.record(file.getFileName().toString(), ProfileMath.ageSeconds(now, modified));
}
}
} catch (Throwable var24) {
var5 = var24;
throw var24;
} finally {
if (files != null) {
if (var5 != null) {
try {
opened.close();
} catch (Throwable var21) {
var5.addSuppressed(var21);
}
} else {
files.close();
}
}
}
} catch (DirectoryIteratorException var26) {
throw new ProfileServices.Failure(1015, "Could not continue profile enumeration");
}
}
@Override
public void load(String name) throws IOException {
if (name == null) {
ModuleConfiguration.resetProfile(this.manager);
} else {
ensure(this.directory.getParent());
ensure(this.directory);
Path target = this.path(name);
if (!Files.isRegularFile(target)) {
throw new ProfileServices.Failure(1008, "Profile does not exist");
}
try {
new ConfigStore(target).loadProfile(this.manager);
} catch (ConfigurationFiles.Failure var4) {
throw new ProfileServices.Failure(var4.code, var4.getMessage());
} catch (IOException var5) {
throw new ProfileServices.Failure(5001, var5.getMessage());
}
}
this.manager.configurationSession().profileApplied(name);
}
@Override
public void save(String name, boolean overwrite) throws IOException {
Path target = this.path(name);
try {
new ConfigStore(target).saveProfile(this.manager, overwrite);
} catch (ConfigurationFiles.Failure var5) {
throw new ProfileServices.Failure(var5.code, var5.getMessage());
}
this.manager.configurationSession().profileApplied(name);
}
@Override
public void delete(String name) throws IOException {
if (name == null) {
throw new ProfileServices.Failure(6004, RenderConstants.text(1313637));
} else {
try {
Files.delete(this.path(name));
} catch (IOException var3) {
throw new ProfileServices.Failure(1011, "Could not delete profile");
}
}
}
@Override
public void explore() throws IOException {
if (!Desktop.isDesktopSupported()) {
throw new ProfileServices.Failure(4000, "Directory opener unavailable");
} else {
Desktop.getDesktop().open(this.directory.toFile());
}
}
@Override
public void saveConfiguration() throws IOException {
try {
this.configuration.save(this.manager);
} catch (ConfigurationFiles.Failure var2) {
throw new ProfileServices.Failure(var2.code, var2.getMessage());
}
}
@Override
public synchronized void bind(String name, int key) {
if (key >= 0 && key <= 103) {
ProfileServices.Record found = null;
for (ProfileServices.Record row : this.rows) {
if (ProfileMath.compareNames(row.name, name) == 0) {
found = row;
break;
}
}
if (found == null) {
throw new IllegalArgumentException("Profile is absent from the current directory records");
} else {
found.binding = key;
this.saveBindings();
this.manager.persist();
}
} else {
throw new IllegalArgumentException("Binding ID outside the recovered key table");
}
}
private void saveBindings() {
List<ModuleConfiguration.ProfileBinding> bindings = new ArrayList<>();
for (ProfileServices.Record row : this.rows) {
if (row.binding != 0) {
bindings.add(new ModuleConfiguration.ProfileBinding(row.name, row.binding));
}
}
this.configuration.profileBindings(bindings);
}
@Override
public String current() {
return this.manager.configurationSession().currentProfile();
}
@Override
public boolean pending() {
return this.manager.configurationSession().pending();
}
}
public interface Host {
List<ProfileServices.Record> records() throws IOException;
default List<ProfileServices.Record> snapshot() {
throw new UnsupportedOperationException("Profile snapshot service is required after refresh failure");
}
void load(String var1) throws IOException;
void save(String var1, boolean var2) throws IOException;
void delete(String var1) throws IOException;
void explore() throws IOException;
void saveConfiguration() throws IOException;
void bind(String var1, int var2);
String current();
boolean pending();
}
public static final class Record {
public final String name;
public final String date;
public final long ageSeconds;
public int binding;
public Record(String name, long ageSeconds, int binding) {
this.name = name;
this.ageSeconds = ageSeconds;
this.date = ProfileMath.date(ageSeconds);
this.binding = binding;
}
}
public interface RecordReceiver {
void record(String var1, long var2) throws IOException;
}
}
@@ -0,0 +1,131 @@
package dev.slinky.client.gui.settings;
import dev.slinky.client.gui.GuiModuleList;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import dev.slinky.setting.Setting;
import java.io.IOException;
import java.util.List;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiTextField;
public final class GuiModuleSettings extends GuiScreen {
private final ModuleManager modules;
private final Module module;
private GuiTextField value;
private int page;
private int selected = -1;
private String message = "";
public GuiModuleSettings(ModuleManager modules, Module module) {
this.modules = modules;
this.module = module;
}
public void initGui() {
this.buttonList.clear();
List<Setting<?>> visible = this.module.visibleSettings();
int rows = Math.max(1, (this.height - 120) / 24);
this.page = Math.max(0, Math.min(this.page, Math.max(0, (visible.size() - 1) / rows)));
for (int i = this.page * rows; i < Math.min(visible.size(), (this.page + 1) * rows); i++) {
Setting<?> setting = visible.get(i);
this.buttonList
.add(
new GuiButton(
100 + i,
this.width / 2 - 170,
35 + (i - this.page * rows) * 24,
340,
20,
setting.getLabel() + " = " + (setting.isConfigured() ? setting.format() : "Choose a value")
)
);
}
this.value = new GuiTextField(1, this.fontRendererObj, this.width / 2 - 170, this.height - 64, 270, 20);
this.value.setMaxStringLength(8192);
if (this.selected >= 0 && this.selected < visible.size()) {
this.value.setText(visible.get(this.selected).format());
}
this.buttonList.add(new GuiButton(1, this.width / 2 + 104, this.height - 65, 65, 20, "Save"));
this.buttonList.add(new GuiButton(2, this.width / 2 - 170, this.height - 36, 60, 20, "Back"));
this.buttonList.add(new GuiButton(3, this.width / 2 + 40, this.height - 36, 60, 20, "Previous"));
this.buttonList.add(new GuiButton(4, this.width / 2 + 110, this.height - 36, 60, 20, "Next"));
}
protected void actionPerformed(GuiButton button) throws IOException {
if (button.id >= 100) {
this.selected = button.id - 100;
this.value.setText(this.module.visibleSettings().get(this.selected).format());
this.value.setFocused(true);
} else if (button.id == 1 && this.selected >= 0) {
try {
Setting<?> edited = this.module.visibleSettings().get(this.selected);
edited.parseAndSet(this.value.getText());
this.module.afterSettingControl(edited.getKey(), this.modules.context());
this.modules.persist();
this.message = "Saved";
this.initGui();
this.modules.configurationSession().controlChanged();
} catch (IllegalArgumentException var3) {
this.message = var3.getMessage();
}
} else if (button.id == 2) {
this.mc.displayGuiScreen(new GuiModuleList(this.modules));
} else if (button.id == 3) {
this.page--;
this.selected = -1;
this.initGui();
} else if (button.id == 4) {
this.page++;
this.selected = -1;
this.initGui();
}
}
protected void keyTyped(char character, int key) throws IOException {
if (!this.value.textboxKeyTyped(character, key)) {
super.keyTyped(character, key);
}
}
protected void mouseClicked(int x, int y, int button) throws IOException {
super.mouseClicked(x, y, button);
this.value.mouseClicked(x, y, button);
}
public void drawScreen(int x, int y, float partial) {
this.drawDefaultBackground();
this.drawCenteredString(this.fontRendererObj, this.module.getName(), this.width / 2, 12, 16777215);
List<Setting<?>> visible = this.module.visibleSettings();
boolean changed = false;
for (GuiButton button : this.buttonList) {
button.drawButton(this.mc, x, y);
if (button.id >= 100 && button.id - 100 < visible.size()) {
changed = this.module.afterSettingControl(visible.get(button.id - 100).getKey(), this.modules.context()) || changed;
}
}
if (changed) {
this.modules.persist();
for (GuiButton buttonx : this.buttonList) {
if (buttonx.id >= 100 && buttonx.id - 100 < visible.size()) {
Setting<?> setting = visible.get(buttonx.id - 100);
buttonx.displayString = setting.getLabel() + " = " + (setting.isConfigured() ? setting.format() : "Choose a value");
}
}
}
this.value.drawTextBox();
this.drawString(this.fontRendererObj, this.message, 12, this.height - 90, 16777215);
}
public boolean doesGuiPauseGame() {
return false;
}
}
@@ -0,0 +1,498 @@
package dev.slinky.client.gui.settings;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import dev.slinky.client.gui.GuiMath;
import dev.slinky.client.gui.GuiPresentation;
import dev.slinky.client.gui.GuiWidgets;
import dev.slinky.client.inventory.ItemPolicyService;
import dev.slinky.client.render.DrawPlan;
import dev.slinky.client.render.world.LoadedChunkAccess;
import dev.slinky.inject.Platform;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import dev.slinky.module.catalog.ModuleCatalog;
import dev.slinky.module.combat.WeaponsModule;
import dev.slinky.module.world.GhostHandModule;
import dev.slinky.setting.BooleanSetting;
import dev.slinky.setting.ChoiceSetting;
import dev.slinky.setting.ColorSetting;
import dev.slinky.setting.NamedColorListSetting;
import dev.slinky.setting.NumberSetting;
import dev.slinky.setting.RangeSetting;
import dev.slinky.setting.Setting;
import dev.slinky.setting.StringListSetting;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Map;
import java.util.Set;
public final class InlineModuleSettings {
private static final JsonObject METADATA = readMetadata();
private static final Map<String, List<String>> ORDER = readOrder();
private static final List<String> ITEMS = readItems(false);
private static final List<String> BLOCKS = readItems(true);
private final ModuleManager manager;
private final GuiWidgets widgets;
private final Map<Setting<?>, InlineModuleSettings.Record> records = new IdentityHashMap<>();
private final GuiMath.Toggle sticks = new GuiMath.Toggle();
private final GuiMath.Toggle swordExcluded = new GuiMath.Toggle();
public InlineModuleSettings(ModuleManager manager, GuiPresentation presentation, GuiPresentation.Tooltip tooltip) {
this.manager = manager;
this.widgets = new GuiWidgets(presentation, tooltip, (format, values) -> {
Object[] args = new Object[values.length];
for (int i = 0; i < values.length; i++) {
args[i] = values[i];
}
return String.format(Locale.ROOT, format.replace("%0.", "%."), args);
});
}
private static JsonObject readMetadata() {
try (InputStreamReader reader = new InputStreamReader(Platform.resource("/assets/slinkysemantic/WidgetMetadata.json"), StandardCharsets.UTF_8)) {
return new JsonParser().parse(reader).getAsJsonObject();
} catch (IOException var14) {
throw new ExceptionInInitializerError(var14);
}
}
private static Map<String, List<String>> readOrder() {
Map<String, List<String>> result = new LinkedHashMap<>();
try (InputStreamReader reader = new InputStreamReader(Platform.resource("/assets/slinkysemantic/ModuleUi.json"), StandardCharsets.UTF_8)) {
for (JsonElement element : new JsonParser().parse(reader).getAsJsonObject().getAsJsonArray("modules")) {
JsonObject module = element.getAsJsonObject();
Map<String, Set<String>> edges = new LinkedHashMap<>();
for (JsonElement variant : module.getAsJsonArray("variants")) {
String previous = null;
Map<String, Integer> counts = new HashMap<>();
for (JsonElement control : variant.getAsJsonObject().getAsJsonArray("controls")) {
JsonObject row = control.getAsJsonObject();
String token;
if (row.has("section")) {
String label = row.get("section").getAsString();
int count = counts.containsKey(label) ? counts.get(label) : 0;
counts.put(label, count + 1);
token = "section:" + count + ":" + label;
} else {
token = row.get("key").getAsString();
}
if (!edges.containsKey(token)) {
edges.put(token, new LinkedHashSet<>());
}
if (previous != null) {
edges.get(previous).add(token);
}
previous = token;
}
}
List<String> order = new ArrayList<>();
while (order.size() < edges.size()) {
String next = null;
for (String tokenx : edges.keySet()) {
if (!order.contains(tokenx)) {
boolean incoming = false;
for (Entry<String, Set<String>> edge : edges.entrySet()) {
if (!order.contains(edge.getKey()) && edge.getValue().contains(tokenx)) {
incoming = true;
break;
}
}
if (!incoming) {
next = tokenx;
break;
}
}
}
if (next == null) {
throw new IllegalStateException("Contradictory recovered UI order " + module.get("module").getAsString());
}
order.add(next);
}
result.put(module.get("module").getAsString(), Collections.unmodifiableList(order));
}
return Collections.unmodifiableMap(result);
} catch (IOException var27) {
throw new ExceptionInInitializerError(var27);
}
}
private InlineModuleSettings.Record record(Setting<?> setting) {
InlineModuleSettings.Record record = this.records.get(setting);
if (record == null) {
record = new InlineModuleSettings.Record();
this.records.put(setting, record);
}
return record;
}
private static String text(JsonObject row, String key, String fallback) {
JsonElement value = row.get(key);
return value != null && !value.isJsonNull() ? value.getAsString() : fallback;
}
private static float parameter(JsonObject row, String key) {
JsonElement value = row.get(key);
return value == null ? 0.0F : value.getAsJsonObject().get("value").getAsFloat();
}
private static JsonObject schema(Module module, Setting<?> setting) {
for (JsonElement row : ModuleCatalog.module(module.getName()).getAsJsonArray("settings")) {
JsonObject value = row.getAsJsonObject();
if (value.get("key").getAsString().equals(setting.getKey())) {
return value;
}
}
throw new IllegalStateException("Missing widget schema");
}
private static JsonObject metadata(Module module, Setting<?> setting) {
return METADATA.getAsJsonObject(module.getName()).getAsJsonObject(setting.getKey());
}
public List<String> render(Module module, GuiMath.Runtime state, DrawPlan plan, GuiMath.Layout layout, float[] accent, double now) {
List<String> drawn = new ArrayList<>();
for (String token : ORDER.get(module.getName())) {
ModuleSettingsLayout.Control next = null;
Map<String, Integer> counts = new HashMap<>();
for (ModuleSettingsLayout.Control control : ModuleSettingsLayout.controls(module)) {
String candidate;
if (control.setting == null) {
int count = counts.containsKey(control.section) ? counts.get(control.section) : 0;
counts.put(control.section, count + 1);
candidate = "section:" + count + ":" + control.section;
} else {
candidate = control.setting.getKey();
}
if (token.equals(candidate)) {
next = control;
break;
}
}
if (next != null) {
drawn.add(token);
if (next.setting == null) {
this.widgets.section(plan, layout, next.section);
} else {
Setting<?> setting = next.setting;
if (module instanceof WeaponsModule && setting.getKey().equals("extra_items")) {
WeaponsModule weapons = (WeaponsModule)module;
boolean old = weapons.sticksEnabled();
boolean nextValue = this.widgets.checkbox(state, plan, layout, "Sticks", this.sticks, old, accent, now);
if (nextValue != old) {
weapons.setSticksEnabled(nextValue);
this.changed(module, setting, true);
}
drawn.add("derived:sticks");
}
this.draw(module, setting, state, plan, layout, accent, now);
if (module instanceof GhostHandModule && setting.getKey().equals("require_mouse_pressed")) {
GhostHandModule ghost = (GhostHandModule)module;
boolean old = ghost.swordExcluded();
boolean nextValue = this.widgets.checkbox(state, plan, layout, "Not holding a sword", this.swordExcluded, old, accent, now);
if (nextValue != old) {
ghost.setSwordExcluded(nextValue);
this.changed(module, module.findSetting("allow_using_sword"), true);
}
drawn.add("derived:sword_excluded");
}
}
}
}
return Collections.unmodifiableList(drawn);
}
private void changed(Module module, Setting<?> setting, boolean dirty) {
module.afterSettingControl(setting.getKey(), this.manager.context());
if (dirty) {
this.manager.configurationSession().controlChanged();
}
this.manager.persist();
}
private void draw(Module module, Setting<?> setting, GuiMath.Runtime state, DrawPlan plan, GuiMath.Layout layout, float[] accent, double now) {
JsonObject info = metadata(module, setting);
JsonObject schema = schema(module, setting);
String label = text(info, "label", setting.getLabel());
InlineModuleSettings.Record r = this.record(setting);
boolean dirty = false;
boolean mutated = false;
if (setting instanceof BooleanSetting) {
BooleanSetting value = (BooleanSetting)setting;
boolean next = this.widgets.checkbox(state, plan, layout, label, r.toggle, value.isEnabled(), accent, now);
if (next != value.isEnabled()) {
value.set(Boolean.valueOf(next));
mutated = true;
dirty = true;
}
} else if (setting instanceof NumberSetting) {
NumberSetting value = (NumberSetting)setting;
float next = this.widgets
.number(
state,
plan,
layout,
label,
text(info, "format", null),
r.lower,
r.number,
(float)value.getMinimum(),
(float)value.getMaximum(),
parameter(schema, "step_observation"),
parameter(schema, "extra_ui_parameter_observation"),
value.floatValue(),
accent,
now
);
dirty = r.number.active;
if (Float.floatToRawIntBits(next) != Float.floatToRawIntBits(value.floatValue())) {
value.set(Double.valueOf((double)next));
mutated = true;
}
} else if (setting instanceof RangeSetting) {
RangeSetting value = (RangeSetting)setting;
RangeSetting.Value old = value.get();
JsonObject extra = schema.getAsJsonObject("extra_ui_parameter_observations");
float step = extra == null ? 0.0F : parameter(extra, "0x30");
float scale = extra == null ? 0.0F : parameter(extra, "0x34");
float[] next = this.widgets
.range(
state,
plan,
layout,
label,
text(info, "format", null),
text(info, "pair_format", null),
r.lower,
r.upper,
r.range,
(float)value.minimum(),
(float)value.maximum(),
step,
scale,
(float)old.lower,
(float)old.upper,
accent,
now
);
dirty = r.range.lower.active || r.range.upper.active;
if (next[0] != (float)old.lower || next[1] != (float)old.upper) {
value.set(new RangeSetting.Value((double)next[0], (double)next[1]));
mutated = true;
}
} else if (setting instanceof ChoiceSetting) {
ChoiceSetting value = (ChoiceSetting)setting;
r.selected[0] = value.index();
dirty = (
this.widgets.choice(state, plan, layout, label, r.choice, value.getOptions(), r.selected, info.get("flag").getAsInt(), accent, now, index -> {
value.set(Integer.valueOf(index));
this.changed(module, setting, false);
}) & 0xFF
)
!= 0;
if (r.selected[0] != value.index()) {
value.set(Integer.valueOf(r.selected[0]));
mutated = true;
}
} else if (setting instanceof ColorSetting) {
ColorSetting value = (ColorSetting)setting;
ColorSetting.Value rgb = value.get();
dirty = this.widgets.color(state, plan, layout, label, new float[]{rgb.red, rgb.green, rgb.blue}, nextx -> {
value.set(new ColorSetting.Value(nextx[0], nextx[1], nextx[2]));
this.changed(module, setting, false);
}, now);
} else {
dirty = this.widgets
.list(
state,
plan,
layout,
label,
text(info, "title", label),
filter(module, setting),
setting instanceof NamedColorListSetting ? 1 : 0,
this.list(module, setting),
info.get("suggestions_endpoint_present").getAsBoolean() ? suggestions(module) : null,
now
);
}
boolean coupled = false;
if (!(setting instanceof NamedColorListSetting) && !(setting instanceof StringListSetting) || dirty || mutated) {
coupled = module.afterSettingControl(setting.getKey(), this.manager.context());
}
if (dirty) {
this.manager.configurationSession().controlChanged();
}
if (mutated || coupled) {
this.manager.persist();
}
}
private static GuiMath.Filter filter(Module module, Setting<?> setting) {
return module.getName().equals("Friends") ? (text, ch) -> {
if (text.length() < 16 && (ch >= 97 && ch <= 122 || ch >= 65 && ch <= 90 || ch >= 48 && ch <= 57 || ch == 95)) {
text.append((char)ch);
}
} : GuiMath::catalogFilter;
}
private static List<String> readItems(boolean blocks) {
try (InputStreamReader reader = new InputStreamReader(Platform.resource("/assets/slinkysemantic/ItemCatalog.json"), StandardCharsets.UTF_8)) {
List<String> names = new ArrayList<>();
for (JsonElement element : new JsonParser().parse(reader).getAsJsonArray()) {
JsonObject item = element.getAsJsonObject();
if (!blocks || item.get("id").getAsInt() < 256) {
names.add(item.get("name").getAsString());
}
}
return Collections.unmodifiableList(names);
} catch (IOException var17) {
throw new ExceptionInInitializerError(var17);
}
}
private static List<String> suggestions(Module module) {
return module.getName().equals("Block ESP") ? BLOCKS : ITEMS;
}
private GuiWidgets.ListData list(final Module module, final Setting<?> setting) {
return new GuiWidgets.ListData() {
@Override
public long size() {
return setting instanceof NamedColorListSetting
? (long)((NamedColorListSetting)setting).get().size()
: (long)((StringListSetting)setting).get().size();
}
@Override
public String name(long index) {
return setting instanceof NamedColorListSetting
? ((NamedColorListSetting)setting).get().get((int)index).name
: ((StringListSetting)setting).get().get((int)index);
}
@Override
public float[] color(long index) {
ColorSetting.Value value = ((NamedColorListSetting)setting).get().get((int)index).color;
return new float[]{value.red, value.green, value.blue};
}
@Override
public void color(long index, float[] rgb) {
NamedColorListSetting value = (NamedColorListSetting)setting;
List<NamedColorListSetting.Entry> rows = new ArrayList<>(value.get());
rows.set((int)index, new NamedColorListSetting.Entry(rows.get((int)index).name, new ColorSetting.Value(rgb[0], rgb[1], rgb[2])));
value.set(rows);
InlineModuleSettings.this.changed(module, setting, false);
}
@Override
public void add(String name, float[] rgb) {
if (setting instanceof NamedColorListSetting) {
int id = module.getName().equals("Block ESP") ? LoadedChunkAccess.blockIdentifier(name) : ItemPolicyService.identifier(name);
if (id < 0) {
return;
}
NamedColorListSetting value = (NamedColorListSetting)setting;
List<NamedColorListSetting.Entry> rows = new ArrayList<>(value.get());
for (NamedColorListSetting.Entry row : rows) {
if (row.name.equals(name)) {
return;
}
}
rows.add(new NamedColorListSetting.Entry(name, new ColorSetting.Value(rgb[0], rgb[1], rgb[2])));
rows.sort((a, b) -> Integer.compare(ItemPolicyService.identifier(a.name), ItemPolicyService.identifier(b.name)));
value.set(rows);
} else {
StringListSetting value = (StringListSetting)setting;
List<String> rows = new ArrayList<>(value.get());
if (rows.contains(name)) {
return;
}
rows.add(name);
value.set(rows);
}
InlineModuleSettings.this.changed(module, setting, false);
}
@Override
public void remove(long index) {
if (setting instanceof NamedColorListSetting) {
NamedColorListSetting value = (NamedColorListSetting)setting;
List<NamedColorListSetting.Entry> rows = new ArrayList<>(value.get());
rows.remove((int)index);
value.set(rows);
} else {
StringListSetting value = (StringListSetting)setting;
List<String> rows = new ArrayList<>(value.get());
rows.remove((int)index);
value.set(rows);
}
InlineModuleSettings.this.changed(module, setting, false);
}
};
}
private static final class Record {
final Object lower = new Object();
final Object upper = new Object();
final GuiMath.Toggle toggle = new GuiMath.Toggle();
final GuiMath.Slider number = new GuiMath.Slider();
final GuiWidgets.Range range = new GuiWidgets.Range();
final GuiWidgets.Choice choice = new GuiWidgets.Choice();
final int[] selected = new int[]{0};
private Record() {
}
}
}
@@ -0,0 +1,120 @@
package dev.slinky.client.gui.settings;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import dev.slinky.inject.Platform;
import dev.slinky.module.Module;
import dev.slinky.setting.BooleanSetting;
import dev.slinky.setting.ChoiceSetting;
import dev.slinky.setting.Setting;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public final class ModuleSettingsLayout {
private static final Map<String, JsonObject> LAYOUTS = read();
private ModuleSettingsLayout() {
}
private static Map<String, JsonObject> read() {
InputStream stream = Platform.resource("/assets/slinkysemantic/ModuleUi.json");
if (stream == null) {
throw new IllegalStateException("Missing recovered UI layouts");
} else {
try (Reader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) {
Map<String, JsonObject> result = new LinkedHashMap<>();
for (JsonElement row : new JsonParser().parse(reader).getAsJsonObject().getAsJsonArray("modules")) {
JsonObject layout = row.getAsJsonObject();
String name = layout.get("module").getAsString();
if (result.put(name, layout) != null) {
throw new IllegalStateException("Duplicate UI layout " + name);
}
}
return Collections.unmodifiableMap(result);
} catch (IOException var18) {
throw new IllegalStateException(var18);
}
}
}
public static List<ModuleSettingsLayout.Control> controls(Module module) {
JsonObject layout = LAYOUTS.get(module.getName());
if (layout == null) {
throw new IllegalStateException("Missing module UI " + module.getName());
} else {
JsonArray gates = layout.getAsJsonArray("gates");
for (JsonElement row : layout.getAsJsonArray("variants")) {
JsonObject variant = row.getAsJsonObject();
JsonArray values = variant.getAsJsonArray("values");
boolean match = true;
for (int i = 0; i < gates.size(); i++) {
Setting<?> setting = module.findSetting(gates.get(i).getAsString());
int value;
if (setting instanceof BooleanSetting) {
value = ((BooleanSetting)setting).isEnabled() ? 1 : 0;
} else {
if (!(setting instanceof ChoiceSetting)) {
throw new IllegalStateException("Invalid recovered UI gate");
}
value = ((ChoiceSetting)setting).index();
}
if (value != values.get(i).getAsInt()) {
match = false;
break;
}
}
if (match) {
List<ModuleSettingsLayout.Control> result = new ArrayList<>();
for (JsonElement element : variant.getAsJsonArray("controls")) {
JsonObject control = element.getAsJsonObject();
if (control.has("section")) {
result.add(new ModuleSettingsLayout.Control(control.get("section").getAsString(), null));
} else {
String key = control.get("key").getAsString();
Setting<?> settingx = module.findSetting(key);
if (settingx == null) {
throw new IllegalStateException("Missing recovered UI setting " + module.getName() + "." + key);
}
if (module.settingVisible(key)) {
result.add(new ModuleSettingsLayout.Control(null, settingx));
}
}
}
return Collections.unmodifiableList(result);
}
}
throw new IllegalStateException("No recovered UI variant for " + module.getName());
}
}
public static final class Control {
public final String section;
public final Setting<?> setting;
private Control(String section, Setting<?> setting) {
this.section = section;
this.setting = setting;
}
}
}
@@ -0,0 +1,362 @@
package dev.slinky.client.hook;
import dev.slinky.client.MinecraftFields;
import dev.slinky.client.event.SemanticEvent;
import dev.slinky.client.inventory.InvWalkControllerInputAdapter;
import dev.slinky.client.packet.NetworkSendAdapter;
import dev.slinky.client.packet.PacketPipeline;
import dev.slinky.client.placement.PlacementSelection;
import dev.slinky.client.render.hook.RenderHooks;
import dev.slinky.client.rotation.RotationService;
import dev.slinky.client.target.RayQueryAdapter;
import dev.slinky.client.target.TargetCombatStatus;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import dev.slinky.module.movement.SafeWalkModule;
import dev.slinky.module.render.NoHurtCamModule;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.Packet;
import net.minecraft.util.MovementInput;
import net.minecraft.world.World;
public final class GameHooks {
private static ModuleManager modules;
private static SemanticEvent ray;
private static double previousDisplaySeconds;
private static final TargetCombatStatus combat = new TargetCombatStatus();
private static boolean uiDrawn;
private static boolean baseSwing;
private GameHooks() {
}
public static void install(ModuleManager manager) {
modules = manager;
RenderHooks.install(manager);
}
private static boolean local(Entity entity) {
return modules != null && entity == Minecraft.getMinecraft().thePlayer;
}
public static boolean mouseAngles(Entity entity, float yaw, float pitch) {
return local(entity) && RotationService.mouseAngles(entity, modules.context(), yaw, pitch);
}
public static void beginMovement() {
if (modules != null) {
RotationService.beginMovement(modules.context());
}
}
public static void endMovement() {
if (modules != null) {
RotationService.endMovement(modules.context());
}
}
public static void updateMovement(MovementInput input) {
beginMovement();
try {
input.updatePlayerMoveState();
} finally {
endMovement();
}
}
public static void beginCamera() {
if (modules != null) {
RotationService.beginCamera(modules.context());
}
}
public static void endCamera() {
RotationService.endCamera();
}
public static void endRender() {
RotationService.endRender();
}
public static void beginRenderInfo() {
if (modules != null) {
RotationService.beginRenderInfo(modules.context());
}
}
public static void endRenderInfo() {
RotationService.endRenderInfo();
}
public static float renderViewPitch(float vanilla) {
return RotationService.renderViewPitch(vanilla);
}
public static float renderViewYaw(float vanilla) {
return RotationService.renderViewYaw(vanilla);
}
public static void beginTick() {
if (modules != null) {
combat.select(modules.context(), modules);
} else {
combat.reset();
}
}
public static EntityPlayer target() {
return combat.target();
}
public static double targetDistanceSquared() {
return combat.distanceSquared();
}
public static void beforeEntities(World world) {
if (modules != null && world == modules.context().world()) {
modules.dispatch(new SemanticEvent(16));
beginMovement();
modules.dispatch(new SemanticEvent(17));
}
}
public static void afterEntities(World world) {
if (modules != null && world == modules.context().world()) {
modules.dispatch(new SemanticEvent(18));
if (!modules.context().ready()) {
endMovement();
} else {
SemanticEvent event = new SemanticEvent(13);
event.active = false;
event.yaw = RotationService.storedRotationValid() ? RotationService.cameraYaw() : modules.context().player().rotationYaw;
event.pitch = RotationService.storedRotationValid() ? RotationService.cameraPitch() : modules.context().player().rotationPitch;
modules.dispatch(event);
RotationService.finishFrame(modules.context(), event);
RotationService.applyPending(modules.context(), true);
}
}
}
public static void abortEntities(World world) {
if (modules != null && world == modules.context().world()) {
endMovement();
}
}
public static SemanticEvent beforeMoveFlying(Entity player, float sideways, float forward, float friction) {
SemanticEvent event = new SemanticEvent(21);
event.sideways = sideways;
event.forward = forward;
event.factor = friction;
if (!local(player)) {
return event;
} else {
event.jump = MinecraftFields.jumpTicks((EntityPlayerSP)player) > 0 && modules.context().minecraft().gameSettings.keyBindJump.isKeyDown();
modules.dispatch(event);
return event;
}
}
public static void afterMoveFlying(Entity player, SemanticEvent before) {
if (local(player)) {
SemanticEvent event = new SemanticEvent(22);
event.sideways = before.sideways;
event.forward = before.forward;
event.factor = before.factor;
event.jump = before.jump;
modules.dispatch(event);
}
}
public static boolean safeWalk(Entity entity) {
if (local(entity)) {
Module module = modules.find("safewalk");
if (module instanceof SafeWalkModule && ((SafeWalkModule)module).allowed()) {
return true;
}
}
return entity.isSneaking();
}
public static void beginRay(float partialTicks) {
ray = null;
if (modules != null && modules.context().ready()) {
RotationService.beginFrame(modules.context(), 0.0, partialTicks);
beginTick();
modules.dispatch(new SemanticEvent(0));
if (modules.context().minecraft().inGameHasFocus) {
ray = new SemanticEvent(8);
ray.distance = 3.0;
modules.dispatch(ray);
}
}
}
public static void afterFrameRender() {
if (modules != null) {
uiDrawn = true;
RenderHooks.beforeDisplay();
}
}
public static strictfp void beforeDisplay() {
if (modules != null) {
if (!uiDrawn) {
RenderHooks.beforeDisplay();
}
uiDrawn = false;
modules.applyPendingChanges();
beforeDisplay((double)System.nanoTime() / 1.0E9);
}
}
private static strictfp void beforeDisplay(double now) {
if (previousDisplaySeconds == 0.0) {
previousDisplaySeconds = now;
}
if (!modules.context().ready()) {
previousDisplaySeconds = now;
} else {
RotationService.beforeDisplay(modules.context());
double elapsed = now - previousDisplaySeconds;
if (!(0.002 > elapsed)) {
SemanticEvent frame = new SemanticEvent(1);
frame.elapsedSeconds = elapsed;
frame.timerRenderPartialTicks = MinecraftFields.timer(modules.context().minecraft()).renderPartialTicks;
modules.dispatch(frame);
RotationService.applyPending(modules.context(), false);
previousDisplaySeconds = now;
RotationService.afterDisplay(modules.context());
}
}
}
public static boolean customRay() {
boolean selected = ray != null && RayQueryAdapter.update(modules, ray);
if (ray != null) {
PlacementSelection.frameReset();
}
return selected;
}
public static double entityReach(double vanilla) {
return ray != null && ray.enabled ? ray.distance : vanilla;
}
public static float queryReach(float vanilla) {
return ray != null && ray.enabled ? (float)Math.max((double)vanilla, ray.distance) : vanilla;
}
public static float hitboxBorder(float vanilla) {
return ray == null ? vanilla : (float)((double)vanilla + ray.expand);
}
public static boolean beforeInteraction(boolean use) {
if (modules == null) {
return false;
} else {
if (!use) {
combat.prepare(modules.context());
}
SemanticEvent event = combat.event(14, use);
modules.dispatch(event);
if (use) {
PlacementSelection.capture(modules.context());
}
return event.cancelled;
}
}
public static void afterInteraction(boolean use) {
if (modules != null) {
modules.dispatch(combat.event(15, use));
if (use) {
PlacementSelection.consume(modules.context());
}
}
}
public static void baseSwing(EntityPlayerSP player) {
baseSwing = true;
try {
player.swingItem();
} finally {
baseSwing = false;
}
}
public static boolean baseSwingOnly() {
return baseSwing;
}
public static void clear() {
ray = null;
combat.clear();
previousDisplaySeconds = 0.0;
try {
RenderHooks.clear();
} finally {
try {
PlacementSelection.reset();
} finally {
try {
RotationService.reset(null);
} finally {
PacketPipeline.releaseAll(false);
}
}
}
}
public static void shutdown() {
if (modules != null) {
modules.shutdown();
}
PacketPipeline.releaseAll(true);
}
public static boolean beforeSend(NetworkManager receiver, Packet<?> packet) {
if (!NetworkSendAdapter.consumeDirect(receiver, packet) && modules != null) {
SemanticEvent event = new SemanticEvent(23);
event.receiver = receiver;
event.packet = packet;
modules.dispatch(event);
return event.cancelled;
} else {
return false;
}
}
public static void updateController() {
if (modules != null) {
SemanticEvent event = new SemanticEvent(25);
modules.dispatch(event);
if (event.cancelled) {
InvWalkControllerInputAdapter.drain();
}
}
}
public static void preRender() {
if (modules != null) {
RotationService.prepareMouse(modules.context());
Module module = modules.find("No Hurt Cam");
if (module != null && module.isEnabled()) {
((NoHurtCamModule)module).applyPreRender(modules.context());
}
}
}
}
@@ -0,0 +1,17 @@
package dev.slinky.client.input;
import dev.slinky.client.ClientContext;
import org.lwjgl.input.Mouse;
public final class AttackInput {
private AttackInput() {
}
public static int button(int attackCode) {
return attackCode == -99 ? 1 : 0;
}
public static boolean pressed(ClientContext context) {
return Mouse.isButtonDown(button(context.minecraft().gameSettings.keyBindAttack.getKeyCode()));
}
}
@@ -0,0 +1,75 @@
package dev.slinky.client.input;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Map;
public final class BindingInput {
private final byte[] keyBits = new byte[104];
private final Map<Module, Boolean> requested = new IdentityHashMap<>();
public boolean update(int nativeId, boolean down, boolean toggle) {
return this.update(nativeId, down ? 1 : 0, toggle ? 1 : 0);
}
public boolean update(int nativeId, int requestedByte, int toggleByte) {
if (nativeId >= 0 && nativeId < this.keyBits.length) {
int old = this.keyBits[nativeId] & 255;
int bit = old & 1;
int requested = requestedByte & 0xFF;
int next = requested;
boolean unchanged = true;
if ((toggleByte & 0xFF) != 1 || bit != requested) {
if ((toggleByte & 0xFF) == 1) {
next = (requested | old & 2) ^ 2;
}
this.keyBits[nativeId] = (byte)next;
unchanged = bit == requested;
}
return unchanged;
} else {
throw new IllegalArgumentException("Binding ID outside the recovered key table");
}
}
public boolean dispatch(ModuleManager manager, int nativeId, boolean down) {
if (nativeId == 0) {
return false;
} else {
boolean accepted = false;
for (Module module : manager.all()) {
if (module.getBinding() == nativeId) {
if (module.isHoldBinding()) {
manager.requestEnabled(module, down);
this.requested.put(module, down);
accepted = true;
} else if (down) {
manager.requestEnabled(module, !module.isEnabled());
}
accepted = true;
}
}
return accepted;
}
}
public boolean lastRequested(Module module) {
return Boolean.TRUE.equals(this.requested.get(module));
}
public int bits(int nativeId) {
return this.keyBits[nativeId] & 0xFF;
}
public void clear() {
Arrays.fill(this.keyBits, (byte)0);
this.requested.clear();
}
}
@@ -0,0 +1,74 @@
package dev.slinky.client.input;
import java.util.Objects;
public final class InputQuery {
private final BindingInput keys;
public InputQuery(BindingInput keys) {
this.keys = Objects.requireNonNull(keys);
}
public int query(InputQuery.Request request, InputQuery.Host host) {
boolean unchanged = true;
if (request.type == 2) {
unchanged = this.keys.update(request.key, request.requested, request.toggle);
}
if (!host.environmentAvailable()) {
return 0;
} else {
int result = host.dispatch(request, unchanged);
if ((result & 0xFF) != 0 || request.type != 2 || unchanged || request.key == 0) {
return result;
} else if (host.ready() && !host.guiEnabled()) {
if (host.friendsEnabled() && request.type == 2 && request.requested == 1 && request.key == 3) {
host.friendAction();
}
if (!host.moduleBindings(request.key, request.requested)) {
host.profileBindings(request.key, request.requested);
}
return result;
} else {
return result;
}
}
}
public interface Host {
boolean environmentAvailable();
int dispatch(InputQuery.Request var1, boolean var2);
boolean ready();
boolean guiEnabled();
boolean friendsEnabled();
void friendAction();
boolean moduleBindings(int var1, byte var2);
void profileBindings(int var1, byte var2);
}
public static final class Request {
public int type;
public int key;
public int x;
public int y;
public byte requested;
public byte toggle;
public float wheel;
public byte[] utf8 = new byte[0];
public Request(int type, int key, boolean down) {
this.type = type;
this.key = key;
this.requested = (byte)(down ? 1 : 0);
}
}
}
@@ -0,0 +1,146 @@
package dev.slinky.client.input;
import dev.slinky.client.event.SemanticEvent;
import dev.slinky.client.gui.profile.ProfileServices;
import dev.slinky.client.render.RenderEventPayload;
import dev.slinky.config.ConfigStore;
import dev.slinky.config.ModuleConfiguration;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import dev.slinky.module.render.GuiModule;
import dev.slinky.module.utility.FriendsModule;
import java.io.IOException;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class InputRouter implements InputQuery.Host {
private final ModuleManager modules;
private final BindingInput keys = new BindingInput();
private final InputQuery query = new InputQuery(this.keys);
private ConfigStore configuration;
private ProfileServices.Host profiles;
public InputRouter(ModuleManager modules) {
this.modules = modules;
}
public void profiles(ConfigStore configuration, ProfileServices.Host profiles) {
this.configuration = Objects.requireNonNull(configuration);
this.profiles = Objects.requireNonNull(profiles);
}
public ProfileServices.Host profiles() {
if (this.profiles == null) {
throw new IllegalStateException("Profile file service has not been installed");
} else {
return this.profiles;
}
}
public int input(InputQuery.Request request) {
try {
return this.query.query(request, this);
} catch (IllegalArgumentException var3) {
this.report(var3);
return 0;
}
}
@Override
public boolean environmentAvailable() {
return this.modules.context().minecraft() != null;
}
@Override
public int dispatch(InputQuery.Request request, boolean unchanged) {
Module candidate = this.modules.find("GUI");
return !(candidate instanceof GuiModule)
? 0
: (int)((GuiModule)candidate)
.dispatchInput(request.type, request.key, request.requested == 1, unchanged ? 1 : 0, request.utf8, request.x, request.y, request.wheel);
}
@Override
public boolean ready() {
return this.modules.context().minecraft().inGameHasFocus;
}
@Override
public boolean guiEnabled() {
Module gui = this.modules.find("GUI");
return gui != null && gui.isEnabled();
}
@Override
public boolean friendsEnabled() {
Module friends = this.modules.find("Friends");
return friends != null && friends.isEnabled();
}
@Override
public void friendAction() {
Module friends = this.modules.find("Friends");
if (friends instanceof FriendsModule) {
((FriendsModule)friends).middleClickAction(this.modules.context(), this.modules);
}
}
@Override
public boolean moduleBindings(int key, byte requested) {
boolean accepted = this.keys.dispatch(this.modules, key, requested == 1);
if (accepted) {
this.modules.persist();
}
return accepted;
}
@Override
public void profileBindings(int key, byte requested) {
if (requested == 1) {
if (this.configuration == null) {
throw new IllegalStateException("Configuration service has not been installed");
} else {
for (ModuleConfiguration.ProfileBinding binding : this.configuration.profileBindings()) {
if (binding.binding == key) {
try {
this.profiles().load(binding.name);
SemanticEvent event = new SemanticEvent(11);
RenderEventPayload payload = RenderEventPayload.attach(event);
payload.action = 0;
payload.profileName = binding.name;
try {
this.modules.dispatch(event);
} finally {
RenderEventPayload.detach(event);
}
} catch (IOException var11) {
this.report(var11);
}
}
}
}
}
}
public void clear() {
this.keys.clear();
}
private void report(Exception failure) {
SemanticEvent event = new SemanticEvent(12);
RenderEventPayload payload = RenderEventPayload.attach(event);
payload.errorCode = failure instanceof ProfileServices.Failure ? ((ProfileServices.Failure)failure).code : 1;
payload.message = failure.getMessage();
try {
this.modules.dispatch(event);
} finally {
RenderEventPayload.detach(event);
}
Logger.getLogger("SlinkySemantic").log(Level.WARNING, "Input action could not be applied", (Throwable)failure);
}
}
@@ -0,0 +1,64 @@
package dev.slinky.client.input;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import dev.slinky.inject.Platform;
import dev.slinky.inject.ReflectionHelper;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Field;
import net.minecraft.client.settings.KeyBinding;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
public final class KeyState {
private static final Field PRESSED = ReflectionHelper.findField(KeyBinding.class, "pressed", "field_74513_e");
private static final String[] NAMES = readNames();
private KeyState() {
}
private static String[] readNames() {
InputStream stream = Platform.resource("/assets/slinkysemantic/KeyNames.json");
if (stream == null) {
throw new IllegalStateException("Missing recovered key labels");
} else {
try (Reader reader = new InputStreamReader(stream, "UTF-8")) {
JsonArray rows = new JsonParser().parse(reader).getAsJsonArray();
String[] result = new String[rows.size()];
for (int i = 0; i < result.length; i++) {
result[i] = rows.get(i).getAsString();
}
return result;
} catch (IOException var17) {
throw new IllegalStateException(var17);
}
}
}
public static String displayName(int binding) {
int index = binding - 1;
return Integer.compareUnsigned(index, 102) > 0 ? NAMES[0] : NAMES[binding];
}
public static boolean physical(KeyBinding key) {
int code = key.getKeyCode();
return code < 0 ? Mouse.isButtonDown(code + 100) : code <= 256 && Keyboard.isKeyDown(code);
}
public static void restore(KeyBinding key) {
set(key, physical(key));
}
public static void set(KeyBinding key, boolean value) {
try {
PRESSED.setBoolean(key, value);
} catch (IllegalAccessException var3) {
throw new IllegalStateException("Cannot set key pressed state", var3);
}
}
}
@@ -0,0 +1,34 @@
package dev.slinky.client.input;
import dev.slinky.module.ModuleManager;
import java.nio.charset.StandardCharsets;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
public final class MinecraftInputAdapter {
private MinecraftInputAdapter() {
}
public static int keyboard(ModuleManager modules) {
InputQuery.Request request = new InputQuery.Request(2, NativeKeyMap.keyboardId(Keyboard.getEventKey()), Keyboard.getEventKeyState());
request.utf8 = String.valueOf(Keyboard.getEventCharacter()).getBytes(StandardCharsets.UTF_8);
return modules.input().input(request);
}
public static int mouse(ModuleManager modules) {
int button = Mouse.getEventButton();
InputQuery.Request request;
if (button >= 0) {
request = new InputQuery.Request(2, NativeKeyMap.mouseId(button), Mouse.getEventButtonState());
} else if (Mouse.getEventDWheel() != 0) {
request = new InputQuery.Request(3, 0, false);
request.wheel = (float)Mouse.getEventDWheel();
} else {
request = new InputQuery.Request(1, 0, false);
}
request.x = Mouse.getEventX();
request.y = modules.context().minecraft().displayHeight - Mouse.getEventY() - 1;
return modules.input().input(request);
}
}
@@ -0,0 +1,107 @@
package dev.slinky.client.input;
import org.lwjgl.input.Keyboard;
public final class NativeKeyMap {
private static final int[] CODES = createCodes();
private NativeKeyMap() {
}
private static int[] createCodes() {
int[] result = new int[104];
for (int id = 1; id <= 5; id++) {
result[id] = id - 101;
}
for (int digit = 0; digit < 10; digit++) {
result[6 + digit] = Keyboard.getKeyIndex(Integer.toString(digit));
result[16 + digit] = Keyboard.getKeyIndex("NUMPAD" + digit);
}
for (int letter = 0; letter < 26; letter++) {
result[26 + letter] = Keyboard.getKeyIndex(Character.toString((char)(65 + letter)));
}
String[] punctuation = new String[]{
"SEMICOLON", "EQUALS", "COMMA", "MINUS", "PERIOD", "SLASH", "GRAVE", "LBRACKET", "BACKSLASH", "RBRACKET", "APOSTROPHE"
};
for (int index = 0; index < punctuation.length; index++) {
result[52 + index] = Keyboard.getKeyIndex(punctuation[index]);
}
String[] arrows = new String[]{"LEFT", "UP", "RIGHT", "DOWN"};
for (int index = 0; index < arrows.length; index++) {
result[63 + index] = Keyboard.getKeyIndex(arrows[index]);
}
for (int function = 1; function <= 16; function++) {
result[66 + function] = Keyboard.getKeyIndex("F" + function);
}
String[] controls = new String[]{
"BACK",
"TAB",
"RETURN",
"CAPITAL",
"ESCAPE",
"SPACE",
"PRIOR",
"NEXT",
"END",
"HOME",
"SYSRQ",
"INSERT",
"DELETE",
"NUMLOCK",
"SCROLL",
"LSHIFT",
"RSHIFT",
"LCONTROL",
"RCONTROL",
"LMENU",
"RMENU"
};
for (int index = 0; index < controls.length; index++) {
result[83 + index] = Keyboard.getKeyIndex(controls[index]);
}
return result;
}
public static boolean supported(int nativeId) {
return nativeId > 0 && nativeId < CODES.length && CODES[nativeId] != 0;
}
public static int gameCode(int nativeId) {
return supported(nativeId) ? CODES[nativeId] : 0;
}
public static int keyboardId(int code) {
if (code <= 0) {
return 0;
} else if (code == 156) {
return 85;
} else {
for (int id = 6; id < CODES.length; id++) {
if (CODES[id] == code) {
return id;
}
}
return 0;
}
}
public static int mouseId(int button) {
return button >= 0 && button < 5 ? button + 1 : 0;
}
public static int capturedId(int nativeId) {
return nativeId != 1 && nativeId != 87 ? nativeId : 0;
}
}
@@ -0,0 +1,140 @@
package dev.slinky.client.inventory;
import dev.slinky.client.ClientContext;
import dev.slinky.client.action.ModuleActionService;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.item.ItemStack;
public interface ConsumableAccess {
boolean ready();
float health();
float maximumHealth();
boolean usingItem();
int current();
int type(int var1);
int damage(int var1);
void remember();
void select(int var1);
void use();
void drop();
boolean claim();
void release();
void disable();
public static final class Live implements ConsumableAccess {
private final Module module;
private final ModuleManager manager;
private InventoryPlayer savedInventory;
private int savedSlot;
public ClientContext context;
public Live(Module module, ModuleManager manager) {
this.module = module;
this.manager = manager;
}
@Override
public boolean ready() {
return this.context.inputReady();
}
@Override
public float health() {
return this.context.player().getHealth();
}
@Override
public float maximumHealth() {
return this.context.player().getMaxHealth();
}
@Override
public boolean usingItem() {
return this.context.player().isUsingItem();
}
@Override
public int current() {
return this.context.player().inventory.currentItem;
}
@Override
public int type(int slot) {
return InventoryActions.type(this.context.player().inventory.getStackInSlot(slot));
}
@Override
public int damage(int slot) {
ItemStack s = this.context.player().inventory.getStackInSlot(slot);
return s == null ? 0 : s.getItemDamage();
}
@Override
public void remember() {
this.savedInventory = this.context.player().inventory;
this.savedSlot = this.savedInventory.currentItem;
}
@Override
public void select(int slot) {
this.context.player().inventory.currentItem = slot;
if (this.context.player().inventory == this.savedInventory && slot == this.savedSlot) {
this.savedInventory = null;
}
}
@Override
public void use() {
InventoryActions.press(this.context.minecraft().gameSettings.keyBindUseItem);
}
@Override
public void drop() {
InventoryActions.press(this.context.minecraft().gameSettings.keyBindDrop);
}
@Override
public boolean claim() {
return ModuleActionService.claim(this.module);
}
@Override
public void release() {
ModuleActionService.release(this.module);
}
@Override
public void disable() {
this.manager.requestEnabled(this.module, false);
}
public void cleanup() {
if (this.savedInventory != null) {
this.savedInventory.currentItem = this.savedSlot;
this.savedInventory = null;
}
this.release();
this.context = null;
}
public void forget() {
this.savedInventory = null;
}
}
}
@@ -0,0 +1,29 @@
package dev.slinky.client.inventory;
import dev.slinky.client.rotation.RotationMath;
import dev.slinky.client.rotation.RotationRandom;
public final strictfp class ConsumableDelay {
private final RotationRandom.Smooth first = new RotationRandom.Smooth();
private final RotationRandom.Smooth gate = new RotationRandom.Smooth();
public strictfp float switching(float base, double now, RotationRandom random) {
float result = RotationMath.max(0.0F, base - this.first.sample(0.0F, 0.5F, 5.0, now, random));
if (this.gate.gate(0.01F, 0.03F, 30.0, now, random)) {
result += random.interval(0.5F, 1.0F);
}
return result;
}
public strictfp float soup(float base, float scale, double now, RotationRandom random) {
float a = this.first.sample(0.0F, 0.5F, 60.0, now, random);
float b = this.first.sample(0.0F, 0.5F, 5.0, now, random);
float sum = b + a;
if (this.gate.gate(0.03F, 0.06F, 60.0, now, random)) {
sum += random.interval(0.5F, 2.5F);
}
return sum * scale + base;
}
}
@@ -0,0 +1,17 @@
package dev.slinky.client.inventory;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
public final class InvWalkControllerInputAdapter {
private InvWalkControllerInputAdapter() {
}
public static void drain() {
while (Mouse.next()) {
}
while (Keyboard.next()) {
}
}
}
@@ -0,0 +1,90 @@
package dev.slinky.client.inventory;
import dev.slinky.inject.ReflectionHelper;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.inventory.GuiChest;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.InventoryBasic;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
public final class InventoryActions {
private static final Field PRESS_COUNT = ReflectionHelper.findField(KeyBinding.class, "pressTime", "field_151474_i");
private static final Method CLICK = ReflectionHelper.findMethod(
GuiContainer.class, null, new String[]{"handleMouseClick", "func_146984_a"}, Slot.class, int.class, int.class, int.class
);
private static final Method KEY = ReflectionHelper.findMethod(GuiScreen.class, null, new String[]{"keyTyped", "func_73869_a"}, char.class, int.class);
private static final Field CHEST_INVENTORY = ReflectionHelper.findField(GuiChest.class, "lowerChestInventory", "field_147015_w");
private static final Field INVENTORY_TITLE = ReflectionHelper.findField(InventoryBasic.class, "inventoryTitle", "field_70483_a");
private InventoryActions() {
}
public static int type(ItemStack stack) {
return stack != null && stack.getItem() != null ? Item.getIdFromItem(stack.getItem()) : 0;
}
public static void press(KeyBinding key) {
try {
PRESS_COUNT.setInt(key, PRESS_COUNT.getInt(key) + 1);
} catch (IllegalAccessException var2) {
throw new IllegalStateException("Cannot increment key press count", var2);
}
}
public static void pressCount(KeyBinding key, int count) {
try {
PRESS_COUNT.setInt(key, count);
} catch (IllegalAccessException var3) {
throw new IllegalStateException("Cannot set key press count", var3);
}
}
public static void click(GuiContainer screen, int slot, int button, int mode) {
invoke(CLICK, screen, null, slot, button, mode);
}
public static void escape(GuiScreen screen) {
invoke(KEY, screen, '\u0000', 1);
}
public static String chestTitle(GuiChest screen) {
try {
IInventory inventory = (IInventory)CHEST_INVENTORY.get(screen);
return inventory instanceof InventoryBasic ? (String)INVENTORY_TITLE.get(inventory) : null;
} catch (IllegalAccessException var2) {
throw new IllegalStateException("Cannot read chest title", var2);
}
}
private static void invoke(Method method, Object target, Object... args) {
try {
method.invoke(target, args);
} catch (ReflectiveOperationException var4) {
throw new IllegalStateException("Minecraft inventory action failed", var4);
}
}
public static int truncateDouble(double value) {
return !Double.isNaN(value) && !(value >= 2.1474836E9F) && !(value < -2.1474836E9F) ? (int)value : Integer.MIN_VALUE;
}
public static long truncateDoubleLong(double value) {
return !Double.isNaN(value) && !(value >= 9.223372E18F) && !(value < -9.223372E18F) ? (long)value : Long.MIN_VALUE;
}
public static float[] grid(int slot) {
long index = Integer.toUnsignedLong(slot);
if (index <= 8L) {
return new float[]{(float)Integer.toUnsignedLong(slot - 5), 1.0F};
} else {
int quotient = (int)(index * 954437177L >>> 33);
return new float[]{(float)Integer.toUnsignedLong(quotient + 3), (float)(slot - quotient * 9)};
}
}
}
@@ -0,0 +1,437 @@
package dev.slinky.client.inventory;
import dev.slinky.client.numeric.NumericMath;
import java.util.Arrays;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagList;
public final strictfp class InventorySelection {
private InventorySelection() {
}
public static strictfp long material(int type) {
int category = 0;
int tier = 0;
switch (type) {
case 256:
category = 16;
tier = 3;
break;
case 257:
category = 14;
tier = 3;
break;
case 258:
category = 1;
tier = 3;
break;
case 267:
category = 22;
tier = 3;
break;
case 268:
category = 22;
tier = 1;
break;
case 269:
category = 16;
tier = 1;
break;
case 270:
category = 14;
tier = 1;
break;
case 271:
category = 1;
tier = 1;
break;
case 272:
category = 22;
tier = 2;
break;
case 273:
category = 16;
tier = 2;
break;
case 274:
category = 14;
tier = 2;
break;
case 275:
category = 1;
tier = 2;
break;
case 276:
category = 22;
tier = 5;
break;
case 277:
category = 16;
tier = 5;
break;
case 278:
category = 14;
tier = 5;
break;
case 279:
category = 1;
tier = 5;
break;
case 283:
category = 22;
tier = 4;
break;
case 284:
category = 16;
tier = 4;
break;
case 285:
category = 14;
tier = 4;
break;
case 286:
category = 1;
tier = 4;
break;
case 359:
category = 15;
tier = 1;
}
return (long)tier << 32 | (long)category;
}
public static strictfp int weighted(int[][] tags, int table) {
int total = 0;
for (int[] tag : tags) {
int id = tag[0] & 65535;
int level = tag[1];
int coefficient = 0;
if (table == 0) {
coefficient = id == 16 ? 50 : (id != 19 && id != 20 ? 0 : 1);
} else if (table == 1) {
coefficient = id == 48 ? 100 : (id == 49 ? 10 : (id != 50 && id != 51 ? 0 : 1));
} else {
coefficient = id == 16 ? 1 : (id == 32 ? 10 : (id == 62 ? 83 : 0));
}
if ((short)level >= 0) {
total += coefficient * level;
}
}
return total;
}
public static strictfp int category(int category, InventorySelection.Stack s) {
if (s != null && category >= 1 && category <= 26) {
int item = s.type;
int damage = s.damage;
switch (category) {
case 2:
switch (item) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 17:
case 18:
case 20:
case 24:
case 35:
case 45:
case 49:
case 78:
case 87:
case 95:
case 98:
case 121:
case 125:
case 133:
case 155:
case 159:
case 162:
return 1;
default:
return 0;
}
case 3:
return item == 261 ? weighted(s.tags, 1) + 1 & 65535 : 0;
case 4:
return item == 54 ? 1 : 0;
case 5:
return item == 368 ? (s.flag ? 1 : 2) : 0;
case 6:
return item == 385 ? 1 : 0;
case 7:
return item == 346 ? 1 : 0;
case 8:
return item == 259 ? 1 : 0;
case 9:
return item == 322 ? (damage == 1 ? 2 : 1) : 0;
case 10:
case 11:
case 19:
case 21:
if (item != 373) {
return 0;
} else {
int effect = damage & 15;
int score;
if (effect == 1 && category == 10) {
score = 1;
} else if (effect == 5 && category == 10) {
score = 2;
} else {
if ((effect != 2 || category != 19) && (effect != 9 || category != 21) && (effect != 11 || category != 11)) {
return 0;
}
score = 1;
}
return score + (damage >>> 6 & 1) + (damage >>> 5 & 1);
}
case 12:
return item == 65 ? 1 : 0;
case 13:
return item == 327 ? 1 : 0;
case 14:
case 15:
case 16:
case 22:
case 25:
default:
long classified = material(item);
int kind = (int)classified;
int tier = (int)(classified >>> 32);
if (category != 22 && category != 25) {
return category == kind ? weighted(s.tags, 2) + tier * 100 & 65535 : 0;
} else {
if (category == 22 && kind != 22) {
return 0;
}
int preference = category == 22 ? 4 : (kind == 1 ? 3 : (kind == 14 ? 2 : (kind == 16 ? 1 : (kind == 22 ? 4 : 0))));
int material = new int[]{0, 0, 1, 2, 0, 3}[tier];
return weighted(s.tags, 0) + (preference + material) * 40 & 65535;
}
case 17:
return item != 332 && item != 344 ? 0 : 1;
case 18:
return item != 413 && item != 282 ? 0 : 1;
case 20:
return item == 280 ? 1 : 0;
case 23:
return item == 46 ? 1 : 0;
case 24:
return item == 326 ? 1 : 0;
case 26:
return item == 35 ? 1 : 0;
}
} else {
return 0;
}
}
public static strictfp int policy(int category, InventorySelection.Stack s, boolean fallback) {
int score = category(category, s);
if (!fallback) {
return score;
} else if (score != 0) {
return score << 16;
} else {
return category == 22 ? category(25, s) : (category == 17 ? category(7, s) : (category == 7 ? category(17, s) : 0));
}
}
public static strictfp int armor(InventorySelection.Stack s, int slot) {
if (s == null) {
return 0;
} else {
int index = s.type - 298;
if (index >= 0 && index < 20 && index % 4 == slot) {
int material = new int[]{0, 2, 3, 4, 1}[index / 4];
int base = new int[]{1, 3, 2, 1, 2, 5, 3, 1, 2, 5, 4, 1, 2, 6, 5, 2, 3, 8, 6, 3}[material * 4 + slot];
float other = 0.0F;
float protection = 0.0F;
for (int[] tag : s.tags) {
int id = tag[0] & 65535;
int level = (short)tag[1];
if (level >= 0) {
if (id >= 1 && id <= 4) {
other = (float)((double)level * 0.1 + (double)other);
} else if (id == 0) {
int quantized = (int)InventoryActions.truncateDoubleLong((double)(level * level + 6) * 0.75 / 3.0);
if (Integer.compareUnsigned(quantized, 25) >= 0) {
quantized = 25;
}
protection = (float)((quantized >>> 1) + (quantized + 1 & 62)) * 0.5F;
}
}
}
double enchantments = (double)(other + protection);
float combined = (float)(1.0 - (1.0 - enchantments * 0.04) * (1.0 - (double)base * 0.04));
return (int)NumericMath.truncateLong(combined * 1000.0F);
} else {
return 0;
}
}
}
public static strictfp InventorySelection.Action planArmor(InventorySelection.Stack[] main, InventorySelection.Stack equipped, int armor) {
int bestScore = armor(equipped, armor);
int best = -1;
for (int slot = 0; slot < 36; slot++) {
int score = armor(main[slot], armor);
if (Integer.compareUnsigned(bestScore, score) < 0) {
bestScore = score;
best = slot;
}
}
if (best == -1) {
return null;
} else if (equipped == null) {
return new InventorySelection.Action(0, best < 9 ? best + 36 : best, 0);
} else {
return best < 9 ? new InventorySelection.Action(1, armor + 5, best) : new InventorySelection.Action(2, best, armor + 5);
}
}
public static strictfp InventorySelection.Action planHotbar(InventorySelection.Stack[] main, int destination, int category, boolean fallback, long processed) {
if (category == 0) {
return null;
} else {
InventorySelection.Stack original = main[destination];
int originalCount = original == null ? 0 : original.count;
int max = original == null ? 0 : original.maximum;
int originalScore = policy(category, original, fallback);
long identity = original == null ? 0L : original.identity();
boolean empty = false;
for (int i = 9; i < 36; i++) {
if (main[i] == null) {
empty = true;
break;
}
}
if (originalCount <= 0) {
originalCount = 0;
}
int bestScore = originalCount * originalScore;
int best = destination;
int mergeFlag = empty ? 0 : 36;
for (int slot = 0; slot < 36; slot++) {
InventorySelection.Stack candidate = main[slot];
if (slot != destination && (processed >>> slot & 1L) == 0L && candidate != null) {
int count = candidate.count;
int merged = 0;
if (originalCount < max && count < max && candidate.identity() == identity && original != null && original.sameTags(candidate)) {
int combined = count + originalCount;
if (combined >= max) {
combined = max;
}
merged = combined * originalScore;
}
int score = policy(category, candidate, fallback) * count;
boolean merge = Integer.compareUnsigned(score - 1, merged) < 0;
if (merge) {
score = merged;
}
if (Integer.compareUnsigned(bestScore, score) < 0) {
if (slot < 9 && !empty && merge) {
mergeFlag &= 255;
} else {
best = slot;
bestScore = score;
mergeFlag = merge ? 1 : 0;
}
}
}
}
return best == destination ? null : new InventorySelection.Action(~mergeFlag & 1, best < 9 ? best + 36 : best, (mergeFlag & 1) != 0 ? 0 : destination);
}
}
public static strictfp long processedBit(int scan) {
return (long)(1 << (scan & 31));
}
public static final strictfp class Action {
public int kind;
public int source;
public int destination;
public Action(int kind, int source, int destination) {
this.kind = kind;
this.source = source;
this.destination = destination;
}
}
public static final strictfp class Stack {
public final int type;
public final int damage;
public final int count;
public final int maximum;
public final boolean flag;
public final int[][] tags;
public final ItemStack source;
public Stack(int type, int damage, int count, int maximum, boolean flag, int[][] tags) {
this(type, damage, count, maximum, flag, tags, null);
}
private Stack(int type, int damage, int count, int maximum, boolean flag, int[][] tags, ItemStack source) {
this.type = type;
this.damage = damage;
this.count = count;
this.maximum = maximum;
this.flag = flag;
this.tags = tags;
this.source = source;
}
public static strictfp InventorySelection.Stack of(ItemStack stack) {
if (stack == null) {
return null;
} else {
NBTTagList nbt = stack.getEnchantmentTagList();
int[][] tags = new int[nbt == null ? 0 : nbt.tagCount()][2];
for (int i = 0; i < tags.length; i++) {
tags[i][0] = nbt.getCompoundTagAt(i).getShort("id") & '\uffff';
tags[i][1] = nbt.getCompoundTagAt(i).getShort("lvl");
}
return new InventorySelection.Stack(
InventoryActions.type(stack), stack.getItemDamage(), stack.stackSize, stack.getMaxStackSize(), stack.hasDisplayName(), tags, stack
);
}
}
public strictfp long identity() {
return (long)this.type << 32 | (long)this.damage;
}
public strictfp boolean sameTags(InventorySelection.Stack other) {
return this.source == null && other.source == null
? Arrays.deepEquals(this.tags, other.tags)
: ItemStack.areItemStackTagsEqual(this.source, other.source);
}
}
}
@@ -0,0 +1,85 @@
package dev.slinky.client.inventory;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import dev.slinky.inject.Platform;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
public final class ItemPolicyService {
private static final Map<String, Integer> IDENTIFIERS = new LinkedHashMap<>();
private static final Map<Integer, String> NAMES = new LinkedHashMap<>();
private ItemPolicyService() {
}
public static int identifier(String canonicalName) {
Integer id = IDENTIFIERS.get(canonicalName);
return id == null ? -1 : id;
}
public static String name(int identifier) {
return NAMES.get(identifier);
}
public static boolean contains(List<String> canonicalNames, int identifier) {
for (String name : canonicalNames) {
int resolved = identifier(name);
if (resolved != -1 && resolved == identifier) {
return true;
}
}
return false;
}
public static List<String> canonicalize(List<String> input) {
TreeSet<Integer> ids = new TreeSet<>(new Comparator<Integer>() {
public int compare(Integer a, Integer b) {
return a - b;
}
});
for (String name : input) {
int id = identifier(name);
if (id != -1) {
ids.add(id);
}
}
List<String> result = new ArrayList<>();
for (Integer id : ids) {
result.add(name(id));
}
return Collections.unmodifiableList(result);
}
static {
try (Reader reader = new InputStreamReader(Platform.resource("/assets/slinkysemantic/ItemCatalog.json"), "UTF-8")) {
for (JsonElement e : new JsonParser().parse(reader).getAsJsonArray()) {
JsonObject row = e.getAsJsonObject();
String name = row.get("name").getAsString();
int id = row.get("id").getAsInt();
IDENTIFIERS.put(name, id);
NAMES.put(id, name);
}
if (IDENTIFIERS.size() != 385) {
throw new IllegalStateException("Incomplete recovered item catalog");
}
} catch (IOException var17) {
throw new ExceptionInInitializerError(var17);
}
}
}
@@ -0,0 +1,103 @@
package dev.slinky.client.inventory;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.client.rotation.RotationMath;
public final strictfp class RefillSelection {
private RefillSelection() {
}
public static strictfp int classify(int type, int damage, boolean soups, boolean pots, boolean nonHealth) {
if (type == 0) {
return 0;
} else if (type == 282) {
return soups ? 2 : 1;
} else if (type == 373 && pots && (damage & 16384) != 0) {
return (damage & 15) != 5 && !nonHealth ? 1 : 2;
} else {
return 1;
}
}
public static strictfp RefillSelection.Result choose(byte[] mask, int previous, float angle, RefillSelection.Gates gates) {
float x = (float)(previous % 9);
float y = (float)(previous / 9);
float bestScore = Float.MAX_VALUE;
float secondScore = Float.MAX_VALUE;
float emptyScore = Float.MAX_VALUE;
float bestBearing = 0.0F;
float secondBearing = 0.0F;
float emptyBearing = 0.0F;
int best = -1;
int second = -1;
int empty = -1;
for (int slot = 0; slot < 27; slot++) {
int mark = mask[slot] & 255;
if (mark == 2 || mark == 0 && !gates.skipEmpty()) {
float dx = (float)(slot % 9) - x;
float dy = (float)(slot / 9) - y;
float distance = NumericMath.floatDistance(dx, dy);
float bearing = RotationMath.atan2(dy, dx);
float angular = RotationMath.abs(RotationMath.wrap(bearing - angle)) * 0.5F;
float score = distance * 1.2F + angular;
if (mark == 0) {
if (!(emptyScore < score)) {
emptyBearing = bearing;
}
if (!(score > emptyScore)) {
empty = slot;
}
emptyScore = RotationMath.min(emptyScore, score);
} else {
boolean replaces = !(score > bestScore);
if (replaces) {
secondScore = bestScore;
second = best;
}
if (!(bestScore < score)) {
secondBearing = bestBearing;
}
if (replaces) {
bestBearing = bearing;
best = slot;
}
bestScore = RotationMath.min(bestScore, score);
}
}
}
if (bestScore > emptyScore && gates.chooseEmpty()) {
return new RefillSelection.Result(empty, emptyScore, emptyBearing);
} else {
return second != -1 && (double)bestScore * 1.5 > (double)secondScore && gates.chooseSecond()
? new RefillSelection.Result(second, secondScore, secondBearing)
: new RefillSelection.Result(best, bestScore, bestBearing);
}
}
public interface Gates {
boolean skipEmpty();
boolean chooseEmpty();
boolean chooseSecond();
}
public static final strictfp class Result {
public final int slot;
public final float score;
public final float bearing;
Result(int slot, float score, float bearing) {
this.slot = slot;
this.score = score;
this.bearing = bearing;
}
}
}
@@ -0,0 +1,208 @@
package dev.slinky.client.numeric;
public final strictfp class DoubleNumeric {
private DoubleNumeric() {
}
private static strictfp long c(int address) {
return NumericConstants.longAt(address);
}
private static strictfp double d(int address) {
return NumericConstants.doubleAt(address);
}
public static strictfp long upperProduct(long a, long b) {
long al = a & 4294967295L;
long bl = b & 4294967295L;
long ah = a >>> 32;
long bh = b >>> 32;
long low = al * bl;
long left = ah * bl;
long right = al * bh;
long middle = (low >>> 32) + (left & 4294967295L) + (right & 4294967295L);
return ah * bh + (left >>> 32) + (right >>> 32) + (middle >>> 32);
}
public static strictfp double invalid(double input) {
long bits = Double.doubleToRawLongBits(input);
return Double.longBitsToDouble(Long.compareUnsigned(bits & Long.MAX_VALUE, 9218868437227405312L) > 0 ? bits | 2251799813685248L : -2251799813685248L);
}
public static strictfp double sqrt(double input) {
long bits = Double.doubleToRawLongBits(input);
long exponent = bits >>> 52;
if (Long.compareUnsigned(exponent - 2047L, -2046L) < 0) {
if (input == 0.0 || bits == 9218868437227405312L) {
return input;
}
if (Long.compareUnsigned(bits, 9218868437227405312L) > 0) {
return invalid(input);
}
input *= d(2864952);
bits = Double.doubleToRawLongBits(input);
exponent = (bits >>> 52) - 52L;
}
long significand = bits << 11 | Long.MIN_VALUE;
if ((exponent & 1L) != 0L) {
significand = bits << 10 & 9223372036854774784L;
}
long reciprocal = (long)NumericConstants.shortAt(2875200 + 2 * (int)(bits >>> 46 & 127L)) << 16;
long root = (significand >>> 32) * reciprocal >>> 32;
long square = root * reciprocal >>> 32;
int correction = -1073741824 - (int)square;
reciprocal = reciprocal * ((long)correction & 4294967295L) >>> 31 & 4294967294L;
root = root * ((long)correction & 4294967295L) >>> 31 & 4294967294L;
correction = -1073741824 - (int)(root * reciprocal >>> 32);
long inverse = 2L * ((long)correction & 4294967295L) * reciprocal & -8589934592L;
root = upperProduct(inverse, significand);
root = upperProduct(root, -4611686018427387904L - upperProduct(root, inverse));
root = root - 2L >>> 9;
significand <<= 42;
long residual = root * root - significand + root;
long rounded = root + (residual >>> 63);
long test = root + residual;
long result = 2303591209400008704L + (exponent << 51) & -4503599627370496L | rounded & 4503599627370495L;
double value = Double.longBitsToDouble(result);
if (++test != 0L) {
long adjustment = 4503599627370496L | (test ^ residual) & Long.MIN_VALUE;
value += Double.longBitsToDouble(adjustment);
}
return value;
}
public static strictfp double scale(double value, int amount) {
long bits = Double.doubleToRawLongBits(value);
int stored = (int)(bits >>> 52 & 2047L);
if (stored == 2047) {
return value;
} else {
long sign = bits & Long.MIN_VALUE;
long doubled = bits * 2L;
int high = doubled != 0L ? 63 - Long.numberOfLeadingZeros(doubled) : 127;
int effective = stored != 0 ? stored : (doubled != 0L ? high - 52 : -53);
if (amount >= 0) {
if (amount > 2046 - effective) {
return Double.longBitsToDouble(bits & c(2863232) | c(2863248));
} else {
int adjusted = effective + amount;
if (adjusted <= 0) {
return Double.longBitsToDouble(bits << amount | sign);
} else if (effective > 0) {
return Double.longBitsToDouble(bits + ((long)amount << 52));
} else {
long fraction = bits << 1 - effective & 4503599627370495L;
return Double.longBitsToDouble(fraction | (long)adjusted << 52 | sign);
}
}
} else {
int positive = -amount;
if (amount > -effective) {
return Double.longBitsToDouble(bits - (((long)positive & 4294967295L) << 52));
} else if (amount < -52 - effective) {
return Double.longBitsToDouble(sign);
} else {
int sum = amount + effective;
int shift = (Integer.compareUnsigned(positive, 1 - sum) < 0 ? positive : 1 - sum) - 1 & 63;
boolean tie = Long.numberOfTrailingZeros(bits) == shift;
long significand = bits & 4503599627370495L | (effective > 0 ? 4503599627370496L : 0L);
long rounded = (significand >>> shift) + 1L >>> 1;
rounded &= ~(tie ? 1L : 0L);
return Double.longBitsToDouble(bits & c(2863232) | rounded & c(2863216));
}
}
}
}
public static strictfp double floor(double value) {
long bits = Double.doubleToRawLongBits(value);
long exponent = bits >>> 52 & 2047L;
if (exponent < 1075L && value != 0.0) {
double positive = d(2868888);
double negative = d(2868896);
double minusOne = d(2868904);
boolean below = bits < 0L;
double correction = below ? positive + (negative + value) - value : positive + value + negative - value;
if (exponent <= 1022L) {
return below ? minusOne : 0.0;
} else {
double integer = value + correction;
return correction > 0.0 ? integer + minusOne : integer;
}
} else {
return value;
}
}
public static strictfp double remainder(double first, double second) {
long x = Double.doubleToRawLongBits(first);
long y = Double.doubleToRawLongBits(second);
long sign = x & Long.MIN_VALUE;
long dividend = x & Long.MAX_VALUE;
long divisor = y & Long.MAX_VALUE;
int dividendExponent = (int)(dividend >>> 52);
int divisorExponent = (int)(divisor >>> 52);
if (divisor != 0L && divisor <= 9218868437227405312L && dividendExponent != 2047) {
if (dividend <= divisor) {
return dividend == divisor ? Double.longBitsToDouble(sign) : first;
} else {
if (dividendExponent != 0) {
dividend = dividend & 4503599627370495L | 4503599627370496L;
} else {
for (dividendExponent = 1; dividend < 4503599627370496L; dividendExponent--) {
dividend <<= 1;
}
}
if (divisorExponent != 0) {
divisor = divisor & 4503599627370495L | 4503599627370496L;
} else {
for (divisorExponent = 1; divisor < 4503599627370496L; divisorExponent--) {
divisor <<= 1;
}
}
while (dividendExponent > divisorExponent) {
if (dividend >= divisor) {
dividend -= divisor;
if (dividend == 0L) {
return Double.longBitsToDouble(sign);
}
}
dividend <<= 1;
dividendExponent--;
}
if (dividend >= divisor) {
dividend -= divisor;
if (dividend == 0L) {
return Double.longBitsToDouble(sign);
}
}
while (dividend < 4503599627370496L) {
dividend <<= 1;
dividendExponent--;
}
if (dividendExponent > 0) {
dividend = dividend - 4503599627370496L | (long)dividendExponent << 52;
} else {
dividend >>>= 1 - dividendExponent;
}
return Double.longBitsToDouble(dividend | sign);
}
} else if (dividend > 9218868437227405312L) {
return invalid(first);
} else {
return divisor > 9218868437227405312L ? invalid(second) : Double.longBitsToDouble(-2251799813685248L);
}
}
}
@@ -0,0 +1,208 @@
package dev.slinky.client.numeric;
public final strictfp class DoubleTrigonometry {
private DoubleTrigonometry() {
}
private static strictfp double c(int address) {
return NumericConstants.doubleAt(address);
}
private static strictfp int exponent(double value) {
return (int)(Double.doubleToRawLongBits(value) >>> 52 & 2047L);
}
private static strictfp double negate(double value, int address) {
return Double.longBitsToDouble(Double.doubleToRawLongBits(value) ^ NumericConstants.longAt(address));
}
private static strictfp void direct(double value, double[] output, int head, int tail) {
double first = value + c(head);
double correction = c(tail);
output[0] = first + correction;
output[1] = first - output[0] + correction;
}
public static strictfp int reduce(double value, double[] output) {
long bits = Double.doubleToRawLongBits(value);
int high = (int)(bits >>> 32) & 2147483647;
boolean negative = bits < 0L;
boolean nearbyCancellation = false;
if (high <= 1074752122) {
if (((int)(bits >>> 32) & 1048575) != 598523) {
if (high <= 1073928572) {
direct(value, output, negative ? 2864800 : 2864616, negative ? 2864624 : 2864792);
return negative ? -1 : 1;
}
direct(value, output, negative ? 2864776 : 2864760, negative ? 2864784 : 2864768);
return negative ? -2 : 2;
}
} else if (high < 1075594812) {
if (high < 1075183037) {
if (high != 1074977148) {
direct(value, output, negative ? 2864744 : 2864728, negative ? 2864752 : 2864736);
return negative ? -3 : 3;
}
} else if (high != 1075388923) {
direct(value, output, negative ? 2864712 : 2864696, negative ? 2864720 : 2864704);
return negative ? -4 : 4;
}
nearbyCancellation = true;
} else if (high >= 1094263291) {
if (high >= 2146435072) {
output[0] = DoubleNumeric.invalid(value);
output[1] = output[0];
return 0;
}
double normalized = Double.longBitsToDouble(bits & 4503599627370495L | 4710765210229538816L);
double[] chunks = new double[]{(double)NumericMath.truncateInt(normalized), 0.0, 0.0};
double fraction = (normalized - chunks[0]) * c(2864560);
chunks[1] = (double)NumericMath.truncateInt(fraction);
chunks[2] = (fraction - chunks[1]) * c(2864560);
int count = 3;
while (chunks[count - 1] == 0.0) {
count--;
}
double[] residual = new double[2];
int quadrant = LargeReduction.reduce(chunks, residual, (high >>> 20) - 1046, count, 1);
if (negative) {
output[0] = negate(residual[0], 2864576);
output[1] = negate(residual[1], 2864584);
return -quadrant;
}
output[0] = residual[0];
output[1] = residual[1];
return quadrant;
}
double multiple = c(2864592) * value;
multiple += c(2864600);
multiple += c(2864608);
int quadrant = NumericMath.truncateInt(multiple);
double remainder = c(2864616) * multiple + value;
double tail = c(2864624) * multiple;
double first = remainder - tail;
boolean adjust = false;
if (c(2864632) > first) {
quadrant--;
multiple += c(2864656);
adjust = true;
} else if (first > c(2864640)) {
quadrant++;
multiple += c(2864648);
adjust = true;
}
if (adjust) {
remainder = value + c(2864616) * multiple;
tail = c(2864624) * multiple;
}
output[0] = remainder - tail;
int inputExponent = high >>> 20;
boolean secondStage = nearbyCancellation ? exponent(output[0]) < 1009 : inputExponent - exponent(output[0]) > 16;
if (secondStage) {
double second = c(2864664) * multiple;
double reduced = remainder - second;
tail = c(2864672) * multiple - (remainder - reduced - second);
output[0] = reduced - tail;
boolean thirdStage = nearbyCancellation ? (Double.doubleToRawLongBits(output[0]) >>> 56 & 127L) <= 60L : inputExponent - exponent(output[0]) >= 50;
remainder = reduced;
if (thirdStage) {
double third = c(2864680) * multiple;
double reducedThird = reduced - third;
tail = multiple * c(2864688) - (reduced - reducedThird - third);
remainder = reducedThird;
output[0] = reducedThird - tail;
}
}
output[1] = remainder - output[0] - tail;
return quadrant;
}
private static strictfp double sineKernel(double x, double y) {
double z = x * x;
double cube = z * z;
double polynomial = c(2866896) * z + c(2866904);
double low = c(2866912) * z + c(2866928);
double high = polynomial * z + c(2866936);
cube *= z;
cube *= low;
double sum = high + cube;
double xz = x * z;
double correction = c(2866864) * y;
sum *= xz;
correction -= sum;
correction *= z;
correction -= y;
xz *= c(2866944);
xz += correction;
return x - xz;
}
private static strictfp double cosineKernel(double x, double tail) {
double z = x * x;
double fourth = z * z;
double pLow = c(2866800) * z;
double pHigh = c(2866808) * z;
double lower = c(2866816) + pLow;
double upper = c(2866840) - pHigh;
lower = lower * z + c(2866848);
upper = upper * z + c(2866856);
fourth *= fourth;
double lowProduct = z * lower;
double highProduct = fourth * upper;
double polynomial = highProduct + lowProduct;
double halfZ = c(2866864) * z;
double one = c(2866872);
double head = one - halfZ;
double correction = one - head - halfZ;
polynomial *= z;
x *= tail;
polynomial -= x;
polynomial += correction;
return polynomial + head;
}
public static strictfp double sine(double value) {
int high = (int)(Double.doubleToRawLongBits(value) >>> 32) & 2147483647;
if (high <= 1072243195) {
if (high < 1045430272) {
return value;
} else {
double z = value * value;
double cube = z * z * z;
double low = z * c(2866960) + c(2866976);
double highTerm = z * c(2866968) + c(2866984);
double lowTerm = cube * low;
double upper = z * highTerm;
double sum = upper + c(2866992);
sum += lowTerm;
double xz = value * z;
sum *= z;
sum += c(2867000);
sum *= xz;
return value + sum;
}
} else if (high > 2146435071) {
return DoubleNumeric.invalid(value);
} else {
double[] residual = new double[2];
int quadrant = reduce(value, residual) & 3;
if ((quadrant & 1) == 0) {
double result = sineKernel(residual[0], residual[1]);
return quadrant == 2 ? negate(result, 2866880) : result;
} else {
double result = cosineKernel(residual[0], residual[1]);
return quadrant == 3 ? negate(result, 2866880) : result;
}
}
}
}
@@ -0,0 +1,23 @@
package dev.slinky.client.numeric;
import java.math.BigInteger;
public final class FixedWidth {
private static final BigInteger TWO_64 = BigInteger.ONE.shiftLeft(64);
private FixedWidth() {
}
private static BigInteger unsigned(long value) {
BigInteger integer = BigInteger.valueOf(value);
return value < 0L ? integer.add(TWO_64) : integer;
}
public static long unsignedMultiplyHigh(long left, long right) {
return unsigned(left).multiply(unsigned(right)).shiftRight(64).longValue();
}
public static long signedMultiplyHigh(long left, long right) {
return BigInteger.valueOf(left).multiply(BigInteger.valueOf(right)).shiftRight(64).longValue();
}
}
@@ -0,0 +1,314 @@
package dev.slinky.client.numeric;
public final strictfp class FloatNumeric {
private FloatNumeric() {
}
private static strictfp float c(int address) {
return NumericConstants.floatAt(address);
}
private static strictfp long unsigned(int value) {
return (long)value & 4294967295L;
}
public static strictfp float log(float input) {
int bits = Float.floatToRawIntBits(input);
int offset;
if (bits < 8388608) {
if (input == 0.0F) {
return c(2868468) / (input * input);
}
if (bits < 0) {
return invalid(input);
}
float value = input * c(2868464);
bits = Float.floatToRawIntBits(value);
offset = -152;
} else {
if (Integer.compareUnsigned(bits, 2139095039) > 0) {
return input;
}
if (bits == 1065353216) {
return 0.0F;
}
offset = -127;
}
bits += 4913933;
int exponent = (bits >>> 23) + offset;
bits = (bits & 8388607) + 1060439283;
float reduced = Float.intBitsToFloat(bits) + c(2868468);
float denominator = c(2868472) + reduced;
float ratio = reduced / denominator;
float square = ratio * ratio;
float fourth = square * square;
float low = fourth * c(2868480);
low += c(2868496);
float high = fourth * c(2868484);
high += c(2868500);
low = square * low;
high = fourth * high;
float sum = high + low;
float halfSquare = c(2868512) * reduced;
halfSquare *= reduced;
sum += halfSquare;
sum *= ratio;
float exponentFloat = (float)exponent;
float correction = c(2868516) * exponentFloat;
correction += sum;
correction -= halfSquare;
correction += reduced;
float result = exponentFloat * c(2868520);
return result + correction;
}
public static strictfp float invalid(float input) {
int bits = Float.floatToRawIntBits(input);
return Float.intBitsToFloat((bits & 2147483647) > 2139095040 ? bits | 4194304 : -4194304);
}
public static strictfp float sqrt(float input) {
int bits = Float.floatToRawIntBits(input);
if (Integer.compareUnsigned(bits - 2139095040, -2130706432) < 0) {
if (input == 0.0F || bits == 2139095040) {
return input;
}
if (Integer.compareUnsigned(bits, 2139095041) >= 0) {
return invalid(input);
}
input *= c(2864960);
bits = Float.floatToRawIntBits(input) + -192937984;
}
int significand = bits << 8 | -2147483648;
if ((bits & 8388608) != 0) {
significand = bits << 7 & 2147483520;
}
int exponent = (bits >>> 1) + 532676608 & 2139095040;
long reciprocal = (long)NumericConstants.shortAt(2875200 + (bits >>> 16 & 254)) << 16;
long root = unsigned(significand) * reciprocal >>> 32;
long square = root * reciprocal >>> 32;
int correction = -1073741824 - (int)square;
reciprocal = unsigned((int)(reciprocal * unsigned(correction) >>> 31) & -2);
root = unsigned((int)(root * unsigned(correction) >>> 31) & -2);
correction = -1073741824 - (int)(reciprocal * root >>> 32);
int integer = (int)(unsigned(correction) * root >>> 32) - 1 >>> 6;
significand <<= 16;
int residual = integer * integer - significand + integer;
int rounded = integer + (residual >>> 31);
int test = integer + residual;
float value = Float.intBitsToFloat(exponent | rounded & 8388607);
test++;
int adjustment = (test == 0 ? 0 : 16777216) | (test ^ residual) & -2147483648;
return value + Float.intBitsToFloat(adjustment);
}
public static strictfp float scale(float value, int power) {
int bits = Float.floatToRawIntBits(value);
int magnitude = bits & 2147483647;
if (magnitude >= 2139095040) {
return value;
} else {
int sign = bits & -2147483648;
int doubled = bits << 1;
int exponent = doubled >>> 24;
if (Integer.compareUnsigned(doubled, 16777216) < 0) {
int highest = doubled != 0 ? 31 - Integer.numberOfLeadingZeros(doubled) : 63;
exponent = (highest ^ -32) + 9;
}
if (power >= 0) {
if (power > 254 - exponent) {
return Float.intBitsToFloat(sign | NumericConstants.intAt(2869408));
}
} else if (power <= -exponent) {
if (power < -23 - exponent) {
return Float.intBitsToFloat(sign);
}
int shift = Math.min(-power, 1 - (power + exponent)) - 1 & 31;
int tie = Integer.numberOfTrailingZeros(bits) == shift ? 1 : 0;
int significand = bits & 8388607 | (exponent > 0 ? 8388608 : 0);
int rounded = (significand >>> shift) + 1 >>> 1;
return Float.intBitsToFloat(sign | rounded & ~tie & 8388607);
}
int nextExponent = power + exponent;
if (nextExponent <= 0) {
return Float.intBitsToFloat(bits << power | sign);
} else if (exponent > 0) {
return Float.intBitsToFloat(bits + (power << 23));
} else {
int fraction = bits << 1 - exponent & 8388607;
return Float.intBitsToFloat(nextExponent << 23 | fraction | sign);
}
}
}
public static strictfp float exp(float input) {
int bits = Float.floatToRawIntBits(input);
int magnitude = bits & 2147483647;
int negative = bits >>> 31;
if (magnitude > 2139095040) {
return input;
} else {
if (magnitude > 1118743631) {
if (negative == 0 && magnitude >= 1118925336) {
return input * c(2869328);
}
if (negative != 0 && magnitude > 1120924084) {
return 0.0F;
}
}
int power = 0;
float high = input;
float low = 0.0F;
float reduced = input;
if (magnitude > 1051816472) {
if (magnitude <= 1065686418) {
power = negative != 0 ? -1 : 1;
} else {
float multiple = c(2869340) * input;
multiple += c(2875456 + negative * 4);
power = NumericMath.truncateInt(multiple);
}
float converted = (float)power;
high = input + c(2869344) * converted;
low = converted * c(2869348);
reduced = high - low;
} else if (magnitude <= 956301312) {
return input + c(2869332);
}
float square = reduced * reduced;
float correction = c(2869352) * square;
correction += c(2869356);
correction *= square;
float denominatorCorrection = reduced - correction;
float result = reduced * denominatorCorrection;
result /= c(2869360) - denominatorCorrection;
result -= low;
result += high;
result += c(2869332);
return power != 0 ? scale(result, power) : result;
}
}
public static strictfp float round(float input) {
int bits = Float.floatToRawIntBits(input);
int exponent = bits >>> 23 & 0xFF;
if (exponent >= 150) {
return input;
} else {
float magnitude = Float.intBitsToFloat(bits & NumericConstants.intAt(2867584));
float delta = c(2867600) + magnitude;
if (exponent <= 125) {
return input * 0.0F;
} else {
delta += c(2867604);
delta -= magnitude;
if (delta > c(2867608)) {
magnitude += delta;
magnitude += c(2867620);
} else {
magnitude += delta;
if (c(2867612) >= delta) {
magnitude += c(2867616);
}
}
return Float.intBitsToFloat(Float.floatToRawIntBits(magnitude) ^ (bits < 0 ? NumericConstants.intAt(2867632) : 0));
}
}
}
public static strictfp float truncate(float input) {
int bits = Float.floatToRawIntBits(input);
int exponent = bits >>> 23 & 0xFF;
if (exponent >= 150) {
return input;
} else {
int shift = exponent >= 127 ? exponent - 118 : 1;
int mask = -1 >>> (shift & 31);
return Float.intBitsToFloat(bits & ~mask);
}
}
public static strictfp float remainder(float first, float second) {
int x = Float.floatToRawIntBits(first);
int y = Float.floatToRawIntBits(second);
int sign = x & -2147483648;
int dividend = x & 2147483647;
int divisor = y & 2147483647;
int dividendExponent = dividend >>> 23;
int divisorExponent = divisor >>> 23;
if (divisor != 0 && divisor <= 2139095040 && dividendExponent != 255) {
if (dividend <= divisor) {
return dividend == divisor ? Float.intBitsToFloat(sign) : first;
} else {
if (dividendExponent != 0) {
dividend = dividend & 8388607 | 8388608;
} else {
for (dividendExponent = 1; dividend < 8388608; dividendExponent--) {
dividend <<= 1;
}
}
if (divisorExponent != 0) {
divisor = divisor & 8388607 | 8388608;
} else {
for (divisorExponent = 1; divisor < 8388608; divisorExponent--) {
divisor <<= 1;
}
}
while (dividendExponent > divisorExponent) {
if (dividend >= divisor) {
dividend -= divisor;
if (dividend == 0) {
return Float.intBitsToFloat(sign);
}
}
dividend <<= 1;
dividendExponent--;
}
if (dividend >= divisor) {
dividend -= divisor;
if (dividend == 0) {
return Float.intBitsToFloat(sign);
}
}
while (dividend < 8388608) {
dividend <<= 1;
dividendExponent--;
}
if (dividendExponent > 0) {
dividend = dividend - 8388608 | dividendExponent << 23;
} else {
dividend >>>= 1 - dividendExponent;
}
return Float.intBitsToFloat(dividend | sign);
}
} else if (dividend > 2139095040) {
return invalid(first);
} else {
return divisor > 2139095040 ? invalid(second) : Float.intBitsToFloat(-4194304);
}
}
}
@@ -0,0 +1,165 @@
package dev.slinky.client.numeric;
public final strictfp class FloatTrigonometry {
private FloatTrigonometry() {
}
private static strictfp double c(int address) {
return NumericConstants.doubleAt(address);
}
private static strictfp float sign(float value, int address) {
return Float.intBitsToFloat(Float.floatToRawIntBits(value) ^ NumericConstants.intAt(address));
}
private static strictfp float sineKernel(double x, int coefficients, int sign, boolean negative) {
double z = x * x;
double squared = z * z;
double signedX = negative ? Double.longBitsToDouble(Double.doubleToRawLongBits(x) ^ NumericConstants.longAt(sign)) : x;
double cube = negative ? signedX * z : z * x;
double low = z * c(coefficients) + c(coefficients + 16);
double high = z * c(coefficients + 8) + c(coefficients + 24);
double lowTerm = squared * cube * low;
double highTerm = cube * high;
highTerm = negative ? highTerm - x : highTerm + x;
return (float)(highTerm + lowTerm);
}
private static strictfp float cosineKernel(double x, int coefficients, int sign, boolean negative) {
double z = x * x;
double fourth = z * z;
double first = c(coefficients) * z + c(coefficients + 8);
double second = c(coefficients + 16) * z + c(coefficients + 24);
double sixth = z * fourth;
double fourthTerm = fourth * c(coefficients + 32) + second;
double sixthTerm = sixth * first;
float value = (float)(sixthTerm + fourthTerm);
return negative ? sign(value, sign) : value;
}
public static strictfp int reduce(float input, double[] output) {
int bits = Float.floatToRawIntBits(input);
int absolute = bits & 2147483647;
if (absolute <= 1305022426) {
double x = (double)input;
double multiple = c(2864832) * x;
multiple += c(2864840);
multiple += c(2864848);
int quadrant = NumericMath.truncateInt(multiple);
double first = c(2864856) * multiple + x;
double remainder = c(2864864) * multiple + first;
output[0] = remainder;
if (c(2864872) > remainder) {
multiple += c(2864896);
quadrant--;
} else {
if (!(remainder > c(2864880))) {
return quadrant;
}
multiple += c(2864888);
quadrant++;
}
double correction = c(2864856) * multiple;
x += correction;
multiple *= c(2864864);
output[0] = multiple + x;
return quadrant;
} else if (absolute > 2139095039) {
output[0] = (double)FloatNumeric.invalid(input);
return 0;
} else {
int exponent = (absolute >>> 23) - 150;
int scaledBits = absolute - (exponent << 23);
double[] reduced = new double[1];
int quadrant = LargeReduction.reduce(new double[]{(double)Float.intBitsToFloat(scaledBits)}, reduced, exponent, 1, 0);
if (bits < 0) {
output[0] = Double.longBitsToDouble(Double.doubleToRawLongBits(reduced[0]) ^ NumericConstants.longAt(2864816));
quadrant = -quadrant;
} else {
output[0] = reduced[0];
}
return quadrant;
}
}
public static strictfp float sine(float input) {
int bits = Float.floatToRawIntBits(input);
int absolute = bits & 2147483647;
boolean negative = bits < 0;
double x = (double)input;
if (absolute <= 1061752794) {
return absolute < 964689920 ? input : sineKernel(x, 2867120, 2867104, false);
} else if (absolute < 1081824210) {
if (absolute >= 1075235812) {
x += c(negative ? 2867184 : 2867192);
return sineKernel(x, 2867120, 2867104, true);
} else {
x += c(negative ? 2867200 : 2867208);
return cosineKernel(x, 2867040, 2867088, negative);
}
} else if (absolute < 1088565718) {
if (absolute >= 1085271520) {
x += c(negative ? 2867152 : 2867160);
return sineKernel(x, 2867120, 2867104, false);
} else {
x += c(negative ? 2867168 : 2867176);
return cosineKernel(x, 2867040, 2867088, !negative);
}
} else if (absolute > 2139095039) {
return FloatNumeric.invalid(input);
} else {
double[] remainder = new double[1];
int quadrant = reduce(input, remainder) & 3;
x = remainder[0];
if (quadrant == 0) {
return sineKernel(x, 2867120, 2867104, false);
} else if (quadrant == 1) {
return cosineKernel(x, 2867040, 2867088, false);
} else {
return quadrant == 2 ? sineKernel(x, 2867120, 2867104, true) : cosineKernel(x, 2867040, 2867088, true);
}
}
}
public static strictfp float cosine(float input) {
int bits = Float.floatToRawIntBits(input);
int absolute = bits & 2147483647;
boolean negative = bits < 0;
double x = (double)input;
if (absolute <= 1061752794) {
return absolute < 964689920 ? NumericConstants.floatAt(2870084) : cosineKernel(x, 2869952, 2870000, false);
} else if (absolute < 1081824210) {
if (absolute > 1075235811) {
x += c(negative ? 2870064 : 2870072);
return cosineKernel(x, 2869952, 2870000, true);
} else {
x = negative ? x + c(2870056) : c(2870056) - x;
return sineKernel(x, 2869920, 2870016, false);
}
} else if (absolute < 1088565718) {
if (absolute > 1085271519) {
x += c(negative ? 2870040 : 2870048);
return cosineKernel(x, 2869952, 2870000, false);
} else {
x = negative ? c(2870032) - x : x + c(2870032);
return sineKernel(x, 2869920, 2870016, false);
}
} else if (absolute > 2139095039) {
return FloatNumeric.invalid(input);
} else {
double[] remainder = new double[1];
int quadrant = reduce(input, remainder) & 3;
x = remainder[0];
if (quadrant == 0) {
return cosineKernel(x, 2869952, 2870000, false);
} else if (quadrant == 1) {
return sineKernel(x, 2869920, 2870016, true);
} else {
return quadrant == 2 ? cosineKernel(x, 2869952, 2870000, true) : sineKernel(x, 2869920, 2870016, false);
}
}
}
}
@@ -0,0 +1,217 @@
package dev.slinky.client.numeric;
public final strictfp class LargeReduction {
private LargeReduction() {
}
private static strictfp double c(int address) {
return NumericConstants.doubleAt(address);
}
private static strictfp double digit(int index) {
return (double)NumericConstants.intAt(2872376 + 4 * index);
}
private static strictfp double convolution(double[] input, double[] table, int lastInput, int index) {
double sum = 0.0;
for (int j = 0; j <= lastInput; j++) {
sum += input[j] * table[lastInput + index - j];
}
return sum;
}
private static strictfp double signed(double value, int complement) {
return complement != 0 ? Double.longBitsToDouble(Double.doubleToRawLongBits(value) ^ NumericConstants.longAt(2863200)) : value;
}
public static strictfp int reduce(double[] input, double[] output, int exponent, int count, int precision) {
int initial = NumericConstants.intAt(2872360 + 4 * precision);
int lastInput = count - 1;
int tableStart = Math.max(0, (short)(exponent - 3) / 24);
int chunkExponent = exponent - 24 * (tableStart + 1);
double[] table = new double[20];
double[] products = new double[20];
double[] weighted = new double[20];
int[] chunks = new int[20];
double tiny = c(2863136);
double radix = c(2863144);
double negativeRadix = c(2863152);
double one = c(2863184);
double half = c(2863176);
for (int i = 0; i <= lastInput + initial; i++) {
int index = tableStart - lastInput + i;
table[i] = index < 0 ? 0.0 : digit(index);
}
for (int i = 0; i <= initial; i++) {
products[i] = convolution(input, table, lastInput, i);
}
int last = initial;
int quadrant = 0;
int complement = 0;
double residual;
while (true) {
residual = products[last];
int i = 0;
for (int j = last; j > 0; j--) {
double carry = (double)NumericMath.truncateInt(residual * tiny);
chunks[i] = NumericMath.truncateInt(carry * negativeRadix + residual);
residual = products[j - 1] + carry;
i++;
}
residual = DoubleNumeric.scale(residual, chunkExponent);
double cycles = DoubleNumeric.floor(residual * c(2863160));
residual = cycles * c(2863168) + residual;
quadrant = NumericMath.truncateInt(residual);
residual -= (double)quadrant;
complement = 0;
if (chunkExponent > 0) {
int high = chunks[last - 1] >> 24 - chunkExponent;
quadrant += high;
chunks[last - 1] = chunks[last - 1] - (high << 24 - chunkExponent);
complement = chunks[last - 1] >> 23 - chunkExponent;
} else if (chunkExponent == 0) {
complement = chunks[last - 1] >> 23;
} else if (!(residual < half)) {
complement = 2;
}
if (complement > 0) {
quadrant++;
boolean carry = false;
for (int ix = 0; ix < last; ix++) {
int chunk = chunks[ix];
if (carry) {
chunks[ix] = 16777215 - chunk;
} else if (chunk != 0) {
carry = true;
chunks[ix] = 16777216 - chunk;
}
}
if (chunkExponent == 1) {
chunks[last - 1] = chunks[last - 1] & 8388607;
} else if (chunkExponent == 2) {
chunks[last - 1] = chunks[last - 1] & 4194303;
}
if (complement == 2) {
residual = one - residual;
if (carry) {
residual -= DoubleNumeric.scale(one, chunkExponent);
}
}
}
if (residual != 0.0) {
break;
}
int nonzero = 0;
for (int ixx = last - 1; ixx >= initial; ixx--) {
nonzero |= chunks[ixx];
}
if (nonzero != 0) {
break;
}
int additional = 1;
while (chunks[initial - additional] == 0) {
additional++;
}
for (int ixx = last + 1; ixx <= last + additional; ixx++) {
table[lastInput + ixx] = digit(tableStart + ixx);
products[ixx] = convolution(input, table, lastInput, ixx);
}
last += additional;
}
if (residual == 0.0) {
last--;
for (chunkExponent -= 24; chunks[last] == 0; chunkExponent -= 24) {
last--;
}
} else {
residual = DoubleNumeric.scale(residual, -chunkExponent);
if (!(residual < radix)) {
double carry = (double)NumericMath.truncateInt(residual * tiny);
chunks[last] = NumericMath.truncateInt(carry * negativeRadix + residual);
last++;
chunkExponent += 24;
chunks[last] = NumericMath.truncateInt(carry);
} else {
chunks[last] = NumericMath.truncateInt(residual);
}
}
double factor = DoubleNumeric.scale(one, chunkExponent);
for (int i = last; i >= 0; i--) {
products[i] = factor * (double)chunks[i];
factor *= tiny;
}
for (int i = last; i >= 0; i--) {
double sum = 0.0;
for (int k = 0; k <= initial && k <= last - i; k++) {
sum += c(2875136 + 8 * k) * products[i + k];
}
weighted[last - i] = sum;
}
if (precision < 3) {
double sum = 0.0;
for (int i = last; i >= 0; i--) {
sum += weighted[i];
}
output[0] = signed(sum, complement);
if (precision == 1 || precision == 2) {
double tail = weighted[0] - sum;
for (int i = 1; i <= last; i++) {
tail += weighted[i];
}
output[1] = signed(tail, complement);
}
} else if (precision == 3) {
for (int pass = 0; pass < 2; pass++) {
for (int i = last; i > pass; i--) {
double sum = weighted[i - 1] + weighted[i];
weighted[i] += weighted[i - 1] - sum;
weighted[i - 1] = sum;
}
}
double tail = 0.0;
for (int i = last; i >= 2; i--) {
tail += weighted[i];
}
output[0] = signed(weighted[0], complement);
output[1] = signed(weighted[1], complement);
output[2] = signed(tail, complement);
}
return quadrant & 7;
}
}
@@ -0,0 +1,69 @@
package dev.slinky.client.numeric;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import dev.slinky.inject.Platform;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
public final class NumericConstants {
private static final Map<Integer, Byte> BYTES = new HashMap<>();
private NumericConstants() {
}
private static long word(int address, int length) {
long result = 0L;
for (int offset = 0; offset < length; offset++) {
Byte value = BYTES.get(address + offset);
if (value == null) {
throw new IllegalArgumentException("Unrecovered numerical operand " + Integer.toHexString(address + offset));
}
result |= (long)(value & 255) << offset * 8;
}
return result;
}
public static int shortAt(int address) {
return (int)word(address, 2);
}
public static int intAt(int address) {
return (int)word(address, 4);
}
public static long longAt(int address) {
return word(address, 8);
}
public static float floatAt(int address) {
return Float.intBitsToFloat(intAt(address));
}
public static double doubleAt(int address) {
return Double.longBitsToDouble(longAt(address));
}
static {
try (Reader reader = new InputStreamReader(Platform.resource("/assets/slinkysemantic/NumericConstants.json"), "UTF-8")) {
for (JsonElement element : new JsonParser().parse(reader).getAsJsonArray()) {
JsonObject row = element.getAsJsonObject();
int address = row.get("address").getAsInt();
String hex = row.get("bytes").getAsString();
for (int offset = 0; offset < hex.length() / 2; offset++) {
BYTES.put(address + offset, (byte)Integer.parseInt(hex.substring(offset * 2, offset * 2 + 2), 16));
}
}
} catch (IOException var18) {
throw new ExceptionInInitializerError(var18);
}
}
}
@@ -0,0 +1,50 @@
package dev.slinky.client.numeric;
public final strictfp class NumericMath {
private NumericMath() {
}
public static strictfp float floatDistance(float first, float second) {
int a = Float.floatToRawIntBits(first) & 2147483647;
int b = Float.floatToRawIntBits(second) & 2147483647;
if (a == 2139095040 || b == 2139095040) {
return Float.POSITIVE_INFINITY;
} else if (a <= 2139095040 && b <= 2139095040) {
double x = (double)first;
double y = (double)second;
return (float)Math.sqrt(x * x + y * y);
} else {
return Float.NaN;
}
}
public static strictfp int truncateInt(float value) {
return !Float.isNaN(value) && !(value >= 2.1474836E9F) && !(value < -2.1474836E9F) ? (int)value : Integer.MIN_VALUE;
}
public static strictfp int truncateInt(double value) {
return !Double.isNaN(value) && !(value >= 2.1474836E9F) && !(value < -2.1474836E9F) ? (int)value : Integer.MIN_VALUE;
}
public static strictfp long truncateLong(float value) {
return !Float.isNaN(value) && !((double)value >= 9.223372E18F) && !((double)value < -9.223372E18F) ? (long)value : Long.MIN_VALUE;
}
public static strictfp long truncateLong(double value) {
return !Double.isNaN(value) && !(value >= 9.223372E18F) && !(value < -9.223372E18F) ? (long)value : Long.MIN_VALUE;
}
public static strictfp float ceiling(float value) {
int bits = Float.floatToRawIntBits(value);
int exponent = bits >>> 23 & 0xFF;
if (exponent >= 150) {
return value;
} else if (exponent >= 127) {
int mask = -1 << (22 - (bits >>> 23) & 31);
int fraction = ~mask;
return (bits & fraction) == 0 ? value : Float.intBitsToFloat(bits + (bits < 0 ? 0 : fraction) & mask);
} else {
return Float.intBitsToFloat(bits < 0 ? Integer.MIN_VALUE : (bits == 0 ? 0 : 1065353216));
}
}
}
@@ -0,0 +1,97 @@
package dev.slinky.client.numeric;
import java.util.Objects;
public final class NumericServices {
public static final String CAPTURED_HYPOT = "0x13cfd0: ucrtbase.dll!hypot";
public static final String CAPTURED_ATAN2 = "0x13cfb0: ucrtbase.dll!atan2";
public static final String CAPTURED_ACOS = "0x13cfa0: ucrtbase.dll!acos";
public static final String CAPTURED_ATAN2F = "0x13cfc0: ucrtbase.dll!atan2f";
public static final String CAPTURED_ACOSF = "0x13d190: ucrtbase.dll!acosf";
public static final String CAPTURED_POWF = "0x13cfe0: ucrtbase.dll!powf";
private static volatile NumericServices.DoublePair pair = StrictMath::hypot;
private static volatile NumericServices.DoublePair direction = StrictMath::atan2;
private static volatile NumericServices.DoubleUnary projection = StrictMath::acos;
private static volatile NumericServices.FloatPair floatDirection = (first, second) -> (float)StrictMath.atan2((double)first, (double)second);
private static volatile NumericServices.FloatUnary floatProjection = value -> (float)StrictMath.acos((double)value);
private static volatile NumericServices.FloatPair scalar = (first, second) -> (float)StrictMath.pow((double)first, (double)second);
private NumericServices() {
}
public static double hypot(double first, double second) {
return pair.call(first, second);
}
public static double atan2(double first, double second) {
return direction.call(first, second);
}
public static double acos(double value) {
return projection.call(value);
}
public static float atan2f(float first, float second) {
return floatDirection.call(first, second);
}
public static float acosf(float value) {
return floatProjection.call(value);
}
public static float powf(float first, float second) {
return scalar.call(first, second);
}
public static NumericServices.DoublePair bindHypot(NumericServices.DoublePair endpoint) {
NumericServices.DoublePair previous = pair;
pair = Objects.requireNonNull(endpoint);
return previous;
}
public static NumericServices.DoublePair bindAtan2(NumericServices.DoublePair endpoint) {
NumericServices.DoublePair previous = direction;
direction = Objects.requireNonNull(endpoint);
return previous;
}
public static NumericServices.DoubleUnary bindAcos(NumericServices.DoubleUnary endpoint) {
NumericServices.DoubleUnary previous = projection;
projection = Objects.requireNonNull(endpoint);
return previous;
}
public static NumericServices.FloatPair bindAtan2f(NumericServices.FloatPair endpoint) {
NumericServices.FloatPair previous = floatDirection;
floatDirection = Objects.requireNonNull(endpoint);
return previous;
}
public static NumericServices.FloatUnary bindAcosf(NumericServices.FloatUnary endpoint) {
NumericServices.FloatUnary previous = floatProjection;
floatProjection = Objects.requireNonNull(endpoint);
return previous;
}
public static NumericServices.FloatPair bindPowf(NumericServices.FloatPair endpoint) {
NumericServices.FloatPair previous = scalar;
scalar = Objects.requireNonNull(endpoint);
return previous;
}
public interface DoublePair {
double call(double var1, double var3);
}
public interface DoubleUnary {
double call(double var1);
}
public interface FloatPair {
float call(float var1, float var2);
}
public interface FloatUnary {
float call(float var1);
}
}
@@ -0,0 +1,51 @@
package dev.slinky.client.packet;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.Packet;
public final class NetworkSendAdapter {
private static final ThreadLocal<NetworkSendAdapter.Invocation> DIRECT = new ThreadLocal<>();
private NetworkSendAdapter() {
}
public static void sendDirect(NetworkManager receiver, Packet<?> packet) {
if (receiver != null && packet != null) {
NetworkSendAdapter.Invocation previous = DIRECT.get();
DIRECT.set(new NetworkSendAdapter.Invocation(receiver, packet));
try {
receiver.sendPacket(packet);
} finally {
if (previous == null) {
DIRECT.remove();
} else {
DIRECT.set(previous);
}
}
} else {
throw new IllegalArgumentException("A direct send needs its receiver and packet");
}
}
public static boolean consumeDirect(NetworkManager receiver, Packet<?> packet) {
NetworkSendAdapter.Invocation invocation = DIRECT.get();
if (invocation != null && !invocation.consumed && invocation.receiver == receiver && invocation.packet == packet) {
invocation.consumed = true;
return true;
} else {
return false;
}
}
private static final class Invocation {
final NetworkManager receiver;
final Packet<?> packet;
boolean consumed;
Invocation(NetworkManager receiver, Packet<?> packet) {
this.receiver = receiver;
this.packet = packet;
}
}
}
@@ -0,0 +1,30 @@
package dev.slinky.client.packet;
import dev.slinky.inject.ReflectionHelper;
import java.lang.reflect.Field;
import net.minecraft.network.play.server.S14PacketEntity;
import net.minecraft.network.play.server.S19PacketEntityStatus;
public final class PacketAccess {
private static final Field STATUS_ID = ReflectionHelper.findField(S19PacketEntityStatus.class, "entityId", "field_149164_a");
private static final Field RELATIVE_ID = ReflectionHelper.findField(S14PacketEntity.class, "entityId", "field_149074_a");
private PacketAccess() {
}
public static int statusId(S19PacketEntityStatus p) {
return integer(STATUS_ID, p);
}
public static int relativeId(S14PacketEntity p) {
return integer(RELATIVE_ID, p);
}
private static int integer(Field field, Object p) {
try {
return field.getInt(p);
} catch (IllegalAccessException var3) {
throw new IllegalStateException(var3);
}
}
}
@@ -0,0 +1,20 @@
package dev.slinky.client.packet;
import net.minecraft.network.Packet;
import net.minecraft.network.play.server.S06PacketUpdateHealth;
import net.minecraft.network.play.server.S19PacketEntityStatus;
import net.minecraft.network.play.server.S29PacketSoundEffect;
import net.minecraft.network.play.server.S2APacketParticles;
public final class PacketKinds {
private PacketKinds() {
}
public static boolean realtimeDamage(Packet<?> p) {
return p instanceof S19PacketEntityStatus || p instanceof S06PacketUpdateHealth || p instanceof S29PacketSoundEffect;
}
public static boolean blinkImmediate(Packet<?> p) {
return p instanceof S2APacketParticles;
}
}
@@ -0,0 +1,231 @@
package dev.slinky.client.packet;
import dev.slinky.client.event.SemanticEvent;
import dev.slinky.module.ModuleManager;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.ReferenceCountUtil;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import net.minecraft.network.Packet;
public final class PacketPipeline extends ChannelDuplexHandler implements Runnable {
private static final AtomicInteger COUNTER = new AtomicInteger();
private static final Set<PacketPipeline> ACTIVE = Collections.newSetFromMap(new ConcurrentHashMap<>());
public static final String NAME = "slinky_semantic_packets";
private final ArrayDeque<PacketPipeline.Entry> inbound = new ArrayDeque<>();
private final ArrayDeque<PacketPipeline.Entry> outbound = new ArrayDeque<>();
private final PacketPipeline.Clock clock;
private final PacketPipeline.Dispatch dispatch;
private final boolean automaticPolling;
private Future<?> polling;
private ChannelHandlerContext owner;
public PacketPipeline(final ModuleManager modules) {
this(new PacketPipeline.Clock() {
@Override
public long millis() {
return System.currentTimeMillis();
}
}, new PacketPipeline.Dispatch() {
@Override
public int classify(Object packet, boolean outbound, long age, int sequence) {
if (!(packet instanceof Packet)) {
return 0;
} else {
SemanticEvent e = new SemanticEvent(outbound ? 3 : 2);
e.packet = (Packet<?>)packet;
e.packetAgeMillis = age;
e.packetSequence = sequence;
modules.dispatch(e);
return e.dispatchMode;
}
}
}, true);
}
public PacketPipeline(PacketPipeline.Clock clock, PacketPipeline.Dispatch dispatch) {
this(clock, dispatch, false);
}
private PacketPipeline(PacketPipeline.Clock clock, PacketPipeline.Dispatch dispatch, boolean automaticPolling) {
this.clock = clock;
this.dispatch = dispatch;
this.automaticPolling = automaticPolling;
}
public void handlerAdded(ChannelHandlerContext context) {
this.owner = context;
ACTIVE.add(this);
if (this.automaticPolling) {
this.polling = context.executor().scheduleWithFixedDelay(this, 4L, 4L, TimeUnit.MILLISECONDS);
}
}
public void write(ChannelHandlerContext context, Object packet, ChannelPromise promise) {
this.accept(context, packet, promise, true);
}
public void channelRead(ChannelHandlerContext context, Object packet) {
this.accept(context, packet, null, false);
}
private void accept(ChannelHandlerContext context, Object packet, ChannelPromise promise, boolean out) {
int sequence = COUNTER.getAndIncrement();
int mode = this.dispatch.classify(packet, out, 0L, sequence);
if (mode == 2) {
discard(packet, promise);
} else {
ArrayDeque<PacketPipeline.Entry> queue = out ? this.outbound : this.inbound;
if (mode != 1 && (mode != 0 || queue.isEmpty())) {
if (out) {
context.write(packet, promise);
} else {
context.fireChannelRead(packet);
}
} else {
queue.add(new PacketPipeline.Entry(context, packet, promise, this.clock.millis(), sequence));
}
}
}
@Override
public void run() {
long now = this.clock.millis();
this.poll(this.inbound, false, now);
this.poll(this.outbound, true, now);
}
private void poll(ArrayDeque<PacketPipeline.Entry> queue, boolean out, long now) {
ChannelHandlerContext flushed = null;
while (!queue.isEmpty()) {
PacketPipeline.Entry entry = queue.peek();
int mode = this.dispatch.classify(entry.packet, out, now - entry.added, entry.sequence);
if (mode == 1) {
break;
}
if (mode == 2) {
queue.removeFirst();
discard(entry.packet, entry.promise);
return;
}
if (out) {
entry.context.write(entry.packet, entry.promise);
flushed = entry.context;
} else {
entry.context.fireChannelRead(entry.packet);
}
queue.removeFirst();
}
if (flushed != null) {
flushed.flush();
}
}
private static void discard(Object packet, ChannelPromise promise) {
ReferenceCountUtil.release(packet);
}
public void handlerRemoved(ChannelHandlerContext context) {
ACTIVE.remove(this);
if (this.polling != null) {
this.polling.cancel(false);
this.polling = null;
}
this.drain(context, this.inbound, false);
this.drain(context, this.outbound, true);
context.flush();
this.owner = null;
}
private void drain(ChannelHandlerContext context, ArrayDeque<PacketPipeline.Entry> queue, boolean out) {
while (!queue.isEmpty()) {
PacketPipeline.Entry entry = queue.removeFirst();
if (!context.channel().isOpen()) {
ReferenceCountUtil.release(entry.packet);
if (entry.promise != null) {
entry.promise.tryFailure(new ClosedChannelException());
}
} else if (out) {
context.write(entry.packet, entry.promise);
} else {
context.fireChannelRead(entry.packet);
}
}
}
public void channelInactive(ChannelHandlerContext context) throws Exception {
ACTIVE.remove(this);
if (this.polling != null) {
this.polling.cancel(false);
this.polling = null;
}
this.drain(context, this.inbound, false);
this.drain(context, this.outbound, true);
super.channelInactive(context);
}
public static void releaseAll(final boolean removeHandler) {
for (final PacketPipeline handler : ACTIVE) {
final ChannelHandlerContext context = handler.owner;
if (context != null) {
context.executor().execute(new Runnable() {
@Override
public void run() {
if (removeHandler) {
if (context.pipeline().context(handler) != null) {
context.pipeline().remove(handler);
}
} else {
handler.drain(context, handler.inbound, false);
handler.drain(context, handler.outbound, true);
context.flush();
}
}
});
}
}
}
public int retained(boolean out) {
return (out ? this.outbound : this.inbound).size();
}
public interface Clock {
long millis();
}
public interface Dispatch {
int classify(Object var1, boolean var2, long var3, int var5);
}
private static final class Entry {
final ChannelHandlerContext context;
final Object packet;
final ChannelPromise promise;
final long added;
final int sequence;
Entry(ChannelHandlerContext context, Object packet, ChannelPromise promise, long added, int sequence) {
this.context = context;
this.packet = packet;
this.promise = promise;
this.added = added;
this.sequence = sequence;
}
}
}
@@ -0,0 +1,64 @@
package dev.slinky.client.packet;
import dev.slinky.client.rotation.RotationMath;
import net.minecraft.util.Vec3;
public final strictfp class PredictionGeometry {
private PredictionGeometry() {
}
private static strictfp double f(double x) {
return (double)((float)x);
}
private static strictfp double min(double x, double y) {
return x < y ? x : y;
}
private static strictfp double max(double x, double y) {
return x > y ? x : y;
}
private static strictfp double minRight(double x, double y) {
return Double.isNaN(y) ? x : min(x, y);
}
private static strictfp double maxRight(double x, double y) {
return Double.isNaN(y) ? x : max(x, y);
}
public static strictfp boolean ray(double[] box, Vec3 origin, Vec3 ray) {
return ray(box, new double[]{origin.xCoord, origin.yCoord, origin.zCoord}, new double[]{ray.xCoord, ray.yCoord, ray.zCoord});
}
public static strictfp boolean ray(double[] box, double[] origin, double[] ray) {
double x0 = f((box[0] - origin[0]) * (1.0 / ray[0]));
double x1 = f((box[3] - origin[0]) * (1.0 / ray[0]));
double enterX = f(min(max(x0, 0.0), max(x1, 0.0)));
double exitX = f(max(min(x0, Double.POSITIVE_INFINITY), min(x1, Double.POSITIVE_INFINITY)));
double y0 = f((box[1] - origin[1]) * (1.0 / ray[1]));
double y1 = f((box[4] - origin[1]) * (1.0 / ray[1]));
double enterY = f(min(max(y0, enterX), max(y1, enterX)));
double exitY = f(max(min(y0, exitX), min(y1, exitX)));
double z0 = f((box[2] - origin[2]) * (1.0 / ray[2]));
double z1 = f((box[5] - origin[2]) * (1.0 / ray[2]));
float entry = (float)minRight(maxRight(enterY, z1), maxRight(enterY, z0));
float exit = (float)maxRight(minRight(exitY, z1), minRight(exitY, z0));
return entry < exit && entry <= 1.0F;
}
public static strictfp Vec3 clamp(double[] box, Vec3 point) {
return new Vec3(
RotationMath.clamp(box[0], box[3], point.xCoord), RotationMath.clamp(box[1], box[4], point.yCoord), RotationMath.clamp(box[2], box[5], point.zCoord)
);
}
public static strictfp Vec3 movement(float yaw, float forward, float lateral, float speed) {
double[] xz = RotationMath.movement(yaw, forward, lateral, speed);
return new Vec3(xz[0], 0.0, xz[1]);
}
public static strictfp Vec3 scale(Vec3 value, double factor) {
return new Vec3(value.xCoord * factor, value.yCoord * factor, value.zCoord * factor);
}
}
@@ -0,0 +1,83 @@
package dev.slinky.client.packet;
import dev.slinky.client.ClientContext;
import dev.slinky.client.rotation.RotationMath;
import dev.slinky.inject.ReflectionHelper;
import java.lang.reflect.Field;
import net.minecraft.client.entity.EntityOtherPlayerMP;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.Vec3;
public final strictfp class TimingAccess {
private static final Field X = ReflectionHelper.findField(EntityOtherPlayerMP.class, "otherPlayerMPX", "field_71185_c");
private static final Field Y = ReflectionHelper.findField(EntityOtherPlayerMP.class, "otherPlayerMPY", "field_71182_d");
private static final Field Z = ReflectionHelper.findField(EntityOtherPlayerMP.class, "otherPlayerMPZ", "field_71183_e");
private static final Field TICKS = ReflectionHelper.findField(EntityOtherPlayerMP.class, "otherPlayerMPPosRotationIncrements", "field_71184_b");
private static final Field YAW = ReflectionHelper.findField(EntityOtherPlayerMP.class, "otherPlayerMPYaw", "field_71180_f");
private TimingAccess() {
}
public static strictfp Vec3 position(Entity p) {
return new Vec3(p.posX, p.posY, p.posZ);
}
public static strictfp Vec3 server(Entity p) {
if (!(p instanceof EntityOtherPlayerMP)) {
return position(p);
} else {
try {
return new Vec3(X.getDouble(p), Y.getDouble(p), Z.getDouble(p));
} catch (IllegalAccessException var2) {
throw new IllegalStateException(var2);
}
}
}
public static strictfp int interpolationTicks(Entity p) {
if (!(p instanceof EntityOtherPlayerMP)) {
return 0;
} else {
try {
return TICKS.getInt(p);
} catch (IllegalAccessException var2) {
throw new IllegalStateException(var2);
}
}
}
public static strictfp double serverYaw(Entity p) {
if (!(p instanceof EntityOtherPlayerMP)) {
return (double)p.rotationYaw;
} else {
try {
return YAW.getDouble(p);
} catch (IllegalAccessException var2) {
throw new IllegalStateException(var2);
}
}
}
public static strictfp boolean facingPlayer(ClientContext c) {
Vec3 self = position(c.player());
for (EntityPlayer p : c.world().playerEntities) {
if (p != c.player() && !(squared(self, position(p)) > 36.0)) {
float angle = RotationMath.wrap(RotationMath.angles(self, position(p))[0] - c.player().rotationYaw);
if (!(RotationMath.abs(angle) > 90.0F)) {
return true;
}
}
}
return false;
}
public static strictfp double squared(Vec3 a, Vec3 b) {
double x = a.xCoord - b.xCoord;
double y = a.yCoord - b.yCoord;
double z = a.zCoord - b.zCoord;
return z * z + (x * x + y * y);
}
}
@@ -0,0 +1,226 @@
package dev.slinky.client.placement;
import dev.slinky.client.ClientContext;
import dev.slinky.client.action.ActionAccess;
import dev.slinky.client.action.ActionConstants;
import dev.slinky.client.input.KeyState;
import dev.slinky.client.rotation.RotationService;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import net.minecraft.block.Block;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.BlockPos;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.Vec3;
public interface PlacementFrame {
boolean ready();
boolean ground();
int hurt();
boolean mounted();
boolean jump();
Vec3 position();
Vec3 eye();
AxisAlignedBB box();
double[] velocity();
float yaw();
float pitch();
int block(int var1, int var2, int var3);
MovingObjectPosition ray(Vec3 var1, Vec3 var2);
MovingObjectPosition hit();
boolean heldBlock();
void select(boolean var1, boolean var2);
void press();
void disable();
boolean claim(int var1);
boolean rotationAvailable();
float storedYaw();
float storedPitch();
float offsetYaw();
float offsetPitch();
void publish(float var1, float var2);
void movement(int var1);
void restoreMovement();
public static final class Game implements PlacementFrame {
private final ClientContext c;
private final ModuleManager modules;
private final Module token;
public Game(ClientContext c, ModuleManager modules, Module token) {
this.c = c;
this.modules = modules;
this.token = token;
}
@Override
public boolean ready() {
return this.c.inputReady();
}
@Override
public boolean ground() {
return this.c.player().onGround;
}
@Override
public int hurt() {
return this.c.player().hurtTime;
}
@Override
public boolean mounted() {
return this.c.player().ridingEntity != null;
}
@Override
public boolean jump() {
return KeyState.physical(this.c.minecraft().gameSettings.keyBindJump);
}
@Override
public Vec3 position() {
return new Vec3(this.c.player().posX, this.c.player().posY, this.c.player().posZ);
}
@Override
public Vec3 eye() {
return this.c.player().getPositionEyes((float)ActionConstants.d(1374064));
}
@Override
public AxisAlignedBB box() {
return this.c.player().getEntityBoundingBox();
}
@Override
public double[] velocity() {
return new double[]{this.c.player().motionX, this.c.player().motionY, this.c.player().motionZ};
}
@Override
public float yaw() {
return this.c.player().rotationYaw;
}
@Override
public float pitch() {
return this.c.player().rotationPitch;
}
@Override
public int block(int x, int y, int z) {
return Block.getIdFromBlock(this.c.world().getBlockState(new BlockPos(x, y, z)).getBlock());
}
@Override
public MovingObjectPosition ray(Vec3 from, Vec3 to) {
return this.c.world().rayTraceBlocks(from, to, false, true, false);
}
@Override
public MovingObjectPosition hit() {
return this.c.minecraft().objectMouseOver;
}
@Override
public boolean heldBlock() {
return ActionAccess.isBlock(this.c.player().getHeldItem());
}
@Override
public void select(boolean force, boolean blockIn) {
PlacementSelection.select(this.c, this.token, force, blockIn);
}
@Override
public void press() {
ActionAccess.increment(this.c.minecraft().gameSettings.keyBindUseItem);
}
@Override
public void disable() {
this.modules.requestEnabled(this.token, false);
}
@Override
public boolean claim(int priority) {
return RotationService.claim(priority);
}
@Override
public boolean rotationAvailable() {
return RotationService.storedRotationValid();
}
@Override
public float storedYaw() {
return RotationService.storedYaw();
}
@Override
public float storedPitch() {
return RotationService.storedPitch();
}
@Override
public float offsetYaw() {
return RotationService.pendingYaw();
}
@Override
public float offsetPitch() {
return RotationService.pendingPitch();
}
@Override
public void publish(float yaw, float pitch) {
RotationService.add(yaw, pitch);
}
@Override
public void movement(int mode) {
GameSettings g = this.c.minecraft().gameSettings;
KeyState.set(g.keyBindBack, mode == 1);
KeyState.set(g.keyBindForward, false);
KeyState.set(g.keyBindLeft, false);
KeyState.set(g.keyBindRight, false);
}
@Override
public void restoreMovement() {
GameSettings g = this.c.minecraft().gameSettings;
KeyState.restore(g.keyBindBack);
KeyState.restore(g.keyBindForward);
KeyState.restore(g.keyBindLeft);
KeyState.restore(g.keyBindRight);
}
}
}
@@ -0,0 +1,77 @@
package dev.slinky.client.placement;
import dev.slinky.client.action.ActionConstants;
import dev.slinky.client.rotation.RotationMath;
import net.minecraft.util.Vec3;
public final strictfp class PlacementGeometry {
private PlacementGeometry() {
}
private static strictfp double narrow(double v) {
return (double)((float)v);
}
private static strictfp double min(double a, double b) {
return a < b ? a : b;
}
private static strictfp double max(double a, double b) {
return a > b ? a : b;
}
private static strictfp double minRight(double a, double b) {
return Double.isNaN(b) ? a : min(a, b);
}
private static strictfp double maxRight(double a, double b) {
return Double.isNaN(b) ? a : max(a, b);
}
public static strictfp Vec3 rayPoint(double[] b, Vec3 o, Vec3 v) {
double numerator = ActionConstants.d(1304712);
double limit = ActionConstants.d(1304720);
double x0 = narrow((b[0] - o.xCoord) * (numerator / v.xCoord));
double x1 = narrow((b[3] - o.xCoord) * (numerator / v.xCoord));
double entryX = narrow(min(max(x0, 0.0), max(x1, 0.0)));
double exitX = narrow(max(min(x0, limit), min(x1, limit)));
double y0 = narrow((b[1] - o.yCoord) * (numerator / v.yCoord));
double y1 = narrow((b[4] - o.yCoord) * (numerator / v.yCoord));
double entryY = narrow(min(max(y0, entryX), max(y1, entryX)));
double exitY = narrow(max(min(y0, exitX), min(y1, exitX)));
double z0 = narrow((b[2] - o.zCoord) * (numerator / v.zCoord));
double z1 = narrow((b[5] - o.zCoord) * (numerator / v.zCoord));
float entry = (float)minRight(maxRight(entryY, z1), maxRight(entryY, z0));
float exit = (float)maxRight(minRight(exitY, z1), minRight(exitY, z0));
return !(exit <= entry) && !(ActionConstants.f(1304728) < entry)
? new Vec3(o.xCoord + v.xCoord * (double)entry, o.yCoord + v.yCoord * (double)entry, o.zCoord + v.zCoord * (double)entry)
: null;
}
public static strictfp Vec3 direction(float yaw, float pitch) {
float yr = (float)((double)yaw * ActionConstants.d(1374032) / ActionConstants.d(1374040) + ActionConstants.d(1374032));
float pr = (float)((double)pitch * ActionConstants.d(1374032) / ActionConstants.d(1374040));
float yc = RotationMath.cosine(yr);
float ys = RotationMath.sine(yr);
float pc = RotationMath.cosine(pr);
float ps = RotationMath.sine(pr);
float negative = Float.intBitsToFloat(Float.floatToRawIntBits(pc) ^ ActionConstants.intAt(1374048));
return new Vec3((double)(ys * negative), (double)ps, (double)(negative * yc));
}
public static strictfp double squared(Vec3 a, Vec3 b) {
double x = a.xCoord - b.xCoord;
double y = a.yCoord - b.yCoord;
double z = a.zCoord - b.zCoord;
return z * z + (x * x + y * y);
}
public static strictfp int magnitude(int value) {
int negated = -value;
return negated < 0 ? value : negated;
}
public static strictfp long absdiff(int a, int b) {
return Integer.toUnsignedLong(b < a ? a - b : b - a);
}
}
@@ -0,0 +1,290 @@
package dev.slinky.client.placement;
import dev.slinky.client.ClientContext;
import dev.slinky.client.action.ActionAccess;
import dev.slinky.client.action.ModuleActionService;
import dev.slinky.client.inventory.InventoryActions;
import java.util.function.IntUnaryOperator;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.item.ItemStack;
public final class PlacementSelection {
private static int preferred;
private static boolean startAfterCurrent;
private static Object selectionOwner;
private static PlacementSelection.Inventory inventoryOwner;
private static Object restorationOwner;
private static int originalSlot;
private static int inactivity;
private static int capturedType;
private static int capturedCount;
private static int lastSlot;
private static int repetitions;
private PlacementSelection() {
}
public static synchronized void remember(int type, boolean afterCurrent) {
preferred = type;
startAfterCurrent = afterCurrent;
}
public static PlacementSelection.Inventory inventory(final InventoryPlayer player) {
return new PlacementSelection.Inventory() {
@Override
public int current() {
return player.currentItem;
}
@Override
public void current(int slot) {
player.currentItem = slot;
}
private ItemStack at(int slot) {
return player.getStackInSlot(slot);
}
@Override
public boolean stack(int slot) {
return this.at(slot) != null;
}
@Override
public boolean item(int slot) {
return this.at(slot) != null && this.at(slot).getItem() != null;
}
@Override
public int type(int slot) {
return InventoryActions.type(this.at(slot));
}
@Override
public boolean block(int slot) {
return ActionAccess.isBlock(this.at(slot));
}
@Override
public int count(int slot) {
return this.at(slot).stackSize;
}
};
}
public static synchronized void frameReset() {
if (selectionOwner != null) {
ModuleActionService.release(selectionOwner);
}
selectionOwner = null;
inactivity++;
preferred = 0;
startAfterCurrent = false;
}
public static synchronized void capture(PlacementSelection.Inventory i) {
if (capturedType == 0) {
int slot = i.current();
if (i.stack(slot) && i.block(slot)) {
capturedType = i.type(slot);
capturedCount = i.count(slot);
if (Integer.compareUnsigned(inactivity, 20) > 0 || lastSlot != slot) {
repetitions = capturedCount;
}
inactivity = 0;
lastSlot = slot;
}
}
}
public static void capture(ClientContext c) {
if (c.ready()) {
capture(inventory(c.player().inventory));
}
}
public static synchronized void consume(PlacementSelection.Inventory i) {
if (capturedType != 0) {
int slot = i.current();
boolean depleted = !i.stack(slot) || !i.item(slot) || i.type(slot) == 0;
if (!depleted && i.count(slot) < capturedCount) {
depleted = --repetitions == 0;
}
if (depleted) {
preferred = capturedType;
startAfterCurrent = true;
}
capturedType = 0;
}
}
public static void consume(ClientContext c) {
if (c.ready()) {
consume(inventory(c.player().inventory));
}
}
public static synchronized int preferred() {
return preferred;
}
public static synchronized boolean afterCurrent() {
return startAfterCurrent;
}
public static int bridgeRank(int id) {
switch (id) {
case 1:
case 2:
case 3:
case 35:
case 45:
return 2;
case 5:
case 17:
case 20:
case 24:
case 87:
return 1;
default:
int offset = id - 121;
return Integer.compareUnsigned(offset, 41) <= 0 && (2473901162497L >>> (offset & 63) & 1L) != 0L ? 1 : -1;
}
}
public static int blockInRank(int id) {
switch (id) {
case 1:
case 2:
case 3:
case 20:
case 24:
case 45:
case 87:
case 159:
return 2;
case 4:
case 5:
case 17:
case 121:
case 162:
return 3;
case 35:
return 1;
case 49:
return 4;
default:
return -1;
}
}
public static int choose(int[] ids, int current, int preferredId, boolean afterCurrent, IntUnaryOperator rank) {
int first = current + (afterCurrent ? 1 : 0);
int chosen = -1;
int best = -1;
for (int n = 0; n < 9; n++) {
int slot = (first + n) % 9;
int id = ids[slot];
if (id != 0) {
if (preferredId != 0 && id == preferredId) {
return slot;
}
int score = rank.applyAsInt(id);
if (score > best) {
best = score;
chosen = slot;
}
}
}
return chosen;
}
public static synchronized boolean select(ClientContext c, Object token, boolean force, boolean blockIn) {
return selectionOwner == null && c.ready() && (force || preferred != 0) ? select(inventory(c.player().inventory), token, force, blockIn) : false;
}
public static synchronized boolean select(PlacementSelection.Inventory inventory, Object token, boolean force, boolean blockIn) {
if (selectionOwner == null && (force || preferred != 0)) {
int[] ids = new int[9];
for (int i = 0; i < 9; i++) {
ids[i] = inventory.type(i);
}
int chosen = choose(ids, inventory.current(), preferred, startAfterCurrent, blockIn ? PlacementSelection::blockInRank : PlacementSelection::bridgeRank);
if (chosen != -1 && ModuleActionService.claim(token)) {
if (restorationOwner != token) {
originalSlot = inventory.current();
inventoryOwner = inventory;
restorationOwner = token;
}
selectionOwner = token;
inventory.current(chosen);
return true;
} else {
return false;
}
} else {
return false;
}
}
public static synchronized void release(Object token) {
if (restorationOwner == token) {
inventoryOwner.current(originalSlot);
restorationOwner = null;
inventoryOwner = null;
}
if (selectionOwner == token) {
selectionOwner = null;
}
ModuleActionService.release(token);
}
public static synchronized void reset() {
if (inventoryOwner != null) {
inventoryOwner.current(originalSlot);
}
if (selectionOwner != null) {
ModuleActionService.release(selectionOwner);
}
restorationOwner = null;
selectionOwner = null;
inventoryOwner = null;
preferred = 0;
startAfterCurrent = false;
repetitions = 0;
lastSlot = 0;
capturedCount = 0;
capturedType = 0;
inactivity = 0;
}
public interface Inventory {
int current();
void current(int var1);
boolean stack(int var1);
boolean item(int var1);
int type(int var1);
boolean block(int var1);
int count(int var1);
}
}
@@ -0,0 +1,266 @@
package dev.slinky.client.render;
import dev.slinky.client.render.font.CanvasFonts;
import dev.slinky.setting.ColorSetting;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import net.minecraft.item.ItemStack;
public final class DrawPlan {
private final List<DrawPlan.Op> operations = new ArrayList<>();
private boolean canvas;
private float[] canvasTransform = new float[]{1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F};
private final Deque<float[]> canvasTransforms = new ArrayDeque<>();
public DrawPlan canvas() {
this.canvas = true;
return this;
}
public List<DrawPlan.Op> operations() {
return Collections.unmodifiableList(this.operations);
}
public DrawPlan op(String kind, float... values) {
this.operations.add(new DrawPlan.Op(kind, null, values));
return this;
}
public DrawPlan push() {
this.canvasTransforms.push((float[])this.canvasTransform.clone());
return this.op("push");
}
public DrawPlan pop() {
this.canvasTransform = this.canvasTransforms.pop();
return this.op("pop");
}
public DrawPlan translate(float x, float y, float z) {
this.canvasTransform[4] = x * this.canvasTransform[0] + y * this.canvasTransform[2] + this.canvasTransform[4];
this.canvasTransform[5] = x * this.canvasTransform[1] + y * this.canvasTransform[3] + this.canvasTransform[5];
return this.op("translate", x, y, z);
}
public DrawPlan scale(float x, float y, float z) {
this.canvasTransform[0] = this.canvasTransform[0] * x;
this.canvasTransform[1] = this.canvasTransform[1] * x;
this.canvasTransform[2] = this.canvasTransform[2] * y;
this.canvasTransform[3] = this.canvasTransform[3] * y;
return this.op("scale", x, y, z);
}
public DrawPlan rotate(float angle, float x, float y, float z) {
return this.op("rotate", angle, x, y, z);
}
public DrawPlan color(ColorSetting.Value c, float alpha) {
return this.op("color", c.red, c.green, c.blue, alpha);
}
public DrawPlan color(float r, float g, float b, float a) {
return this.op("color", r, g, b, a);
}
public DrawPlan lineWidth(float width) {
return this.op("lineWidth", width);
}
public DrawPlan indexed(int mode, int dimensions, int vertices, int indices, int count) {
float[] values = new float[1 + count * dimensions];
values[0] = (float)mode;
for (int i = 0; i < count; i++) {
int v = RenderConstants.u16(indices + i * 2);
for (int j = 0; j < dimensions; j++) {
values[1 + i * dimensions + j] = RenderConstants.f(vertices + (v * dimensions + j) * 4);
}
}
this.operations.add(new DrawPlan.Op("vertices" + dimensions, null, values));
return this;
}
public DrawPlan graphics(int address) {
int mode;
int count;
int vertex;
int index;
int dim;
switch (address) {
case 168960:
mode = 5;
count = 14;
vertex = 1375248;
index = 1375344;
dim = 3;
break;
case 169008:
mode = 1;
count = 24;
vertex = 1375248;
index = 1375360;
dim = 3;
break;
case 169120:
mode = 5;
count = 4;
vertex = 1375392;
index = 1375424;
dim = 2;
break;
case 169168:
mode = 2;
count = 4;
vertex = 1375392;
index = 1375428;
dim = 2;
break;
case 169280:
mode = 6;
count = 4;
vertex = 1375440;
index = 1375472;
dim = 2;
break;
case 169328:
mode = 2;
count = 4;
vertex = 1375440;
index = 1375476;
dim = 2;
break;
default:
throw new IllegalArgumentException("Unknown recovered draw helper");
}
float[] values = new float[1 + count * dim];
values[0] = (float)mode;
for (int i = 0; i < count; i++) {
int v = RenderConstants.i(index + (i & -4)) >>> (i & 3) * 8 & 0xFF;
for (int j = 0; j < dim; j++) {
values[1 + i * dim + j] = RenderConstants.f(vertex + (v * dim + j) * 4);
}
}
this.operations.add(new DrawPlan.Op("vertices" + dim, null, values));
return this;
}
public DrawPlan vertices(int mode, int dimensions, float... vertices) {
float[] v = new float[vertices.length + 1];
v[0] = (float)mode;
System.arraycopy(vertices, 0, v, 1, vertices.length);
this.operations.add(new DrawPlan.Op("vertices" + dimensions, null, v));
return this;
}
public DrawPlan line(float x, float y, float ex, float ey) {
if (this.canvas) {
this.operations.add(new DrawPlan.Op("vertices2", Boolean.TRUE, new float[]{1.0F, x, y, ex, ey}));
return this;
} else {
return this.vertices(1, 2, x, y, ex, ey);
}
}
public DrawPlan text(String text, float x, float y, int color, boolean shadow, float size) {
this.operations.add(new DrawPlan.Op("text", text, new float[]{x, y, Float.intBitsToFloat(color), shadow ? 1.0F : 0.0F, size}));
return this;
}
public float canvasText(String text, float x, float y, int color, float size, int alignment) {
return this.canvasText(text, x, y, color, size, alignment, 0);
}
public float canvasText(String text, float x, float y, int color, float size, int alignment, int style) {
CanvasFonts.Text rendered = CanvasFonts.text(text, x, y, size, alignment, this.canvasTransform, style);
this.operations.add(new DrawPlan.Op("text", rendered, new float[]{x, y, Float.intBitsToFloat(color), 0.0F, size}));
return rendered.end;
}
public DrawPlan alphaSprite(int texture, int atlasWidth, int atlasHeight, byte[] pixels, List<float[]> vertices, int color) {
CanvasFonts.Batch batch = new CanvasFonts.Batch(texture, atlasWidth, atlasHeight, pixels, vertices);
CanvasFonts.Text sprite = new CanvasFonts.Text("", Collections.singletonList(batch), 0.0F);
this.operations.add(new DrawPlan.Op("text", sprite, new float[]{0.0F, 0.0F, Float.intBitsToFloat(color), 0.0F, 0.0F}));
return this;
}
public DrawPlan item(ItemStack stack, float x, float y) {
this.operations.add(new DrawPlan.Op("item", stack, new float[]{x, y}));
return this;
}
public DrawPlan rect(float x, float y, float width, float height, float radius) {
return this.op("roundedRect", x, y, width, height, radius);
}
public DrawPlan clip(float x, float y, float width, float height) {
return this.op("screenClip", x, y, width, height);
}
public DrawPlan unclip() {
return this.op("unclip");
}
public DrawPlan roundedStroke(float x, float y, float width, float height, float radius) {
return this.op("roundedStroke", x, y, width, height, radius);
}
public DrawPlan opacity(float alpha) {
return this.op("opacity", alpha);
}
public DrawPlan gradient(RenderMath.Rect rect, float[] first, float[] second) {
return this.op("gradient", rect.x, rect.y, rect.width, rect.height, first[0], first[1], first[2], first[3], second[0], second[1], second[2], second[3]);
}
public DrawPlan pillGradient(RenderMath.Rect rect, float radius, float[]... stops) {
float[] values = new float[5 + stops.length * 4];
values[0] = rect.x;
values[1] = rect.y;
values[2] = rect.width;
values[3] = rect.height;
values[4] = radius;
for (int i = 0; i < stops.length; i++) {
System.arraycopy(stops[i], 0, values, 5 + i * 4, 4);
}
return this.op("pillGradient", values);
}
public DrawPlan circleStroke(float x, float y, float radius) {
return this.op("circleStroke", x, y, radius);
}
public DrawPlan rectVarying(float x, float y, float width, float height, float topLeft, float topRight, float bottomRight, float bottomLeft) {
return this.op("roundedRectVarying", x, y, width, height, topLeft, topRight, bottomRight, bottomLeft);
}
public DrawPlan enable(int capability) {
return this.op("enable", (float)capability);
}
public DrawPlan disable(int capability) {
return this.op("disable", (float)capability);
}
public static final class Op {
public final String kind;
public final float[] values;
public final Object value;
private Op(String kind, Object value, float... values) {
this.kind = kind;
this.value = value;
this.values = (float[])values.clone();
}
}
}
@@ -0,0 +1,131 @@
package dev.slinky.client.render;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final strictfp class NotificationQueue {
private final NotificationQueue.Message[] records = new NotificationQueue.Message[10];
private long start;
private int count;
private String enabledName;
private String disabledName;
private int enabledCount;
private int disabledCount;
public strictfp void append(int kind, String text) {
byte[] source = text.getBytes(StandardCharsets.UTF_8);
String limited = source.length < 101 ? text : new String(source, 0, 100, StandardCharsets.UTF_8);
this.records[(int)Long.remainderUnsigned(this.start + (long)this.count, 10L)] = new NotificationQueue.Message(kind, limited);
if (this.count == 10) {
this.start++;
} else {
this.count++;
}
}
public strictfp void state(String name, boolean enabled) {
if (enabled) {
this.enabledName = name;
this.enabledCount++;
} else {
this.disabledName = name;
this.disabledCount++;
}
}
public strictfp int advance(float elapsed, int oldFlags, boolean hidden) {
if (hidden) {
this.count = 0;
this.enabledCount = this.disabledCount = 0;
return oldFlags;
} else {
float expiry = RenderConstants.f(1384008);
while (this.count != 0 && this.records[(int)Long.remainderUnsigned(this.start, 10L)].age > expiry) {
this.start++;
this.count--;
}
if (this.enabledCount != 0) {
this.append(
1,
this.enabledCount == 1
? RenderConstants.text(1384300) + " " + this.enabledName
: RenderConstants.text(1384300) + " " + Integer.toUnsignedString(this.enabledCount) + " mods"
);
this.enabledCount = 0;
}
if (this.disabledCount != 0) {
this.append(
1,
this.disabledCount == 1
? RenderConstants.text(1384308) + " " + this.disabledName
: RenderConstants.text(1384308) + " " + Integer.toUnsignedString(this.disabledCount) + " mods"
);
this.disabledCount = 0;
}
if (this.count == 0) {
return oldFlags;
} else {
boolean transitioning = false;
double late = RenderConstants.d(1384016);
double early = RenderConstants.d(1384024);
for (NotificationQueue.Message m : this.messages()) {
m.age += elapsed;
if ((double)m.age > late || (double)m.age < early) {
transitioning = true;
}
}
return oldFlags | (transitioning ? 3 : 1);
}
}
}
public strictfp List<NotificationQueue.Message> messages() {
List<NotificationQueue.Message> result = new ArrayList<>();
for (int i = 0; i < this.count; i++) {
result.add(this.records[(int)Long.remainderUnsigned(this.start + (long)i, 10L)]);
}
return Collections.unmodifiableList(result);
}
public strictfp void clearMessages() {
this.count = 0;
}
public strictfp void clearAll() {
this.count = this.enabledCount = this.disabledCount = 0;
}
public static strictfp float opacity(float value) {
double age = (double)value;
double duration = RenderConstants.d(1384040);
float amount = RenderConstants.f(1384048);
if (duration > age) {
amount = RenderMath.smooth((float)(age / duration));
} else if (age > RenderConstants.d(1384056)) {
amount = RenderMath.smooth((float)((RenderConstants.d(1384064) - age) / duration));
}
return RenderMath.min(RenderConstants.f(1384048), amount);
}
public static final strictfp class Message {
public final int kind;
public final String text;
public float age;
private Message(int kind, String text) {
this.kind = kind;
this.text = text;
}
}
}
@@ -0,0 +1,457 @@
package dev.slinky.client.render;
import dev.slinky.client.ClientContext;
import dev.slinky.client.event.SemanticEvent;
import dev.slinky.client.render.canvas.CanvasPath;
import dev.slinky.client.render.canvas.CanvasState;
import dev.slinky.client.render.canvas.GlCanvasBackend;
import dev.slinky.client.render.font.CanvasFonts;
import dev.slinky.client.render.hook.GlStateScope;
import dev.slinky.inject.ReflectionHelper;
import dev.slinky.setting.ColorSetting;
import java.lang.reflect.Method;
import java.nio.FloatBuffer;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.function.DoubleSupplier;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.EntityRenderer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.entity.RenderItem;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.item.ItemStack;
import net.minecraft.scoreboard.ScorePlayerTeam;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
public final class RenderAccess {
private static float[] model;
private static float[] projection;
private static Consumer<DrawPlan> sink = RenderAccess::execute;
private static DoubleSupplier clock = () -> (double)System.nanoTime() * 1.0E-9;
private static Method fovModifier;
private RenderAccess() {
}
public static double now() {
return clock.getAsDouble();
}
public static DoubleSupplier bindClock(DoubleSupplier next) {
DoubleSupplier previous = clock;
clock = Objects.requireNonNull(next);
return previous;
}
public static Consumer<DrawPlan> bindSink(Consumer<DrawPlan> next) {
Consumer<DrawPlan> previous = sink;
sink = Objects.requireNonNull(next);
return previous;
}
public static void submit(SemanticEvent event, DrawPlan plan) {
if (!event.cancelled) {
sink.accept(plan);
}
}
public static void captureWorldProjection() {
FloatBuffer m = BufferUtils.createFloatBuffer(16);
FloatBuffer p = BufferUtils.createFloatBuffer(16);
GL11.glGetFloat(2982, m);
GL11.glGetFloat(2983, p);
float[] ma = new float[16];
float[] pa = new float[16];
m.get(ma);
p.get(pa);
model = ma;
projection = pa;
}
public static void setProjection(float[] m, float[] p) {
model = (float[])m.clone();
projection = (float[])p.clone();
}
public static void clearProjection() {
projection = null;
model = null;
}
public static void releaseCanvasResources() {
CanvasFonts.release();
}
public static RenderAccess.Frame frame(ClientContext c, SemanticEvent e) {
return model != null && projection != null && e.renderWidth > 0.0F && e.renderHeight > 0.0F
? new RenderAccess.Frame(e.renderWidth, e.renderHeight, model, projection, new double[]{e.cameraX, e.cameraY, e.cameraZ})
: null;
}
public static RenderAccess.Frame fixtureFrame(float width, float height, float[] m, float[] p, double[] camera) {
return new RenderAccess.Frame(width, height, m, p, camera);
}
public static double[] position(Entity e) {
return new double[]{e.posX, e.posY, e.posZ};
}
public static double[] previous(Entity e) {
return new double[]{e.prevPosX, e.prevPosY, e.prevPosZ};
}
public static boolean arrowInGround(EntityArrow arrow) {
try {
return ReflectionHelper.findField(EntityArrow.class, "inGround", "field_70254_i").getBoolean(arrow);
} catch (IllegalAccessException var2) {
throw new IllegalStateException(var2);
}
}
public static float width(ClientContext c, String text) {
return (float)c.minecraft().fontRendererObj.getStringWidth(text);
}
public static float textHeight(ClientContext c) {
return (float)c.minecraft().fontRendererObj.FONT_HEIGHT;
}
public static float fov(ClientContext c, float partial) {
if (fovModifier == null) {
fovModifier = ReflectionHelper.findMethod(EntityRenderer.class, null, new String[]{"getFOVModifier", "func_78481_a"}, float.class, boolean.class);
}
try {
return (Float)fovModifier.invoke(c.minecraft().entityRenderer, partial, true);
} catch (ReflectiveOperationException var3) {
throw new IllegalStateException(var3);
}
}
public static ColorSetting.Value nameColor(EntityPlayer player) {
String prefix = player.getTeam() instanceof ScorePlayerTeam ? ((ScorePlayerTeam)player.getTeam()).getColorPrefix() : "";
int index = -1;
for (int i = 0; i + 1 < prefix.length(); i++) {
if (prefix.charAt(i) == 167) {
int code = "0123456789abcdef".indexOf(Character.toLowerCase(prefix.charAt(i + 1)));
if (code >= 0) {
index = code;
}
}
}
int rgb = index < 0 ? 16777215 : Minecraft.getMinecraft().fontRendererObj.getColorCode("0123456789abcdef".charAt(index));
return new ColorSetting.Value((float)(rgb >>> 16 & 0xFF) / 255.0F, (float)(rgb >>> 8 & 0xFF) / 255.0F, (float)(rgb & 0xFF) / 255.0F);
}
private static void execute(DrawPlan plan) {
try (GlStateScope attributes = GlStateScope.open()) {
GL11.glPushMatrix();
CanvasState canvas = canvas();
CanvasState.Stats stats = new CanvasState.Stats();
GlCanvasBackend backend = new GlCanvasBackend();
int pushes = 0;
float alpha = 1.0F;
float savedItemDepth = Minecraft.getMinecraft().getRenderItem().zLevel;
Deque<Float> alphas = new ArrayDeque<>();
try {
GlStateManager.enableBlend();
GlStateManager.tryBlendFuncSeparate(770, 771, 1, 771);
GlStateManager.disableTexture2D();
for (DrawPlan.Op op : plan.operations()) {
float[] v = op.values;
if (op.kind.equals("push")) {
GL11.glPushMatrix();
canvas.save();
pushes++;
alphas.push(alpha);
} else if (op.kind.equals("pop")) {
if (pushes <= 0) {
throw new IllegalStateException("Unbalanced drawing matrix");
}
GL11.glPopMatrix();
canvas.restore();
pushes--;
alpha = alphas.pop();
} else if (op.kind.equals("translate")) {
GL11.glTranslatef(v[0], v[1], v[2]);
canvas.translate(v[0], v[1]);
} else if (op.kind.equals("scale")) {
GL11.glScalef(v[0], v[1], v[2]);
canvas.scale(v[0], v[1]);
} else if (op.kind.equals("rotate")) {
GL11.glRotatef(v[0], v[1], v[2], v[3]);
} else if (op.kind.equals("color")) {
GlStateManager.color(v[0], v[1], v[2], v[3] * alpha);
canvas.fillColor(v);
canvas.strokeColor(v);
} else if (op.kind.equals("opacity")) {
alpha = v[0];
canvas.selected().alpha = alpha;
} else if (op.kind.equals("screenClip")) {
GL11.glEnable(3089);
GL11.glScissor((int)v[0], Minecraft.getMinecraft().displayHeight - (int)(v[1] + v[3]), Math.max(0, (int)v[2]), Math.max(0, (int)v[3]));
} else if (op.kind.equals("unclip")) {
GL11.glDisable(3089);
} else if (op.kind.equals("gradient")) {
CanvasState.Paint paint = CanvasState.linearGradient(
v[0], v[1], v[0] + v[2], v[1], new float[]{v[4], v[5], v[6], v[7]}, new float[]{v[8], v[9], v[10], v[11]}
);
canvas.fillPaint(paint);
shape(canvas, new CanvasPath(0.25F, 0.01F, 1.0F).rectangle(v[0], v[1], v[2], v[3]), false, backend, stats);
} else if (op.kind.equals("lineWidth")) {
GL11.glLineWidth(v[0]);
canvas.selected().lineWidth = v[0];
} else if (op.kind.equals("enable")) {
capability((int)v[0], true);
} else if (op.kind.equals("disable")) {
capability((int)v[0], false);
} else if (op.kind.equals("vertices2") && op.value == Boolean.TRUE) {
shape(canvas, new CanvasPath(0.25F, 0.01F, 1.0F).command(0.0F, v[1], v[2], 1.0F, v[3], v[4]), true, backend, stats);
} else if (op.kind.startsWith("vertices")) {
int dimensions = op.kind.equals("vertices2") ? 2 : 3;
GL11.glBegin((int)v[0]);
try {
for (int i = 1; i < v.length; i += dimensions) {
GL11.glVertex3f(v[i], v[i + 1], dimensions == 3 ? v[i + 2] : 0.0F);
}
} finally {
GL11.glEnd();
}
} else if (op.kind.equals("roundedRect") || op.kind.equals("roundedStroke")) {
shape(
canvas,
new CanvasPath(0.25F, 0.01F, 1.0F).rounded(v[0], v[1], v[2], v[3], v[4], v[4], v[4], v[4]),
op.kind.equals("roundedStroke"),
backend,
stats
);
} else if (op.kind.equals("roundedRectVarying")) {
shape(canvas, new CanvasPath(0.25F, 0.01F, 1.0F).rounded(v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]), false, backend, stats);
} else if (op.kind.equals("circle")) {
shape(canvas, new CanvasPath(0.25F, 0.01F, 1.0F).ellipse(v[0], v[1], v[2], v[2]), false, backend, stats);
} else if (op.kind.equals("circleStroke")) {
shape(canvas, new CanvasPath(0.25F, 0.01F, 1.0F).ellipse(v[0], v[1], v[2], v[2]), true, backend, stats);
} else if (op.kind.equals("pillGradient")) {
pillGradient(canvas.selected().transform, v, alpha);
} else if (op.kind.equals("itemDepth")) {
Minecraft.getMinecraft().getRenderItem().zLevel = v[0];
} else if (op.kind.equals("text") && op.value instanceof CanvasFonts.Text) {
CanvasFonts.render((CanvasFonts.Text)op.value, Float.floatToRawIntBits(v[2]), alpha);
} else if (op.kind.equals("text")) {
GlStateManager.enableTexture2D();
GL11.glPushMatrix();
try {
float factor = v[4] / (float)Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT;
GL11.glTranslatef(v[0], v[1], 0.0F);
GL11.glScalef(factor, factor, 1.0F);
int color = Float.floatToRawIntBits(v[2]);
int opacity = (int)((float)(color >>> 24) * alpha);
Minecraft.getMinecraft().fontRendererObj.drawString((String)op.value, 0.0F, 0.0F, opacity << 24 | color & 16777215, v[3] != 0.0F);
} finally {
GL11.glPopMatrix();
GlStateManager.disableTexture2D();
}
} else if (op.kind.equals("item")) {
GlStateManager.enableTexture2D();
RenderHelper.enableGUIStandardItemLighting();
try {
Minecraft.getMinecraft().getRenderItem().renderItemAndEffectIntoGUI((ItemStack)op.value, (int)v[0], (int)v[1]);
Minecraft.getMinecraft().getRenderItem().renderItemOverlays(Minecraft.getMinecraft().fontRendererObj, (ItemStack)op.value, (int)v[0], (int)v[1]);
} finally {
RenderHelper.disableStandardItemLighting();
GlStateManager.disableTexture2D();
}
}
}
} finally {
Minecraft.getMinecraft().getRenderItem().zLevel = savedItemDepth;
while (pushes-- > 0) {
GL11.glPopMatrix();
}
GL11.glPopMatrix();
}
}
}
private static void pillGradient(float[] transform, float[] v, float alpha) {
float x = v[0];
float y = v[1];
float width = v[2];
float height = v[3];
float radius = Math.max(0.0F, Math.min(v[4], Math.min(height, width) * 0.5F));
int stops = (v.length - 5) / 4;
if (stops >= 1 && !(width <= 0.0F) && !(height <= 0.0F)) {
TreeSet<Float> columns = new TreeSet<>();
int capSteps = Math.max(4, (int)Math.ceil((double)(radius * 2.0F)));
for (int i = 0; i <= capSteps; i++) {
float inset = radius - radius * (float)Math.cos((Math.PI / 2) * (double)i / (double)capSteps);
columns.add(x + inset);
columns.add(x + width - inset);
}
for (int i = 0; i < stops; i++) {
columns.add(x + (stops == 1 ? 0.0F : width * (float)i / (float)(stops - 1)));
}
float middle = y + height * 0.5F;
float straight = height * 0.5F - radius;
float[] color = new float[4];
GL11.glShadeModel(7425);
GL11.glPushMatrix();
GL11.glLoadIdentity();
GL11.glBegin(5);
try {
for (float px : columns) {
float dx = px < x + radius ? x + radius - px : (px > x + width - radius ? px - (x + width - radius) : 0.0F);
float half = straight + (float)Math.sqrt((double)Math.max(0.0F, radius * radius - dx * dx));
if (stops == 1) {
System.arraycopy(v, 5, color, 0, 4);
} else {
float position = Math.max(0.0F, Math.min(1.0F, (px - x) / width)) * (float)(stops - 1);
int index = Math.min(stops - 2, (int)position);
float f = position - (float)index;
for (int c = 0; c < 4; c++) {
color[c] = v[5 + index * 4 + c] + (v[5 + (index + 1) * 4 + c] - v[5 + index * 4 + c]) * f;
}
}
GL11.glColor4f(color[0], color[1], color[2], color[3] * alpha);
vertex(transform, px, middle - half);
vertex(transform, px, middle + half);
}
} finally {
GL11.glEnd();
GL11.glPopMatrix();
}
}
}
private static void vertex(float[] m, float x, float y) {
GL11.glVertex2f(m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]);
}
private static void capability(int value, boolean enable) {
switch (value) {
case 2884:
if (enable) {
GlStateManager.enableCull();
} else {
GlStateManager.disableCull();
}
break;
case 2896:
if (enable) {
GlStateManager.enableLighting();
} else {
GlStateManager.disableLighting();
}
break;
case 2929:
if (enable) {
GlStateManager.enableDepth();
} else {
GlStateManager.disableDepth();
}
break;
case 3008:
if (enable) {
GlStateManager.enableAlpha();
} else {
GlStateManager.disableAlpha();
}
break;
case 3042:
if (enable) {
GlStateManager.enableBlend();
} else {
GlStateManager.disableBlend();
}
break;
case 3553:
if (enable) {
GlStateManager.enableTexture2D();
} else {
GlStateManager.disableTexture2D();
}
break;
default:
if (enable) {
GL11.glEnable(value);
} else {
GL11.glDisable(value);
}
}
}
public static CanvasState canvas() {
CanvasState.State initial = new CanvasState.State();
initial.transform[0] = initial.transform[3] = 1.0F;
initial.alpha = 1.0F;
initial.lineWidth = 1.0F;
initial.miter = 10.0F;
initial.cap = 0;
initial.join = 1;
initial.shapeAntialias = 1;
initial.scissorExtent[0] = initial.scissorExtent[1] = -1.0F;
initial.composite[0] = 770;
initial.composite[2] = 1;
initial.composite[1] = initial.composite[3] = 771;
return new CanvasState(initial);
}
private static void shape(CanvasState canvas, CanvasPath path, boolean stroke, CanvasState.Backend backend, CanvasState.Stats stats) {
path.transform(canvas.selected().transform);
GL11.glPushMatrix();
try {
GL11.glLoadIdentity();
if (stroke) {
canvas.stroke(path, backend, stats, true);
} else {
canvas.fill(path, backend, stats, true);
}
} finally {
GL11.glPopMatrix();
}
}
public static final class Frame {
public final float width;
public final float height;
public final float[] model;
public final float[] projection;
public final double[] camera;
private Frame(float w, float h, float[] m, float[] p, double[] c) {
this.width = w;
this.height = h;
this.model = (float[])m.clone();
this.projection = (float[])p.clone();
this.camera = (double[])c.clone();
}
public RenderMath.Projection project(double[] relative) {
return RenderMath.project(relative, this.model, this.projection, this.width, this.height);
}
}
}
@@ -0,0 +1,118 @@
package dev.slinky.client.render;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import dev.slinky.inject.Platform;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public final class RenderConstants {
private static final Map<Integer, byte[]> SPANS = new LinkedHashMap<>();
private static final long CAPTURE_BASE = captureBase();
private static final ConcurrentHashMap<Integer, String> TEXTS;
private static long captureBase() {
try (Reader r = new InputStreamReader(Platform.resource("/assets/slinkysemantic/MetadataCatalog.json"), "UTF-8")) {
return Long.decode(new JsonParser().parse(r).getAsJsonObject().get("runtime_base").getAsString());
} catch (IOException var15) {
throw new ExceptionInInitializerError(var15);
}
}
private RenderConstants() {
}
private static ByteBuffer at(int address, int count) {
for (Entry<Integer, byte[]> row : SPANS.entrySet()) {
int offset = address - row.getKey();
if (offset >= 0 && offset + count <= row.getValue().length) {
return ByteBuffer.wrap(row.getValue(), offset, count).order(ByteOrder.LITTLE_ENDIAN);
}
}
throw new IllegalArgumentException("Uncaptured immutable drawing operand 0x" + Integer.toHexString(address));
}
public static int i(int address) {
return at(address, 4).getInt();
}
public static long l(int address) {
return at(address, 8).getLong();
}
public static float f(int address) {
return at(address, 4).getFloat();
}
public static double d(int address) {
return at(address, 8).getDouble();
}
public static int u16(int address) {
return at(address, 2).getShort() & 65535;
}
public static String text(int address) {
String cached = TEXTS.get(address);
if (cached == null) {
TEXTS.put(address, cached = decode(address));
}
return cached;
}
private static String decode(int address) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
for (int n = 0; n < 256; n++) {
int b = at(address + n, 1).get() & 255;
if (b == 0) {
break;
}
bytes.write(b);
}
try {
return new String(bytes.toByteArray(), "UTF-8");
} catch (UnsupportedEncodingException var4) {
throw new AssertionError(var4);
}
}
public static String pointerText(int address) {
long p = l(address);
return p == 0L ? null : text((int)(p - CAPTURE_BASE));
}
static {
try (Reader r = new InputStreamReader(Platform.resource("/assets/slinkysemantic/RenderConstants.json"), "UTF-8")) {
for (JsonElement e : new JsonParser().parse(r).getAsJsonArray()) {
JsonObject row = e.getAsJsonObject();
String hex = row.get("bytes").getAsString();
byte[] bytes = new byte[hex.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte)Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
}
SPANS.put(row.get("address").getAsInt(), bytes);
}
} catch (IOException var18) {
throw new ExceptionInInitializerError(var18);
}
TEXTS = new ConcurrentHashMap<>();
}
}
@@ -0,0 +1,38 @@
package dev.slinky.client.render;
import dev.slinky.client.event.SemanticEvent;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import net.minecraft.world.World;
public final class RenderEventPayload {
private static final Map<SemanticEvent, RenderEventPayload> ATTACHED = Collections.synchronizedMap(new WeakHashMap<>());
public int redrawFlags;
public int action;
public int errorCode;
public int regionKind;
public int x;
public int y;
public int z;
public int blockId;
public String profileName;
public String message;
public boolean friendAdded;
public Runnable guiDraw;
public World world;
public static RenderEventPayload attach(SemanticEvent event) {
RenderEventPayload payload = new RenderEventPayload();
ATTACHED.put(event, payload);
return payload;
}
public static RenderEventPayload get(SemanticEvent event) {
return ATTACHED.get(event);
}
public static void detach(SemanticEvent event) {
ATTACHED.remove(event);
}
}
@@ -0,0 +1,137 @@
package dev.slinky.client.render;
import dev.slinky.setting.ColorSetting;
public final strictfp class RenderMath {
private RenderMath() {
}
private static strictfp float model(float[] m, int i, float x, float y, float z) {
return m[i + 8] * z + (m[i] * x + m[i + 4] * y) + m[i + 12];
}
private static strictfp float row(float[] m, int i, float x, float y, float z, float w) {
return m[i + 12] * w + (m[i + 8] * z + (m[i] * x + m[i + 4] * y));
}
public static strictfp RenderMath.Projection project(double[] p, float[] model, float[] projection, float width, float height) {
float x = (float)p[0];
float y = (float)p[1];
float z = (float)p[2];
float mx = model(model, 0, x, y, z);
float my = model(model, 1, x, y, z);
float mz = model(model, 2, x, y, z);
float mw = z * model[11] + (x * model[3] + y * model[7]) + model[15];
float w = row(projection, 3, mx, my, mz, mw);
if (w == 0.0F) {
return null;
} else {
float one = RenderConstants.f(1375232);
float half = RenderConstants.f(1375236);
float reciprocal = one / w * half;
float depth = row(projection, 2, mx, my, mz, mw) * reciprocal + half;
if (!(0.0F >= depth) && !(depth >= one)) {
float py = row(projection, 1, mx, my, mz, mw) * reciprocal + half;
float px = (mw * projection[12] + (mz * projection[8] + (mx * projection[0] + my * projection[4]))) * reciprocal + half;
return new RenderMath.Projection(px * width, height - py * height, reciprocal);
} else {
return null;
}
}
}
public static strictfp float lerp(float a, float b, float t) {
return a + t * (b - a);
}
public static strictfp double[] lerp(double[] a, double[] b, double t) {
return new double[]{a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1]), a[2] + t * (b[2] - a[2])};
}
public static strictfp double[] subtract(double[] a, double[] b) {
return new double[]{a[0] - b[0], a[1] - b[1], a[2] - b[2]};
}
public static strictfp double[] add(double[] a, double[] b) {
return new double[]{a[0] + b[0], a[1] + b[1], a[2] + b[2]};
}
public static strictfp double[] scale(double[] a, double n) {
return new double[]{a[0] * n, a[1] * n, a[2] * n};
}
public static strictfp double distanceSquared(double[] a, double[] b) {
double x = a[0] - b[0];
double y = a[1] - b[1];
double z = a[2] - b[2];
return z * z + (x * x + y * y);
}
public static strictfp float min(float a, float b) {
return a < b ? a : b;
}
public static strictfp float max(float a, float b) {
return a > b ? a : b;
}
public static strictfp float clamp(float lo, float hi, float value) {
return value < lo ? lo : min(hi, value);
}
public static strictfp float smooth(float value) {
float square = value * value;
return (RenderConstants.f(1304788) - (value + value)) * square;
}
public static strictfp int compareDistance(float a, float b) {
return Float.compare(b, a);
}
public static strictfp ColorSetting.Value blend(ColorSetting.Value a, ColorSetting.Value b, float t) {
return new ColorSetting.Value(lerp(a.red, b.red, t), lerp(a.green, b.green, t), lerp(a.blue, b.blue, t));
}
public static strictfp boolean visibleBounds(float minx, float miny, float maxx, float maxy, float width, float height) {
return !(0.0F > minx) && !(0.0F > miny) && !(minx > width) && !(miny > height) || !(0.0F > maxx) && !(maxx > width) && !(0.0F > maxy) && !(maxy > height);
}
public static strictfp boolean catalogCharacter(int character) {
int b = character & 0xFF;
return ((b & 223) + 191 & 0xFF) < 26 || b == 32 || (b - 48 & 0xFF) < 10;
}
public static final strictfp class Projection {
public final float x;
public final float y;
public final float reciprocal;
public Projection(float x, float y, float reciprocal) {
this.x = x;
this.y = y;
this.reciprocal = reciprocal;
}
}
public static final strictfp class Rect {
public final float x;
public final float y;
public final float width;
public final float height;
public Rect(float x, float y, float w, float h) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
}
public strictfp boolean contains(float px, float py) {
return px >= this.x && py >= this.y && this.x + this.width > px && this.y + this.height > py;
}
public strictfp RenderMath.Rect inset(float amount) {
return new RenderMath.Rect(amount + this.x, amount + this.y, this.width - (amount + amount), this.height - (amount + amount));
}
}
}
@@ -0,0 +1,750 @@
package dev.slinky.client.render.canvas;
import dev.slinky.client.numeric.FloatTrigonometry;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.client.numeric.NumericServices;
import dev.slinky.client.render.RenderConstants;
import dev.slinky.client.render.RenderMath;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final strictfp class CanvasPath {
private final List<Float> commands = new ArrayList<>();
private final List<CanvasPath.Path> paths = new ArrayList<>();
public final float[] bounds = new float[4];
public final float tolerance;
public final float distanceTolerance;
public final float fringe;
public CanvasPath(float tolerance, float distanceTolerance, float fringe) {
this.tolerance = tolerance;
this.distanceTolerance = distanceTolerance;
this.fringe = fringe;
}
private static strictfp float c(int r) {
return RenderConstants.f(r);
}
private static strictfp float neg(float x) {
return Float.intBitsToFloat(Float.floatToRawIntBits(x) ^ Integer.MIN_VALUE);
}
private static strictfp float abs(float x) {
float n = neg(x);
return n <= x ? x : n;
}
public strictfp CanvasPath command(float... values) {
for (float f : values) {
this.commands.add(f);
}
this.paths.clear();
return this;
}
public strictfp CanvasPath transform(float[] matrix) {
int i = 0;
while (i < this.commands.size()) {
int kind = (int)this.commands.get(i).floatValue();
int pairs = kind != 0 && kind != 1 ? (kind == 2 ? 3 : 0) : 1;
for (int j = 0; j < pairs; j++) {
int at = i + 1 + j * 2;
float x = this.commands.get(at);
float y = this.commands.get(at + 1);
this.commands.set(at, x * matrix[0] + y * matrix[2] + matrix[4]);
this.commands.set(at + 1, x * matrix[1] + y * matrix[3] + matrix[5]);
}
i += kind != 0 && kind != 1 ? (kind == 2 ? 7 : (kind == 4 ? 2 : 1)) : 3;
}
this.paths.clear();
return this;
}
public strictfp CanvasPath rectangle(float x, float y, float w, float h) {
return this.command(0.0F, x, y, 1.0F, x, h + y, 1.0F, w + x, h + y, 1.0F, w + x, y, 3.0F);
}
public strictfp CanvasPath rounded(float x, float y, float w, float h, float tl, float tr, float br, float bl) {
float threshold = c(2839776);
if (threshold > tl && threshold > tr && threshold > br && threshold > bl) {
return this.rectangle(x, y, w, h);
} else {
float nw = neg(w);
float nh = neg(h);
float hw = (w >= nw ? w : nw) * c(2839808);
float hh = (h >= nh ? h : nh) * c(2839808);
float sx = c(nw <= w ? 2839812 : 2839816);
float sy = c(nh <= h ? 2839812 : 2839816);
float xb = RenderMath.min(bl, hw) * sx;
float yb = RenderMath.min(bl, hh) * sy;
float xr = RenderMath.min(br, hw) * sx;
float yr = RenderMath.min(br, hh) * sy;
float xt = RenderMath.min(tr, hw) * sx;
float yt = RenderMath.min(tr, hh) * sy;
float xl = RenderMath.min(tl, hw) * sx;
float yl = RenderMath.min(tl, hh) * sy;
float bottom = h + y;
float right = w + x;
float k = c(2839840);
float k0 = c(2839824);
float k1 = c(2839828);
return this.command(
0.0F,
x,
y + yl,
1.0F,
x,
bottom - yb,
2.0F,
x,
neg(yb) * k0 + bottom,
xb * k1 + x,
bottom,
xb + x,
bottom,
1.0F,
right - xr,
bottom,
2.0F,
right - xr * k,
bottom,
right,
bottom - yr * k,
right,
bottom - yr,
1.0F,
right,
yt + y,
2.0F,
right,
yt * k0 + y,
neg(xt) * k1 + right,
y,
right - xt,
y,
1.0F,
x + xl,
y,
2.0F,
xl * k + x,
y,
x,
yl * k + y,
x,
y + yl,
3.0F
);
}
}
public strictfp CanvasPath ellipse(float x, float y, float rx, float ry) {
float a = c(2839872);
float b = c(2839876);
float left = x - rx;
float right = x + rx;
float bottom = y + ry;
float top = y - ry;
float bc = ry * a + y;
float lc = neg(rx) * b + x;
float tc = neg(ry) * a + y;
float rc = rx * b + x;
return this.command(
0.0F,
left,
y,
2.0F,
left,
bc,
lc,
bottom,
x,
bottom,
2.0F,
rc,
bottom,
right,
bc,
right,
y,
2.0F,
right,
tc,
rc,
top,
x,
top,
2.0F,
lc,
top,
left,
tc,
left,
y,
3.0F
);
}
public strictfp List<Float> commands() {
return Collections.unmodifiableList(this.commands);
}
public strictfp List<CanvasPath.Path> paths() {
this.flatten();
return Collections.unmodifiableList(this.paths);
}
private strictfp void point(CanvasPath.Path path, float x, float y, int flags) {
if (!path.points.isEmpty()) {
CanvasPath.Point p = path.points.get(path.points.size() - 1);
float dx = x - p.x;
float dy = y - p.y;
if (this.distanceTolerance * this.distanceTolerance > dx * dx + dy * dy) {
p.flags |= flags;
return;
}
}
path.points.add(new CanvasPath.Point(x, y, flags));
}
private strictfp void bezier(CanvasPath.Path p, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, int level, int flags) {
if (level <= 10) {
float dx = x4 - x1;
float dy = y4 - y1;
float d2 = abs((x2 - x4) * dy - (y2 - y4) * dx);
float d3 = abs((x3 - x4) * dy - (y3 - y4) * dx);
float distance = d3 + d2;
if ((dx * dx + dy * dy) * this.tolerance > distance * distance) {
this.point(p, x4, y4, flags);
} else {
float half = c(2842720);
float x12 = (x1 + x2) * half;
float y12 = (y1 + y2) * half;
float x23 = (x2 + x3) * half;
float y23 = (y2 + y3) * half;
float x34 = (x4 + x3) * half;
float y34 = (y4 + y3) * half;
float x123 = (x23 + x12) * half;
float y123 = (y23 + y12) * half;
float x234 = (x23 + x34) * half;
float y234 = (y23 + y34) * half;
float x = (x234 + x123) * half;
float y = (y234 + y123) * half;
this.bezier(p, x1, y1, x12, y12, x123, y123, x, y, level + 1, 0);
this.bezier(p, x, y, x234, y234, x34, y34, x4, y4, level + 1, flags);
}
}
}
public strictfp void flatten() {
if (this.paths.isEmpty()) {
CanvasPath.Path p = null;
int i = 0;
while (i < this.commands.size()) {
int kind = NumericMath.truncateInt(this.commands.get(i));
if (kind == 0) {
p = new CanvasPath.Path();
this.paths.add(p);
}
if (kind != 0 && kind != 1) {
if (kind == 2) {
if (p != null && !p.points.isEmpty()) {
CanvasPath.Point previous = p.points.get(p.points.size() - 1);
this.bezier(
p,
previous.x,
previous.y,
this.commands.get(i + 1),
this.commands.get(i + 2),
this.commands.get(i + 3),
this.commands.get(i + 4),
this.commands.get(i + 5),
this.commands.get(i + 6),
0,
1
);
}
i += 7;
} else if (kind == 3) {
if (p != null) {
p.closed = true;
}
i++;
} else if (kind == 4) {
if (p != null) {
p.winding = NumericMath.truncateInt(this.commands.get(i + 1));
}
i += 2;
} else {
i++;
}
} else {
if (p != null) {
this.point(p, this.commands.get(i + 1), this.commands.get(i + 2), 1);
}
i += 3;
}
}
for (int j = 0; j < 4; j++) {
this.bounds[j] = c(2839936 + 4 * j);
}
float half = c(2839952);
float threshold = c(2839956);
float one = c(2839960);
for (CanvasPath.Path path : this.paths) {
List<CanvasPath.Point> ps = path.points;
if (!ps.isEmpty()) {
CanvasPath.Point first = ps.get(0);
CanvasPath.Point last = ps.get(ps.size() - 1);
float dx = first.x - last.x;
float dy = first.y - last.y;
if (this.distanceTolerance * this.distanceTolerance > dx * dx + dy * dy) {
ps.remove(ps.size() - 1);
path.closed = true;
}
if (ps.size() > 2) {
float area = 0.0F;
for (int ix = 2; ix < ps.size(); ix++) {
CanvasPath.Point a = ps.get(ix - 1);
CanvasPath.Point b = ps.get(ix);
area += (b.x - first.x) * (a.y - first.y) - (b.y - first.y) * (a.x - first.x);
}
area *= half;
if (path.winding == 1 && 0.0F > area || path.winding == 2 && area > 0.0F) {
Collections.reverse(ps);
}
}
if (!ps.isEmpty()) {
CanvasPath.Point previous = ps.get(ps.size() - 1);
for (CanvasPath.Point next : ps) {
dx = next.x - previous.x;
dy = next.y - previous.y;
float length = (float)StrictMath.sqrt((double)(dx * dx + dy * dy));
if (length > threshold) {
float inverse = one / length;
dx *= inverse;
dy *= inverse;
}
previous.dx = dx;
previous.dy = dy;
previous.length = length;
this.bounds[0] = RenderMath.min(this.bounds[0], previous.x);
this.bounds[1] = RenderMath.min(this.bounds[1], previous.y);
this.bounds[2] = RenderMath.max(this.bounds[2], previous.x);
this.bounds[3] = RenderMath.max(this.bounds[3], previous.y);
previous = next;
}
}
}
}
}
}
public strictfp void joins(float width, int join, float miter) {
this.flatten();
float one = c(2842752);
float half = c(2842784);
float threshold = c(2842788);
float maximum = c(2842792);
float minimum = c(2842796);
float inverse = width > 0.0F ? one / width : 0.0F;
for (CanvasPath.Path path : this.paths) {
path.bevels = 0;
int left = 0;
if (!path.points.isEmpty()) {
CanvasPath.Point previous = path.points.get(path.points.size() - 1);
for (CanvasPath.Point p : path.points) {
p.mx = (previous.dy + p.dy) * half;
p.my = (neg(p.dx) - previous.dx) * half;
float square = p.mx * p.mx + p.my * p.my;
if (square > threshold) {
float scale = RenderMath.min(maximum, one / square);
p.mx *= scale;
p.my *= scale;
}
p.flags &= 1;
float cross = previous.dy * p.dx - previous.dx * p.dy;
if (cross > 0.0F) {
left++;
p.flags |= 2;
}
float limit = RenderMath.max(minimum, RenderMath.min(previous.length, p.length) * inverse);
if (one > square * limit * limit) {
p.flags |= 8;
}
if ((p.flags & 1) != 0 && ((join & -3) == 1 || one > square * miter * miter)) {
p.flags |= 4;
}
if ((p.flags & 12) != 0) {
path.bevels++;
}
previous = p;
}
path.convex = left == path.points.size();
}
}
}
private static strictfp void vertex(List<float[]> out, float x, float y, float u) {
vertex(out, x, y, u, 1.0F);
}
private static strictfp void vertex(List<float[]> out, float x, float y, float u, float v) {
out.add(new float[]{x, y, u, v});
}
private static strictfp float[][] bevel(boolean bevel, CanvasPath.Point p0, CanvasPath.Point p, float width) {
float[] a = bevel ? new float[]{p0.dy * width + p.x, p.y - p0.dx * width} : new float[]{p.mx * width + p.x, p.my * width + p.y};
float[] b = bevel ? new float[]{p.dy * width + p.x, p.y - p.dx * width} : (float[])a.clone();
return new float[][]{a, b};
}
public static strictfp void bevelJoin(List<float[]> out, CanvasPath.Point previous, CanvasPath.Point p, float lw, float rw, float lu, float ru) {
boolean left = (p.flags & 2) != 0;
float[][] ab = bevel((p.flags & 8) != 0, previous, p, left ? lw : neg(rw));
float sign = left ? neg(rw) : lw;
float ax = previous.dy * sign + p.x;
float ay = neg(previous.dx) * sign + p.y;
float bx = p.dy * sign + p.x;
float by = neg(p.dx) * sign + p.y;
float[] a = ab[0];
float[] b = ab[1];
if (left) {
vertex(out, a[0], a[1], lu);
vertex(out, ax, ay, ru);
} else {
vertex(out, ax, ay, lu);
vertex(out, a[0], a[1], ru);
}
if ((p.flags & 4) != 0) {
if (left) {
vertex(out, a[0], a[1], lu);
vertex(out, ax, ay, ru);
vertex(out, b[0], b[1], lu);
vertex(out, bx, by, ru);
} else {
vertex(out, ax, ay, lu);
vertex(out, a[0], a[1], ru);
vertex(out, bx, by, lu);
vertex(out, b[0], b[1], ru);
}
} else {
float cx = p.mx * sign + p.x;
float cy = p.my * sign + p.y;
if (left) {
vertex(out, p.x, p.y, 0.5F);
vertex(out, ax, ay, ru);
vertex(out, cx, cy, ru);
vertex(out, cx, cy, ru);
vertex(out, p.x, p.y, 0.5F);
vertex(out, bx, by, ru);
} else {
vertex(out, ax, ay, lu);
vertex(out, p.x, p.y, 0.5F);
vertex(out, cx, cy, lu);
vertex(out, cx, cy, lu);
vertex(out, bx, by, lu);
vertex(out, p.x, p.y, 0.5F);
}
}
if (left) {
vertex(out, b[0], b[1], lu);
vertex(out, bx, by, ru);
} else {
vertex(out, bx, by, lu);
vertex(out, b[0], b[1], ru);
}
}
public strictfp void expandFill(float width) {
this.joins(width, 4, c(2839984));
boolean convex = this.paths.size() == 1 && this.paths.get(0).convex;
float offset = this.fringe * c(2839988);
float left = convex ? offset : width + offset;
float right = width - offset;
float lu = convex ? c(2839988) : 0.0F;
for (CanvasPath.Path path : this.paths) {
path.fill.clear();
path.stroke.clear();
if (!path.points.isEmpty()) {
CanvasPath.Point previous = path.points.get(path.points.size() - 1);
for (CanvasPath.Point p : path.points) {
if (width > 0.0F) {
if ((p.flags & 4) != 0 && (p.flags & 2) == 0) {
vertex(path.fill, previous.dy * offset + p.x, neg(previous.dx) * offset + p.y, 0.5F);
vertex(path.fill, p.dy * offset + p.x, p.y - p.dx * offset, 0.5F);
} else {
vertex(path.fill, p.mx * offset + p.x, p.my * offset + p.y, 0.5F);
}
} else {
vertex(path.fill, p.x, p.y, 0.5F);
}
previous = p;
}
if (width > 0.0F) {
previous = path.points.get(path.points.size() - 1);
for (CanvasPath.Point p : path.points) {
if ((p.flags & 12) != 0) {
bevelJoin(path.stroke, previous, p, left, right, lu, 1.0F);
} else {
vertex(path.stroke, p.mx * left + p.x, p.my * left + p.y, lu);
vertex(path.stroke, p.x - p.mx * right, p.y - p.my * right, 1.0F);
}
previous = p;
}
float[] a = path.stroke.get(0);
float[] b = path.stroke.get(1);
vertex(path.stroke, a[0], a[1], lu);
vertex(path.stroke, b[0], b[1], 1.0F);
}
}
}
}
private static strictfp float[] direction(CanvasPath.Point a, CanvasPath.Point b) {
float x = b.x - a.x;
float y = b.y - a.y;
float length = (float)StrictMath.sqrt((double)(x * x + y * y));
float inverse = c(2840148) / length;
float dx = inverse * x;
float dy = inverse * y;
return length > c(2840144) ? new float[]{dx, dy} : new float[]{x, y};
}
private static strictfp void flatCap(
List<float[]> out, CanvasPath.Point p, float dx, float dy, float width, float distance, float aa, float u0, float u1, boolean end
) {
float px = end ? dx * distance + p.x : p.x - dx * distance;
float py = end ? dy * distance + p.y : p.y - dy * distance;
float ax = dy * width + px;
float ay = neg(dx) * width + py;
float bx = neg(dy) * width + px;
float by = dx * width + py;
float ex = (end ? dx : neg(dx)) * aa;
float ey = (end ? dy : neg(dy)) * aa;
if (end) {
vertex(out, ax, ay, u0);
vertex(out, bx, by, u1);
}
vertex(out, ex + ax, ey + ay, u0, 0.0F);
vertex(out, ex + bx, ey + by, u1, 0.0F);
if (!end) {
vertex(out, ax, ay, u0);
vertex(out, bx, by, u1);
}
}
private static strictfp void roundCap(List<float[]> out, CanvasPath.Point p, float dx, float dy, float width, int divisions, float u0, float u1, boolean end) {
if (end) {
vertex(out, dy * width + p.x, neg(dx) * width + p.y, u0);
vertex(out, neg(dy) * width + p.x, dx * width + p.y, u1);
}
float denominator = (float)(divisions - 1);
for (int i = 0; i < divisions; i++) {
float angle = (float)i / denominator * c(2840096);
float ax = FloatTrigonometry.cosine(angle) * width;
float ay = FloatTrigonometry.sine(angle) * width;
float x = (end ? dx : neg(dx)) * ay + (neg(dy) * ax + p.x);
float y = (end ? dy : neg(dy)) * ay + (dx * ax + p.y);
if (end) {
vertex(out, p.x, p.y, 0.5F);
}
vertex(out, x, y, u0);
if (!end) {
vertex(out, p.x, p.y, 0.5F);
}
}
if (!end) {
vertex(out, dy * width + p.x, neg(dx) * width + p.y, u0);
vertex(out, neg(dy) * width + p.x, dx * width + p.y, u1);
}
}
private static strictfp void roundJoin(List<float[]> out, CanvasPath.Point previous, CanvasPath.Point p, float width, float lu, float ru, int divisions) {
boolean left = (p.flags & 2) != 0;
float[][] ab = bevel((p.flags & 8) != 0, previous, p, left ? width : neg(width));
float a0 = NumericServices.atan2f(left ? previous.dx : neg(previous.dx), left ? neg(previous.dy) : previous.dy);
float a1 = NumericServices.atan2f(left ? p.dx : neg(p.dx), left ? neg(p.dy) : p.dy);
if (left ? a0 < a1 : a1 < a0) {
a1 += c(left ? 2840176 : 2840180);
}
float signed = left ? neg(width) : width;
float px = previous.dy * signed + p.x;
float py = neg(previous.dx) * signed + p.y;
float qx = p.dy * signed + p.x;
float qy = neg(p.dx) * signed + p.y;
float[] a = ab[0];
float[] b = ab[1];
if (left) {
vertex(out, a[0], a[1], lu);
vertex(out, px, py, ru);
} else {
vertex(out, px, py, lu);
vertex(out, a[0], a[1], ru);
}
float arc = left ? a0 - a1 : a1 - a0;
int count = NumericMath.truncateInt(NumericMath.ceiling(arc / c(2840096) * (float)divisions));
int n = count < 2 ? 2 : (count < divisions ? count : divisions);
for (int i = 0; i < n; i++) {
float angle = (float)i / (float)(n - 1) * (a1 - a0) + a0;
float x = FloatTrigonometry.cosine(angle) * width + p.x;
float y = FloatTrigonometry.sine(angle) * width + p.y;
if (left) {
vertex(out, p.x, p.y, 0.5F);
}
vertex(out, x, y, left ? ru : lu);
if (!left) {
vertex(out, p.x, p.y, 0.5F);
}
}
if (left) {
vertex(out, b[0], b[1], lu);
vertex(out, qx, qy, ru);
} else {
vertex(out, qx, qy, lu);
vertex(out, b[0], b[1], ru);
}
}
public strictfp void expandStroke(float width, float aa, int cap, int join, float miter) {
this.flatten();
float arc = NumericServices.acosf(width / (this.tolerance + width));
arc += arc;
int steps = NumericMath.truncateInt(NumericMath.ceiling(c(2840096) / arc));
int divisions = steps >= 3 ? steps : 2;
width = aa * c(2840100) + width;
float u0 = aa == 0.0F ? c(2840100) : 0.0F;
float u1 = c(aa == 0.0F ? 2840108 : 2840104);
float butt = aa * c(2840112);
float square = width - aa;
this.joins(width, join, miter);
for (CanvasPath.Path path : this.paths) {
path.fill.clear();
path.stroke.clear();
int count = path.points.size();
if (count >= 2) {
CanvasPath.Point previous = path.closed ? path.points.get(count - 1) : path.points.get(0);
CanvasPath.Point p = path.closed ? path.points.get(0) : path.points.get(1);
if (!path.closed) {
float[] dir = direction(previous, p);
if (cap == 0 || cap == 2) {
flatCap(path.stroke, previous, dir[0], dir[1], width, cap == 0 ? butt : square, aa, u0, u1, false);
} else if (cap == 1) {
roundCap(path.stroke, previous, dir[0], dir[1], width, divisions, u0, u1, false);
}
}
int start = path.closed ? 0 : 1;
int end = path.closed ? count : count - 1;
for (int i = start; i < end; i++) {
p = path.points.get(i);
if ((p.flags & 12) != 0) {
if (join == 1) {
roundJoin(path.stroke, previous, p, width, u0, u1, divisions);
} else {
bevelJoin(path.stroke, previous, p, width, width, u0, u1);
}
} else {
vertex(path.stroke, p.mx * width + p.x, p.my * width + p.y, u0);
vertex(path.stroke, p.x - p.mx * width, p.y - p.my * width, u1);
}
previous = p;
}
if (path.closed) {
float[] a = path.stroke.get(0);
float[] b = path.stroke.get(1);
vertex(path.stroke, a[0], a[1], u0);
vertex(path.stroke, b[0], b[1], u1);
} else {
p = path.points.get(count - 1);
float[] dir = direction(previous, p);
if (cap == 0 || cap == 2) {
flatCap(path.stroke, p, dir[0], dir[1], width, cap == 0 ? butt : square, aa, u0, u1, true);
} else if (cap == 1) {
roundCap(path.stroke, p, dir[0], dir[1], width, divisions, u0, u1, true);
}
}
}
}
}
public static final class Path {
public final List<CanvasPath.Point> points = new ArrayList<>();
public boolean closed;
public boolean convex;
public int winding = 1;
public int bevels;
public final List<float[]> fill = new ArrayList<>();
public final List<float[]> stroke = new ArrayList<>();
}
public static final strictfp class Point {
public float x;
public float y;
public float dx;
public float dy;
public float length;
public float mx;
public float my;
public int flags;
Point(float x, float y, int f) {
this.x = x;
this.y = y;
this.flags = f;
}
}
}
@@ -0,0 +1,127 @@
package dev.slinky.client.render.canvas;
import java.util.List;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GLContext;
final class CanvasShader {
private static final String VERTEX = "#version 110\nvarying vec2 position;\nvarying vec2 coverage;\nvoid main() {\n position = gl_Vertex.xy;\n coverage = gl_MultiTexCoord0.xy;\n gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n}\n";
private static final String FRAGMENT = "#version 110\nuniform vec4 inner;\nuniform vec4 outer;\nuniform vec3 paintX;\nuniform vec3 paintY;\nuniform vec2 extent;\nuniform float radius;\nuniform float feather;\nuniform float strokeMultiplier;\nuniform float fringe;\nvarying vec2 position;\nvarying vec2 coverage;\nvoid main() {\n float mask = 1.0;\n if (fringe > 0.5)\n mask = clamp((1.0 - abs(coverage.x * 2.0 - 1.0)) * strokeMultiplier, 0.0, 1.0) * min(1.0, coverage.y);\n float amount = 0.0;\n if (feather != 0.0) {\n vec2 p = vec2(dot(paintX, vec3(position, 1.0)), dot(paintY, vec3(position, 1.0)));\n vec2 d = abs(p) - extent + vec2(radius);\n float distance = min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius;\n amount = clamp((distance + feather * 0.5) / feather, 0.0, 1.0);\n }\n vec4 color = mix(inner, outer, amount);\n gl_FragColor = vec4(color.rgb, color.a * mask);\n}\n";
private static boolean tried;
private static int program;
private static int innerLocation;
private static int outerLocation;
private static int paintXLocation;
private static int paintYLocation;
private static int extentLocation;
private static int radiusLocation;
private static int featherLocation;
private static int multiplierLocation;
private static int fringeLocation;
private CanvasShader() {
}
static boolean available() {
if (!tried) {
tried = true;
try {
if (GLContext.getCapabilities().OpenGL20) {
program = link();
}
} catch (LinkageError | RuntimeException var1) {
program = 0;
}
}
return program != 0;
}
static void reset() {
tried = false;
program = 0;
}
private static int compile(int type, String source) {
int shader = GL20.glCreateShader(type);
GL20.glShaderSource(shader, source);
GL20.glCompileShader(shader);
if (GL20.glGetShaderi(shader, 35713) == 0) {
GL20.glDeleteShader(shader);
throw new IllegalStateException("canvas shader did not compile");
} else {
return shader;
}
}
private static int link() {
int vertex = compile(
35633,
"#version 110\nvarying vec2 position;\nvarying vec2 coverage;\nvoid main() {\n position = gl_Vertex.xy;\n coverage = gl_MultiTexCoord0.xy;\n gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n}\n"
);
int fragment = compile(
35632,
"#version 110\nuniform vec4 inner;\nuniform vec4 outer;\nuniform vec3 paintX;\nuniform vec3 paintY;\nuniform vec2 extent;\nuniform float radius;\nuniform float feather;\nuniform float strokeMultiplier;\nuniform float fringe;\nvarying vec2 position;\nvarying vec2 coverage;\nvoid main() {\n float mask = 1.0;\n if (fringe > 0.5)\n mask = clamp((1.0 - abs(coverage.x * 2.0 - 1.0)) * strokeMultiplier, 0.0, 1.0) * min(1.0, coverage.y);\n float amount = 0.0;\n if (feather != 0.0) {\n vec2 p = vec2(dot(paintX, vec3(position, 1.0)), dot(paintY, vec3(position, 1.0)));\n vec2 d = abs(p) - extent + vec2(radius);\n float distance = min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius;\n amount = clamp((distance + feather * 0.5) / feather, 0.0, 1.0);\n }\n vec4 color = mix(inner, outer, amount);\n gl_FragColor = vec4(color.rgb, color.a * mask);\n}\n"
);
int linked = GL20.glCreateProgram();
GL20.glAttachShader(linked, vertex);
GL20.glAttachShader(linked, fragment);
GL20.glLinkProgram(linked);
GL20.glDeleteShader(vertex);
GL20.glDeleteShader(fragment);
if (GL20.glGetProgrami(linked, 35714) == 0) {
GL20.glDeleteProgram(linked);
throw new IllegalStateException("canvas shader did not link");
} else {
innerLocation = GL20.glGetUniformLocation(linked, "inner");
outerLocation = GL20.glGetUniformLocation(linked, "outer");
paintXLocation = GL20.glGetUniformLocation(linked, "paintX");
paintYLocation = GL20.glGetUniformLocation(linked, "paintY");
extentLocation = GL20.glGetUniformLocation(linked, "extent");
radiusLocation = GL20.glGetUniformLocation(linked, "radius");
featherLocation = GL20.glGetUniformLocation(linked, "feather");
multiplierLocation = GL20.glGetUniformLocation(linked, "strokeMultiplier");
fringeLocation = GL20.glGetUniformLocation(linked, "fringe");
return linked;
}
}
static void draw(int mode, List<float[]> vertices, CanvasState.Paint paint, float multiplier, boolean fringe) {
int previous = GL11.glGetInteger(35725);
GL20.glUseProgram(program);
try {
float[] m = paint.transform;
float determinant = m[0] * m[3] - m[1] * m[2];
float feather = paint.feather;
if (determinant == 0.0F) {
feather = 0.0F;
} else {
GL20.glUniform3f(paintXLocation, m[3] / determinant, -m[2] / determinant, (m[2] * m[5] - m[3] * m[4]) / determinant);
GL20.glUniform3f(paintYLocation, -m[1] / determinant, m[0] / determinant, (m[1] * m[4] - m[0] * m[5]) / determinant);
}
GL20.glUniform4f(innerLocation, paint.inner[0], paint.inner[1], paint.inner[2], paint.inner[3]);
GL20.glUniform4f(outerLocation, paint.outer[0], paint.outer[1], paint.outer[2], paint.outer[3]);
GL20.glUniform2f(extentLocation, paint.extent[0], paint.extent[1]);
GL20.glUniform1f(radiusLocation, paint.radius);
GL20.glUniform1f(featherLocation, feather);
GL20.glUniform1f(multiplierLocation, multiplier);
GL20.glUniform1f(fringeLocation, fringe ? 1.0F : 0.0F);
GL11.glBegin(mode);
try {
for (float[] vertex : vertices) {
GL11.glTexCoord2f(vertex.length > 2 ? vertex[2] : 0.5F, vertex.length > 3 ? vertex[3] : 1.0F);
GL11.glVertex2f(vertex[0], vertex[1]);
}
} finally {
GL11.glEnd();
}
} finally {
GL20.glUseProgram(previous);
}
}
}
@@ -0,0 +1,309 @@
package dev.slinky.client.render.canvas;
import dev.slinky.client.numeric.FloatTrigonometry;
import dev.slinky.client.render.RenderConstants;
import dev.slinky.client.render.RenderMath;
import java.util.List;
import java.util.Objects;
public final strictfp class CanvasState {
private final CanvasState.State[] states = new CanvasState.State[33];
private int depth;
public CanvasState(CanvasState.State initial) {
this.states[0] = Objects.requireNonNull(initial).copy();
this.depth = 1;
}
public strictfp CanvasState.State selected() {
return this.states[this.depth - 1];
}
public strictfp int depth() {
return this.depth;
}
public strictfp void save() {
if (this.depth <= 31) {
this.states[this.depth] = this.selected().copy();
this.depth++;
}
}
public strictfp void restore() {
if (this.depth >= 2) {
this.depth--;
}
}
public strictfp void translate(float x, float y) {
float[] m = this.selected().transform;
float a = m[0];
float b = m[1];
float c = m[2];
float d = m[3];
float tx = m[4];
float ty = m[5];
m[0] = c * 0.0F + a;
m[1] = d * 0.0F + b;
m[2] = 0.0F * a + c;
m[3] = 0.0F * b + d;
m[4] = x * a + y * c + tx;
m[5] = x * b + y * d + ty;
}
public strictfp void scale(float x, float y) {
float[] m = this.selected().transform;
float a = m[0];
float b = m[1];
float c = m[2];
float d = m[3];
float tx = m[4];
float ty = m[5];
float zc = c * 0.0F;
float zd = d * 0.0F;
m[0] = zc + x * a;
m[1] = zd + x * b;
m[2] = y * c + 0.0F * a;
m[3] = y * d + 0.0F * b;
m[4] = a * 0.0F + zc + tx;
m[5] = b * 0.0F + zd + ty;
}
public strictfp void scissor(float x, float y, float width, float height) {
CanvasState.State s = this.selected();
float[] m = s.transform;
float[] r = s.scissor;
float w = RenderMath.max(0.0F, width);
float h = RenderMath.max(0.0F, height);
float half = RenderConstants.f(2839408);
float cx = w * half + x;
float cy = h * half + y;
r[0] = m[2] * 0.0F + m[0];
r[1] = m[3] * 0.0F + m[1];
r[2] = 0.0F * m[0] + m[2];
r[3] = 0.0F * m[1] + m[3];
r[4] = cx * m[0] + cy * m[2] + m[4];
r[5] = cx * m[1] + cy * m[3] + m[5];
s.scissorExtent[0] = w * RenderConstants.f(2839432);
s.scissorExtent[1] = h * RenderConstants.f(2839436);
}
private static strictfp CanvasState.Paint color(float[] rgba, int identity) {
CanvasState.Paint p = new CanvasState.Paint();
for (int i = 0; i < 4; i++) {
p.transform[i] = RenderConstants.f(identity + i * 4);
}
p.feather = 1.0F;
System.arraycopy(rgba, 0, p.inner, 0, 4);
System.arraycopy(rgba, 0, p.outer, 0, 4);
return p;
}
public strictfp void fillColor(float[] rgba) {
float[] copy = (float[])rgba.clone();
this.selected().fill = color(copy, 2839184);
}
public strictfp void strokeColor(float[] rgba) {
float[] copy = (float[])rgba.clone();
this.selected().stroke = color(copy, 2839168);
}
public strictfp void fillPaint(CanvasState.Paint incoming) {
CanvasState.Paint p = incoming.copy();
float[] a = (float[])incoming.transform.clone();
float[] m = this.selected().transform;
float tx = a[4] * m[0] + a[5] * m[2] + m[4];
float ty = a[4] * m[1] + a[5] * m[3] + m[5];
p.transform[0] = a[0] * m[0] + a[1] * m[2];
p.transform[1] = a[0] * m[1] + a[1] * m[3];
p.transform[2] = a[2] * m[0] + a[3] * m[2];
p.transform[3] = a[2] * m[1] + a[3] * m[3];
p.transform[4] = tx;
p.transform[5] = ty;
this.selected().fill = p;
}
public static strictfp CanvasState.Paint imagePaint(float x, float y, float width, float height, float rotation, int id, float alpha) {
CanvasState.Paint p = new CanvasState.Paint();
float cosine = FloatTrigonometry.cosine(rotation);
float sine = FloatTrigonometry.sine(rotation);
p.transform[0] = cosine;
p.transform[1] = sine;
p.transform[2] = Float.intBitsToFloat(Float.floatToRawIntBits(sine) ^ RenderConstants.i(2839376));
p.transform[3] = cosine;
p.transform[4] = x;
p.transform[5] = y;
p.extent[0] = width;
p.extent[1] = height;
p.image = id;
p.inner[0] = p.outer[0] = RenderConstants.f(2839392);
p.inner[1] = p.outer[1] = RenderConstants.f(2839396);
p.inner[2] = p.outer[2] = 1.0F;
p.inner[3] = p.outer[3] = alpha;
return p;
}
public static strictfp CanvasState.Paint linearGradient(float sx, float sy, float ex, float ey, float[] inner, float[] outer) {
CanvasState.Paint p = new CanvasState.Paint();
float x = ex - sx;
float y = ey - sy;
float length = (float)StrictMath.sqrt((double)(x * x + y * y));
boolean regular = length > RenderConstants.f(2839200);
float extent = RenderConstants.f(2839264) * length;
float feather = RenderMath.max(RenderConstants.f(2839272), length);
float nx = x / length;
float ny = y / length;
if (!regular) {
nx = 0.0F;
ny = 0.0F;
}
float ox = regular ? nx : RenderConstants.f(2839216);
float oy = regular ? ny : RenderConstants.f(2839220);
p.transform[0] = oy;
p.transform[1] = Float.intBitsToFloat(Float.floatToRawIntBits(nx) ^ RenderConstants.i(2839232));
p.transform[2] = ox;
p.transform[3] = oy;
p.transform[4] = RenderConstants.f(2839248) * ox + sx;
p.transform[5] = RenderConstants.f(2839252) * oy + sy;
p.extent[0] = Float.intBitsToFloat(1203982336);
p.extent[1] = extent + RenderConstants.f(2839268);
p.feather = feather;
System.arraycopy(inner, 0, p.inner, 0, 4);
System.arraycopy(outer, 0, p.outer, 0, 4);
return p;
}
public strictfp void fill(CanvasPath path, CanvasState.Backend backend, CanvasState.Stats stats, boolean canvasAntialias) {
CanvasState.State s = this.selected();
CanvasState.Paint p = s.fill.copy();
path.flatten();
path.expandFill(canvasAntialias && s.shapeAntialias != 0 ? path.fringe : 0.0F);
p.inner[3] = p.inner[3] * s.alpha;
p.outer[3] = s.alpha * p.outer[3];
backend.fill(
p, (int[])s.composite.clone(), (float[])s.scissor.clone(), (float[])s.scissorExtent.clone(), path.fringe, path.paths(), (float[])path.bounds.clone()
);
for (CanvasPath.Path item : path.paths()) {
stats.fillTriangles = stats.fillTriangles + (item.fill.size() + item.stroke.size() - 4);
stats.drawCalls += 2;
}
}
public strictfp void stroke(CanvasPath path, CanvasState.Backend backend, CanvasState.Stats stats, boolean canvasAntialias) {
CanvasState.State s = this.selected();
float[] m = s.transform;
float x = (float)StrictMath.sqrt((double)(m[0] * m[0] + m[2] * m[2]));
float y = (float)StrictMath.sqrt((double)(m[1] * m[1] + m[3] * m[3]));
float width = (y + x) * RenderConstants.f(2840064) * s.lineWidth;
float clamped = RenderMath.min(RenderConstants.f(2840068), width);
width = width < 0.0F ? 0.0F : clamped;
CanvasState.Paint p = s.stroke.copy();
float drawn = path.fringe;
if (drawn > width) {
float ratio = width / drawn;
float maximum = RenderMath.min(RenderConstants.f(2840072), ratio);
ratio = ratio < 0.0F ? 0.0F : maximum;
ratio *= ratio;
p.inner[3] = p.inner[3] * ratio;
p.outer[3] = p.outer[3] * ratio;
} else {
drawn = width;
}
p.inner[3] = p.inner[3] * s.alpha;
p.outer[3] = p.outer[3] * s.alpha;
path.flatten();
float aa = canvasAntialias && s.shapeAntialias != 0 ? path.fringe : 0.0F;
path.expandStroke(RenderConstants.f(2840064) * drawn, aa, s.cap, s.join, s.miter);
backend.stroke(p, (int[])s.composite.clone(), (float[])s.scissor.clone(), (float[])s.scissorExtent.clone(), path.fringe, drawn, path.paths());
for (CanvasPath.Path item : path.paths()) {
stats.strokeTriangles = stats.strokeTriangles + (item.stroke.size() - 2);
stats.drawCalls++;
}
}
public interface Backend {
void fill(CanvasState.Paint var1, int[] var2, float[] var3, float[] var4, float var5, List<CanvasPath.Path> var6, float[] var7);
void stroke(CanvasState.Paint var1, int[] var2, float[] var3, float[] var4, float var5, float var6, List<CanvasPath.Path> var7);
}
public static final class Paint {
public final float[] transform = new float[6];
public final float[] extent = new float[2];
public final float[] inner = new float[4];
public final float[] outer = new float[4];
public float radius;
public float feather;
public int image;
public strictfp CanvasState.Paint copy() {
CanvasState.Paint p = new CanvasState.Paint();
System.arraycopy(this.transform, 0, p.transform, 0, 6);
System.arraycopy(this.extent, 0, p.extent, 0, 2);
System.arraycopy(this.inner, 0, p.inner, 0, 4);
System.arraycopy(this.outer, 0, p.outer, 0, 4);
p.radius = this.radius;
p.feather = this.feather;
p.image = this.image;
return p;
}
}
public static final class State {
public final float[] transform = new float[6];
public final float[] scissor = new float[6];
public final float[] scissorExtent = new float[2];
public CanvasState.Paint fill = new CanvasState.Paint();
public CanvasState.Paint stroke = new CanvasState.Paint();
public float alpha;
public float lineWidth;
public float miter;
public float fontSize;
public float blur;
public float spacing;
public float lineHeight;
public int alignment;
public int font;
public int cap;
public int join;
public int shapeAntialias;
public final int[] composite = new int[4];
public strictfp CanvasState.State copy() {
CanvasState.State s = new CanvasState.State();
System.arraycopy(this.transform, 0, s.transform, 0, 6);
System.arraycopy(this.scissor, 0, s.scissor, 0, 6);
System.arraycopy(this.scissorExtent, 0, s.scissorExtent, 0, 2);
System.arraycopy(this.composite, 0, s.composite, 0, 4);
s.fill = this.fill.copy();
s.stroke = this.stroke.copy();
s.alpha = this.alpha;
s.lineWidth = this.lineWidth;
s.miter = this.miter;
s.fontSize = this.fontSize;
s.blur = this.blur;
s.spacing = this.spacing;
s.lineHeight = this.lineHeight;
s.alignment = this.alignment;
s.font = this.font;
s.cap = this.cap;
s.join = this.join;
s.shapeAntialias = this.shapeAntialias;
return s;
}
}
public static final class Stats {
public int drawCalls;
public int fillTriangles;
public int strokeTriangles;
}
}
@@ -0,0 +1,227 @@
package dev.slinky.client.render.canvas;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import net.minecraft.client.renderer.GlStateManager;
import org.lwjgl.opengl.GL11;
public final class GlCanvasBackend implements CanvasState.Backend {
private static final GlCanvasBackend.Driver OPENGL = new GlCanvasBackend.Driver() {
@Override
public void blend(int[] composite) {
GlStateManager.tryBlendFuncSeparate(composite[0], composite[1], composite[2], composite[3]);
}
@Override
public void draw(int mode, List<float[]> vertices) {
GlStateManager.disableTexture2D();
GlStateManager.disableAlpha();
GlStateManager.shadeModel(7425);
GL11.glBegin(mode);
try {
for (float[] vertex : vertices) {
GlStateManager.color(vertex[2], vertex[3], vertex[4], vertex[5]);
GL11.glVertex2f(vertex[0], vertex[1]);
}
} finally {
GL11.glEnd();
}
}
};
private final GlCanvasBackend.Driver driver;
public GlCanvasBackend() {
this(OPENGL);
}
public GlCanvasBackend(GlCanvasBackend.Driver driver) {
this.driver = Objects.requireNonNull(driver);
}
@Override
public void fill(CanvasState.Paint paint, int[] composite, float[] scissor, float[] extent, float fringe, List<CanvasPath.Path> paths, float[] bounds) {
this.driver.blend(composite);
for (CanvasPath.Path path : paths) {
this.draw(6, path.fill, paint, 1.0F, false);
if (!path.stroke.isEmpty()) {
this.draw(5, path.stroke, paint, 1.0F, true);
}
}
}
@Override
public void stroke(CanvasState.Paint paint, int[] composite, float[] scissor, float[] extent, float fringe, float width, List<CanvasPath.Path> paths) {
this.driver.blend(composite);
float multiplier = fringe == 0.0F ? 1.0F : (width * 0.5F + fringe * 0.5F) / fringe;
for (CanvasPath.Path path : paths) {
this.draw(5, path.stroke, paint, multiplier, true);
}
}
private void draw(int mode, List<float[]> vertices, CanvasState.Paint paint, float multiplier, boolean fringe) {
if (!vertices.isEmpty()) {
if (this.driver == OPENGL && CanvasShader.available()) {
GlStateManager.disableTexture2D();
GlStateManager.disableAlpha();
CanvasShader.draw(mode, vertices, paint, multiplier, fringe);
} else {
List<float[]> emitted = fringe ? coverageTriangles(mode, vertices, multiplier) : vertices;
List<float[]> rgba = new ArrayList<>();
for (float[] vertex : emitted) {
float coverage = fringe ? vertex[4] : 1.0F;
float[] color = paint(paint, vertex[0], vertex[1]);
rgba.add(new float[]{vertex[0], vertex[1], color[0], color[1], color[2], color[3] * coverage});
}
this.driver.draw(fringe ? 4 : mode, rgba);
}
}
}
public static List<float[]> coverageTriangles(int mode, List<float[]> vertices, float multiplier) {
List<float[]> result = new ArrayList<>();
float[] bands = multiplier > 1.0F ? new float[]{0.0F, 0.5F / multiplier, 1.0F - 0.5F / multiplier, 1.0F} : new float[]{0.0F, 0.5F, 1.0F};
for (int index = 2; index < vertices.size(); index++) {
float[] b = vertices.get(index - 1);
float[] c = vertices.get(index);
float[] a;
if (mode == 6) {
a = vertices.get(0);
} else {
if (mode != 5) {
throw new IllegalArgumentException("Unsupported canvas fringe primitive");
}
a = vertices.get(index - 2);
if ((index & 1) != 0) {
float[] swap = a;
a = b;
b = swap;
}
}
float low = Math.min(a[2], Math.min(b[2], c[2]));
float high = Math.max(a[2], Math.max(b[2], c[2]));
if (low == high) {
triangle(result, a, b, c, multiplier, 0);
} else {
for (int band = 1; band < bands.length; band++) {
List<float[]> polygon = clip(Arrays.asList(a, b, c), bands[band - 1], true);
polygon = clip(polygon, bands[band], false);
for (int point = 2; point < polygon.size(); point++) {
triangle(result, polygon.get(0), polygon.get(point - 1), polygon.get(point), multiplier, 0);
}
}
}
}
return result;
}
private static List<float[]> clip(List<float[]> input, float boundary, boolean above) {
List<float[]> output = new ArrayList<>();
if (input.isEmpty()) {
return output;
} else {
float[] previous = input.get(input.size() - 1);
boolean wasInside = above ? previous[2] >= boundary : previous[2] <= boundary;
for (float[] current : input) {
boolean inside = above ? current[2] >= boundary : current[2] <= boundary;
if (wasInside != inside) {
float fraction = (boundary - previous[2]) / (current[2] - previous[2]);
float[] point = interpolate(previous, current, fraction);
point[2] = boundary;
output.add(point);
}
if (inside) {
output.add(current);
}
previous = current;
wasInside = inside;
}
return output;
}
}
private static float[] interpolate(float[] a, float[] b, float fraction) {
float[] result = new float[4];
for (int index = 0; index < 4; index++) {
result[index] = a[index] + (b[index] - a[index]) * fraction;
}
return result;
}
public static float coverage(float u, float v, float multiplier) {
return Math.min(1.0F, Math.max(0.0F, (1.0F - Math.abs(u * 2.0F - 1.0F)) * multiplier)) * Math.min(1.0F, v);
}
private static void triangle(List<float[]> output, float[] a, float[] b, float[] c, float multiplier, int depth) {
float area = (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
if (area != 0.0F) {
float ac = coverage(a[2], a[3], multiplier);
float bc = coverage(b[2], b[3], multiplier);
float cc = coverage(c[2], c[3], multiplier);
float[] ab = interpolate(a, b, 0.5F);
float[] ca = interpolate(c, a, 0.5F);
float[] cb = interpolate(c, b, 0.5F);
float error = Math.max(
Math.abs(coverage(ab[2], ab[3], multiplier) - (ac + bc) * 0.5F),
Math.max(Math.abs(coverage(ca[2], ca[3], multiplier) - (cc + ac) * 0.5F), Math.abs(coverage(cb[2], cb[3], multiplier) - (cc + bc) * 0.5F))
);
if (error > 9.765625E-4F && depth < 6) {
triangle(output, a, ab, ca, multiplier, depth + 1);
triangle(output, ab, b, cb, multiplier, depth + 1);
triangle(output, ca, cb, c, multiplier, depth + 1);
triangle(output, ab, cb, ca, multiplier, depth + 1);
} else {
output.add(new float[]{a[0], a[1], a[2], a[3], ac});
output.add(new float[]{b[0], b[1], b[2], b[3], bc});
output.add(new float[]{c[0], c[1], c[2], c[3], cc});
}
}
}
public static float[] paint(CanvasState.Paint paint, float x, float y) {
float[] m = paint.transform;
float determinant = m[0] * m[3] - m[1] * m[2];
float amount = 0.0F;
if (determinant != 0.0F && paint.feather != 0.0F) {
float px = ((x - m[4]) * m[3] - (y - m[5]) * m[2]) / determinant;
float py = ((y - m[5]) * m[0] - (x - m[4]) * m[1]) / determinant;
float dx = Math.abs(px) - paint.extent[0] + paint.radius;
float dy = Math.abs(py) - paint.extent[1] + paint.radius;
float distance = Math.min(Math.max(dx, dy), 0.0F)
+ (float)StrictMath.sqrt((double)(Math.max(dx, 0.0F) * Math.max(dx, 0.0F) + Math.max(dy, 0.0F) * Math.max(dy, 0.0F)))
- paint.radius;
amount = Math.max(0.0F, Math.min(1.0F, (distance + paint.feather * 0.5F) / paint.feather));
}
float[] result = new float[4];
for (int i = 0; i < 4; i++) {
result[i] = paint.inner[i] + (paint.outer[i] - paint.inner[i]) * amount;
}
return result;
}
public interface Driver {
void blend(int[] var1);
void draw(int var1, List<float[]> var2);
}
}
@@ -0,0 +1,100 @@
package dev.slinky.client.render.font;
import dev.slinky.inject.Platform;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
public final class BundledGuiFonts {
public static CanvasFonts.Engine load(int style) {
String name = style == 1 ? "InterSemiBold.ttf" : "Inter-Medium.ttf";
byte[] bytes;
try (InputStream input = Platform.resource("/assets/slinkysemantic/fonts/" + name)) {
if (input == null) {
throw new IOException("Missing bundled GUI font " + name);
}
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
int count;
while ((count = input.read(buffer)) != -1) {
output.write(buffer, 0, count);
}
bytes = output.toByteArray();
} catch (IOException var19) {
throw new IllegalStateException("Cannot load GUI font", var19);
}
FontTables.Font font = new FontTables.Font(bytes, new FontRasterization.Scratch(() -> {
throw new IllegalStateException("GUI glyph scratch capacity exceeded");
}));
font.head = table(bytes, "head");
font.hhea = table(bytes, "hhea");
font.hmtx = table(bytes, "hmtx");
font.loca = table(bytes, "loca");
font.glyf = table(bytes, "glyf");
font.kern = table(bytes, "kern");
font.gpos = table(bytes, "GPOS");
font.glyphCount = FontTables.u16(bytes, required(table(bytes, "maxp")) + 4);
font.locaFormat = FontTables.u16(bytes, required(font.head) + 50);
int map = required(table(bytes, "cmap"));
for (int i = 0; i < FontTables.u16(bytes, map + 2); i++) {
int record = map + 4 + i * 8;
int candidate = map + FontTables.u32(bytes, record + 4);
int format = FontTables.u16(bytes, candidate);
int platform = FontTables.u16(bytes, record);
int encoding = FontTables.u16(bytes, record + 2);
if ((platform == 0 || platform == 3 && (encoding == 1 || encoding == 10)) && (format == 4 || format == 12) && (font.cmap == 0 || format == 12)) {
font.cmap = candidate;
}
}
required(font.cmap);
required(font.glyf);
required(font.loca);
required(font.hhea);
required(font.hmtx);
float units = (float)(FontTables.s16(bytes, font.hhea + 4) - FontTables.s16(bytes, font.hhea + 6));
float ascent = (float)FontTables.s16(bytes, font.hhea + 4) / units;
float descent = (float)FontTables.s16(bytes, font.hhea + 6) / units;
float gap = (float)FontTables.s16(bytes, font.hhea + 8) / units;
int[] buckets = new int[256];
Arrays.fill(buckets, -1);
return new CanvasFonts.Engine(new FontGlyphCache.Font(font, buckets, ascent, descent, ascent - descent + gap), null, 512, 256);
}
private static int required(int offset) {
if (offset == 0) {
throw new IllegalStateException("Incomplete bundled TrueType face");
} else {
return offset;
}
}
private static int table(byte[] bytes, String tag) {
int count = FontTables.u16(bytes, 4);
for (int i = 0; i < count; i++) {
int record = 12 + i * 16;
boolean match = true;
for (int n = 0; n < 4; n++) {
match &= bytes[record + n] == tag.charAt(n);
}
if (match) {
return FontTables.u32(bytes, record + 8);
}
}
return 0;
}
private BundledGuiFonts() {
}
}
@@ -0,0 +1,434 @@
package dev.slinky.client.render.font;
import dev.slinky.client.render.RenderConstants;
import dev.slinky.client.render.hook.GlStateScope;
import dev.slinky.client.render.hook.RenderHooks;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.resources.IReloadableResourceManager;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
public final strictfp class CanvasFonts {
private static final float[] IDENTITY = new float[]{1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F};
private static CanvasFonts.Engine engine;
private static CanvasFonts.Engine secondary;
private static boolean listener;
private static final Map<Integer, Integer> textures = new HashMap<>();
private static boolean guiFace;
private static CanvasFonts.Engine guiEngine;
private static CanvasFonts.Engine guiSecondary;
private static final Map<Integer, Integer> uploadedVersions = new HashMap<>();
public static strictfp int allocateTextureIdentity() {
return CanvasFonts.Engine.textureSequence++;
}
private static strictfp CanvasFonts.Engine current() {
return current(0);
}
public static strictfp CanvasFonts.GuiFaceScope guiFace() {
return new CanvasFonts.GuiFaceScope();
}
private static strictfp CanvasFonts.Engine current(int style) {
if (guiFace) {
CanvasFonts.Engine selected = style == 1 ? guiSecondary : guiEngine;
if (selected == null) {
selected = BundledGuiFonts.load(style);
if (style == 1) {
guiSecondary = selected;
} else {
guiEngine = selected;
}
}
return selected;
} else {
CanvasFonts.Engine selected = style == 1 ? secondary : engine;
if (selected != null && selected.resourceSource != null && !selected.resourceSource.isCurrent()) {
release();
selected = null;
}
if (selected == null) {
Minecraft game = Minecraft.getMinecraft();
if (!listener && game.getResourceManager() instanceof IReloadableResourceManager) {
listener = true;
((IReloadableResourceManager)game.getResourceManager()).registerReloadListener(resources -> release());
}
try {
GameFontProvider source = new GameFontProvider(game, style == 1);
int[] heads = new int[256];
Arrays.fill(heads, -1);
FontGlyphCache.Font font = new FontGlyphCache.Font(null, heads, 0.8888889F, -0.11111111F, 1.0F);
selected = new CanvasFonts.Engine(font, source, 512, 256);
if (style == 1) {
secondary = selected;
} else {
engine = selected;
}
} catch (IOException var6) {
throw new IllegalStateException("Cannot load game font resources", var6);
}
}
return selected;
}
}
public static strictfp CanvasFonts.Engine bindEngine(CanvasFonts.Engine next) {
CanvasFonts.Engine previous = engine;
engine = Objects.requireNonNull(next);
secondary = next;
return previous;
}
public static strictfp void restoreEngine(CanvasFonts.Engine previous) {
engine = previous;
secondary = null;
}
public static strictfp void bindGuiEngine(CanvasFonts.Engine next) {
guiSecondary = next;
guiEngine = next;
}
public static strictfp float bounds(String text, float x, float y, float size, int alignment, float[] output) {
return current().bounds(text, x, y, size, alignment, output);
}
public static strictfp CanvasFonts.Text text(String text, float x, float y, float size, int alignment, float[] transform) {
return current().text(text, x, y, size, alignment, transform);
}
public static strictfp CanvasFonts.Text text(String text, float x, float y, float size, int alignment, float[] transform, int style) {
return current(style).text(text, x, y, size, alignment, transform);
}
public static strictfp float width(String text, int style, float size) {
return current(style).bounds(text, 0.0F, 0.0F, size, 9, null);
}
public static strictfp void render(CanvasFonts.Text text, int color, float alpha) {
if (GlStateScope.active()) {
draw(text, color, alpha);
} else {
try (GlStateScope attributes = GlStateScope.open()) {
draw(text, color, alpha);
}
}
}
private static strictfp void draw(CanvasFonts.Text text, int color, float alpha) {
GlStateManager.enableTexture2D();
GlStateManager.disableAlpha();
GL11.glShadeModel(7425);
GL11.glTexEnvi(8960, 8704, 8448);
GlStateManager.color(
(float)(color >>> 16 & 0xFF) / 255.0F, (float)(color >>> 8 & 0xFF) / 255.0F, (float)(color & 0xFF) / 255.0F, (float)(color >>> 24) / 255.0F * alpha
);
for (CanvasFonts.Batch batch : text.batches) {
Integer texture = textures.get(batch.texture);
if (texture == null) {
texture = GL11.glGenTextures();
textures.put(batch.texture, texture);
}
GlStateManager.bindTexture(texture);
Integer uploaded = uploadedVersions.get(batch.texture);
if (uploaded == null || uploaded != batch.version[0]) {
GL11.glTexParameteri(3553, 10241, 9729);
GL11.glTexParameteri(3553, 10240, 9729);
GL11.glTexParameteri(3553, 10242, 10496);
GL11.glTexParameteri(3553, 10243, 10496);
ByteBuffer pixels = BufferUtils.createByteBuffer(batch.pixels.length);
((Buffer)pixels.put(batch.pixels)).flip();
GL11.glPixelStorei(3317, 1);
GL11.glTexImage2D(3553, 0, 6406, batch.width, batch.height, 0, 6406, 5121, pixels);
uploadedVersions.put(batch.texture, batch.version[0]);
}
GL11.glBegin(4);
try {
for (float[] vertex : batch.vertices) {
GL11.glTexCoord2f(vertex[2], vertex[3]);
GL11.glVertex2f(vertex[0], vertex[1]);
}
} finally {
GL11.glEnd();
}
}
GlStateManager.disableTexture2D();
}
public static strictfp void release() {
if (!textures.isEmpty() && Display.isCreated()) {
try {
if (Display.isCurrent()) {
for (int texture : textures.values()) {
GlStateManager.deleteTexture(texture);
}
}
} catch (LWJGLException var5) {
throw new IllegalStateException("Cannot release font textures", var5);
} finally {
textures.clear();
uploadedVersions.clear();
guiSecondary = null;
guiEngine = null;
secondary = null;
engine = null;
}
} else {
textures.clear();
uploadedVersions.clear();
guiSecondary = null;
guiEngine = null;
secondary = null;
engine = null;
}
}
private CanvasFonts() {
}
public static final strictfp class Batch {
public final int texture;
public final int width;
public final int height;
public final byte[] pixels;
public final List<float[]> vertices;
final int[] version;
public Batch(int texture, int width, int height, byte[] pixels, List<float[]> vertices) {
this(texture, width, height, pixels, new int[1], vertices);
}
Batch(int texture, int width, int height, byte[] pixels, int[] version, List<float[]> vertices) {
this.texture = texture;
this.width = width;
this.height = height;
this.pixels = pixels;
this.version = version;
this.vertices = new ArrayList<>();
for (float[] vertex : vertices) {
this.vertices.add((float[])vertex.clone());
}
}
}
public static final strictfp class Engine {
private final FontGlyphCache face;
private final FontAtlasService.Canvas atlas;
private final GameFontProvider resourceSource;
private final Map<Integer, int[]> dimensions = new HashMap<>();
private final Map<Integer, byte[]> images = new HashMap<>();
private final Map<Integer, int[]> versions = new HashMap<>();
private static int textureSequence = 1;
public Engine(FontGlyphCache.Font font, FontGlyphCache.Source source, int width, int height) {
this.resourceSource = source instanceof GameFontProvider ? (GameFontProvider)source : null;
FontGlyphCache.Stash cache = new FontGlyphCache.Stash(
new FontServices.Atlas(width, height), new byte[width * height], width, width, height, 0, 0, null
);
FontAtlasService.Stash stash = new FontAtlasService.Stash(cache, width, height, Arrays.asList(font), (previous, count) -> new byte[(int)count]);
FontAtlasService.Driver driver = new FontAtlasService.Driver() {
@Override
public strictfp void upload(int texture, int x, int y, int w, int h, byte[] pixels) {
int stride = Engine.this.dimensions.get(texture)[0];
byte[] target = Engine.this.images.get(texture);
for (int row = 0; row < h; row++) {
System.arraycopy(pixels, (y + row) * stride + x, target, (y + row) * stride + x, w);
}
Engine.this.versions.get(texture)[0]++;
}
@Override
public strictfp void dimensions(int texture, int[] output) {
int[] found = Engine.this.dimensions.get(texture);
if (found == null) {
throw new IllegalStateException("Unknown caller atlas");
} else {
output[0] = found[0];
output[1] = found[1];
}
}
@Override
public strictfp int create(int type, int w, int h, int flags, byte[] pixels) {
int id = CanvasFonts.Engine.textureSequence++;
Engine.this.dimensions.put(id, new int[]{w, h});
Engine.this.images.put(id, pixels == null ? new byte[w * h] : (byte[])pixels.clone());
Engine.this.versions.put(id, new int[1]);
return id;
}
};
int texture = driver.create(1, width, height, 0, null);
this.atlas = new FontAtlasService.Canvas(stash, 0, new int[]{texture, 0, 0, 0}, driver, width, height, 0, 0);
FontAtlasService.reset(stash, width, height, 0, 0);
this.face = new FontGlyphCache(cache, font, source);
}
public strictfp float bounds(String text, float x, float y, float size, int alignment, float[] output) {
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
return FontServices.canvasBounds(
this.face,
0,
x,
y,
bytes,
bytes.length,
size,
0.0F,
0.0F,
alignment,
true,
this.atlas.stash.inverseWidth,
this.atlas.stash.inverseHeight,
CanvasFonts.IDENTITY,
(float)RenderHooks.canvasScale(),
output
);
}
public strictfp CanvasFonts.Text text(String text, float x, float y, float size, int alignment, float[] transform) {
float scale = FontServices.transformScale(
transform, RenderConstants.f(2840336), RenderConstants.f(2840340), RenderConstants.f(2840344), (float)RenderHooks.canvasScale()
);
final FontServices.Iterator iterator = new FontServices.Iterator(
this.face,
x * scale,
y * scale,
text.getBytes(StandardCharsets.UTF_8),
size * scale,
0.0F,
0.0F,
alignment,
2,
true,
this.atlas.stash.inverseWidth,
this.atlas.stash.inverseHeight
);
final List<CanvasFonts.Batch> batches = new ArrayList<>();
float end = FontServices.drawText(
iterator,
x,
scale,
CanvasFonts.IDENTITY,
new FontServices.TextBackend() {
private float[] reserved;
@Override
public strictfp boolean reserve(int count) {
this.reserved = new float[count * 4];
return this.reserved.length >= count * 4;
}
@Override
public strictfp boolean allocateTextAtlas() {
boolean result = FontAtlasService.allocate(Engine.this.atlas);
if (result) {
iterator.inverseWidth = Engine.this.atlas.stash.inverseWidth;
iterator.inverseHeight = Engine.this.atlas.stash.inverseHeight;
}
return result;
}
@Override
public strictfp void draw(List<float[]> vertices) {
if (!vertices.isEmpty()) {
this.uploadDirtyTexture();
int offset = 0;
for (float[] vertex : vertices) {
for (float value : vertex) {
this.reserved[offset++] = value;
}
}
int texture = Engine.this.atlas.textures[Engine.this.atlas.index];
batches.add(
new CanvasFonts.Batch(
texture,
Engine.this.atlas.stash.width,
Engine.this.atlas.stash.height,
Engine.this.images.get(texture),
Engine.this.versions.get(texture),
vertices
)
);
}
}
@Override
public strictfp void uploadDirtyTexture() {
FontGlyphCache.Stash cache = Engine.this.atlas.stash.cache;
if (Engine.this.atlas.stash.dirty()) {
Engine.this.atlas
.driver
.upload(
Engine.this.atlas.textures[Engine.this.atlas.index],
cache.dirtyX,
cache.dirtyY,
cache.dirtyRight - cache.dirtyX,
cache.dirtyBottom - cache.dirtyY,
cache.pixels
);
}
Engine.this.atlas.stash.clean();
}
},
new FontServices.TextStats()
);
return new CanvasFonts.Text(text, batches, end);
}
}
public static final strictfp class GuiFaceScope implements AutoCloseable {
private final boolean previous = CanvasFonts.guiFace;
private GuiFaceScope() {
CanvasFonts.guiFace = true;
}
@Override
public strictfp void close() {
CanvasFonts.guiFace = this.previous;
}
}
public static final strictfp class Text {
public final String text;
public final List<CanvasFonts.Batch> batches;
public final float end;
public Text(String text, List<CanvasFonts.Batch> batches, float end) {
this.text = text;
this.batches = batches;
this.end = end;
}
}
}
@@ -0,0 +1,202 @@
package dev.slinky.client.render.font;
import dev.slinky.client.render.RenderConstants;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public final strictfp class FontAtlasService {
public static strictfp boolean allocate(FontAtlasService.Canvas canvas) {
FontAtlasService.Stash stash = canvas.stash;
FontGlyphCache.Stash cache = stash.cache;
if (stash.dirty()) {
int x = cache.dirtyX;
int y = cache.dirtyY;
int width = cache.dirtyRight - x;
int height = cache.dirtyBottom - y;
stash.clean();
int texture = canvas.textures[canvas.index];
if (texture != 0) {
canvas.driver.upload(texture, x, y, width, height, cache.pixels);
}
}
int index = canvas.index;
if (index > 2) {
return false;
} else {
int[] dimensions = new int[]{canvas.queryWidth, canvas.queryHeight};
int texture = canvas.textures[index + 1];
if (texture != 0) {
canvas.driver.dimensions(texture, dimensions);
} else {
canvas.driver.dimensions(canvas.textures[index], dimensions);
if (dimensions[0] > dimensions[1]) {
dimensions[1] += dimensions[1];
} else {
dimensions[0] += dimensions[0];
}
if (dimensions[0] >= 2049 || dimensions[1] >= 2049) {
dimensions[0] = dimensions[1] = 2048;
}
int created = canvas.driver.create(1, dimensions[0], dimensions[1], 0, null);
canvas.textures[canvas.index + 1] = created;
}
canvas.index++;
reset(canvas.stash, dimensions[0], dimensions[1], canvas.resetX, canvas.resetY);
return true;
}
}
public static strictfp boolean reset(FontAtlasService.Stash stash, int width, int height, int incomingX, int incomingY) {
if (stash == null) {
return false;
} else {
FontGlyphCache.Stash cache = stash.cache;
if (stash.dirty()) {
if (stash.upload != null) {
stash.upload.upload(new int[]{cache.dirtyX, cache.dirtyY, cache.dirtyRight, cache.dirtyBottom}, cache.pixels);
}
stash.clean();
}
if (stash.vertexCount > 0) {
if (stash.flush != null) {
stash.flush.flush(stash.positions, stash.uv, stash.colors, stash.vertexCount);
}
stash.vertexCount = 0;
}
if (stash.resized != null && !stash.resized.resize(width, height)) {
return false;
} else {
cache.atlas = new FontServices.Atlas(width, height);
long size = (long)(height * width);
cache.pixels = stash.allocation.resize(cache.pixels, size);
if (cache.pixels == null) {
return false;
} else {
Arrays.fill(cache.pixels, 0, (int)size, (byte)0);
cache.dirtyX = width;
cache.dirtyY = height;
cache.dirtyRight = cache.dirtyBottom = 0;
for (FontGlyphCache.Font font : stash.fonts) {
font.glyphs.clear();
Arrays.fill(font.heads, -1);
}
stash.width = width;
stash.height = height;
cache.stride = width;
stash.inverseWidth = RenderConstants.f(2838368) / (float)width;
stash.inverseHeight = RenderConstants.f(2838372) / (float)height;
int[] point = cache.atlas.add(2, 2);
if (point != null) {
int x = point[0];
int y = point[1];
int offset = width * y + x;
cache.pixels[offset] = cache.pixels[offset + 1] = cache.pixels[offset + width] = cache.pixels[offset + width + 1] = -1;
cache.dirtyX = Math.min(cache.dirtyX, x);
cache.dirtyY = Math.min(cache.dirtyY, y);
cache.dirtyRight = Math.max(cache.dirtyRight, x + 2);
cache.dirtyBottom = Math.max(cache.dirtyBottom, y + 2);
}
return true;
}
}
}
}
private FontAtlasService() {
}
public interface Allocation {
byte[] resize(byte[] var1, long var2);
}
public static final strictfp class Canvas {
public FontAtlasService.Stash stash;
public int index;
public final int[] textures;
public final FontAtlasService.Driver driver;
public final int queryWidth;
public final int queryHeight;
public final int resetX;
public final int resetY;
public Canvas(
FontAtlasService.Stash stash, int index, int[] textures, FontAtlasService.Driver driver, int queryWidth, int queryHeight, int resetX, int resetY
) {
this.stash = stash;
this.index = index;
this.textures = Objects.requireNonNull(textures);
this.driver = Objects.requireNonNull(driver);
this.queryWidth = queryWidth;
this.queryHeight = queryHeight;
this.resetX = resetX;
this.resetY = resetY;
}
}
public interface DirtyUpload {
void upload(int[] var1, byte[] var2);
}
public interface Driver {
void upload(int var1, int var2, int var3, int var4, int var5, byte[] var6);
void dimensions(int var1, int[] var2);
int create(int var1, int var2, int var3, int var4, byte[] var5);
}
public interface ResizeNotification {
boolean resize(int var1, int var2);
}
public static final strictfp class Stash {
public final FontGlyphCache.Stash cache;
public int width;
public int height;
public int vertexCount;
public float inverseWidth;
public float inverseHeight;
public float[] positions;
public float[] uv;
public int[] colors;
public final List<FontGlyphCache.Font> fonts;
public FontAtlasService.DirtyUpload upload;
public FontAtlasService.VertexFlush flush;
public FontAtlasService.ResizeNotification resized;
public FontAtlasService.Allocation allocation;
public Stash(FontGlyphCache.Stash cache, int width, int height, List<FontGlyphCache.Font> fonts, FontAtlasService.Allocation allocation) {
this.cache = Objects.requireNonNull(cache);
this.width = width;
this.height = height;
this.fonts = Objects.requireNonNull(fonts);
this.allocation = Objects.requireNonNull(allocation);
}
public strictfp boolean dirty() {
return this.cache.dirtyX < this.cache.dirtyRight && this.cache.dirtyY < this.cache.dirtyBottom;
}
public strictfp void clean() {
this.cache.dirtyX = this.width;
this.cache.dirtyY = this.height;
this.cache.dirtyRight = this.cache.dirtyBottom = 0;
}
}
public interface VertexFlush {
void flush(float[] var1, float[] var2, int[] var3, int var4);
}
}
@@ -0,0 +1,576 @@
package dev.slinky.client.render.font;
import dev.slinky.client.numeric.NumericMath;
import java.util.ArrayList;
import java.util.List;
public final strictfp class FontCff {
public static strictfp FontCff.Buffer element(FontCff.Buffer index, int element) {
index.seek(0);
int count = index.bytes(2);
int width = index.byteValue();
index.skip(element * width);
int first = index.bytes(width);
int end = index.bytes(width);
int at = 2 + (count + 1) * width + first;
return index.range(at, end - first);
}
public static strictfp int integer(FontCff.Buffer b) {
int first = b.byteValue();
if (first >= 32 && first <= 246) {
return first - 139;
} else if (first >= 247 && first <= 250) {
return (first - 247) * 256 + b.byteValue() + 108;
} else if (first >= 251 && first <= 254) {
return -(first - 251) * 256 - b.byteValue() - 108;
} else if (first == 28) {
return b.bytes(2);
} else {
return first == 29 ? b.bytes(4) : 0;
}
}
public static strictfp void dictionary(FontCff.Buffer b, int key, int count, int[] output) {
b.seek(0);
while (b.cursor < b.size) {
int start = b.cursor;
while (b.cursor < b.size && (b.data[b.offset + b.cursor] & 255) >= 28) {
if ((b.data[b.offset + b.cursor] & 255) == 30) {
b.skip(1);
while (b.cursor < b.size) {
int n = b.byteValue();
if ((n & 15) == 15 || n >>> 4 == 15) {
break;
}
}
} else {
integer(b);
}
}
int end = b.cursor;
int op = b.byteValue();
if (op == 12) {
op = b.byteValue() | 256;
}
if (op == key) {
FontCff.Buffer operands = b.range(start, end - start);
if (operands.cursor < operands.size) {
int i = 0;
do {
output[i++] = integer(operands);
} while (Integer.toUnsignedLong(i) < Integer.toUnsignedLong(count) && operands.cursor < operands.size);
}
return;
}
}
}
public static strictfp FontCff.Buffer readIndex(FontCff.Buffer b) {
int start = b.cursor;
int count = b.bytes(2);
if (count != 0) {
int width = b.byteValue();
b.skip(width * count);
b.skip(b.bytes(width) - 1);
}
return b.range(start, b.cursor - start);
}
public static strictfp FontCff.Buffer privateSubroutines(FontCff.Buffer cff, FontCff.Buffer dict) {
int[] location = new int[2];
int[] subrs = new int[1];
dictionary(dict, 18, 2, location);
if (location[0] != 0 && location[1] != 0) {
FontCff.Buffer privateDict = cff.range(location[1], location[0]);
dictionary(privateDict, 19, 1, subrs);
if (subrs[0] == 0) {
return new FontCff.Buffer(new byte[0]);
} else {
cff.seek(location[1] + subrs[0]);
return readIndex(cff);
}
} else {
return new FontCff.Buffer(new byte[0]);
}
}
private static strictfp FontCff.Buffer subroutine(FontCff.Buffer index, int number) {
index = index.copy();
index.seek(0);
int count = index.bytes(2);
int bias = count >= 33900 ? '耀' : (count >= 1240 ? 1131 : 107);
int element = number + bias;
return Integer.compareUnsigned(element, count) >= 0 ? new FontCff.Buffer(new byte[0]) : element(index, element);
}
private static strictfp FontCff.Buffer glyphSubroutines(FontCff.Info info, int glyph) {
FontCff.Buffer selector = info.fdSelect.copy();
selector.seek(0);
int format = selector.byteValue();
int selected = -1;
if (format == 0) {
selector.skip(glyph);
selected = selector.byteValue();
} else if (format == 3) {
int count = selector.bytes(2);
int start = selector.bytes(2);
for (int i = 0; i < count; i++) {
int value = selector.byteValue();
int end = selector.bytes(2);
if (glyph >= start && glyph < end) {
selected = value;
break;
}
start = end;
}
}
FontCff.Buffer dictionary = element(info.fdArray.copy(), selected);
return privateSubroutines(info.cff.copy(), dictionary);
}
private static strictfp int trunc(float value) {
return NumericMath.truncateInt(value);
}
private static strictfp float neg(float value) {
return Float.intBitsToFloat(Float.floatToRawIntBits(value) ^ Integer.MIN_VALUE);
}
public static strictfp void close(FontCff.Context c) {
if (c.firstX != c.x || c.firstY != c.y) {
c.vertex(2, trunc(c.firstX), trunc(c.firstY), 0, 0, 0, 0);
}
}
public static strictfp void move(FontCff.Context c, float dx, float dy) {
close(c);
c.firstX = c.x += dx;
c.firstY = c.y += dy;
c.vertex(1, trunc(c.x), trunc(c.y), 0, 0, 0, 0);
}
public static strictfp void line(FontCff.Context c, float dx, float dy) {
c.x += dx;
c.y += dy;
c.vertex(2, trunc(c.x), trunc(c.y), 0, 0, 0, 0);
}
public static strictfp void curve(FontCff.Context c, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) {
float x1 = dx1 + c.x;
float y1 = dy1 + c.y;
float x2 = dx2 + x1;
float y2 = dy2 + y1;
c.x = dx3 + x2;
c.y = dy3 + y2;
c.vertex(4, trunc(c.x), trunc(c.y), trunc(x1), trunc(y1), trunc(x2), trunc(y2));
}
public static strictfp boolean run(FontCff.Info font, int glyph, FontCff.Context c) {
int header = 1;
int maskBits = 0;
int height = 0;
int sp = 0;
boolean hasSubrs = false;
float[] s = new float[48];
FontCff.Buffer[] returns = new FontCff.Buffer[10];
FontCff.Buffer local = font.localSubrs.copy();
FontCff.Buffer b = element(font.strings.copy(), glyph);
while (b.cursor < b.size) {
int op = b.byteValue();
int i = 0;
boolean clear = true;
switch (op) {
case 1:
case 3:
case 18:
case 23:
maskBits += sp / 2;
break;
case 2:
case 9:
case 13:
case 15:
case 16:
case 17:
case 28:
default:
if (op != 255 && op != 28 && op < 32) {
return false;
}
float value;
if (op == 255) {
value = (float)b.bytes(4) * 1.5258789E-5F;
} else {
b.skip(-1);
value = (float)((short)integer(b));
}
if (sp >= 48) {
return false;
}
s[sp++] = value;
clear = false;
break;
case 4:
if (sp < 1) {
return false;
}
move(c, 0.0F, s[sp - 1]);
header = 0;
break;
case 5:
if (sp < 2) {
return false;
}
while (i + 1 < sp) {
line(c, s[i], s[i + 1]);
i += 2;
}
break;
case 6:
case 7:
if (sp < 1) {
return false;
}
for (boolean horizontal = op == 6; i < sp; i++) {
line(c, horizontal ? s[i] : 0.0F, horizontal ? 0.0F : s[i]);
horizontal = !horizontal;
}
break;
case 8:
if (sp < 6) {
return false;
}
while (i + 5 < sp) {
curve(c, s[i], s[i + 1], s[i + 2], s[i + 3], s[i + 4], s[i + 5]);
i += 6;
}
break;
case 10:
case 29:
if (op == 10 && !hasSubrs) {
if (font.fdSelect.size != 0) {
local = glyphSubroutines(font, glyph);
}
hasSubrs = true;
}
if (sp < 1 || height >= 10) {
return false;
}
int number = trunc(s[--sp]);
returns[height] = b;
b = subroutine(op == 10 ? local : font.globalSubrs, number);
if (b.size == 0) {
return false;
}
height++;
b.cursor = 0;
clear = false;
break;
case 11:
if (height <= 0) {
return false;
}
b = returns[--height];
clear = false;
break;
case 12:
int escape = b.byteValue();
if (escape == 34) {
if (sp < 7) {
return false;
}
curve(c, s[0], 0.0F, s[1], s[2], s[3], 0.0F);
curve(c, s[4], 0.0F, s[5], neg(s[2]), s[6], 0.0F);
} else if (escape == 35) {
if (sp < 13) {
return false;
}
curve(c, s[0], s[1], s[2], s[3], s[4], s[5]);
curve(c, s[6], s[7], s[8], s[9], s[10], s[11]);
} else if (escape == 36) {
if (sp < 9) {
return false;
}
curve(c, s[0], s[1], s[2], s[3], s[4], 0.0F);
curve(c, s[5], 0.0F, s[6], s[7], s[8], neg(s[1] + s[3] + s[7]));
} else {
if (escape != 37) {
return false;
}
if (sp < 11) {
return false;
}
float dx = s[0] + s[2] + s[4] + s[6] + s[8];
float dy = s[1] + s[3] + s[5] + s[7] + s[9];
float absX = Float.intBitsToFloat(Float.floatToRawIntBits(dx) & 2147483647);
float absY = Float.intBitsToFloat(Float.floatToRawIntBits(dy) & 2147483647);
float dx6 = absX > absY ? s[10] : neg(dx);
float dy6 = absX > absY ? neg(dy) : s[10];
curve(c, s[0], s[1], s[2], s[3], s[4], s[5]);
curve(c, s[6], s[7], s[8], s[9], dx6, dy6);
}
break;
case 14:
close(c);
return true;
case 19:
case 20:
if (header != 0) {
maskBits += sp / 2;
}
header = 0;
b.skip((maskBits + 7) / 8);
break;
case 21:
if (sp < 2) {
return false;
}
move(c, s[sp - 2], s[sp - 1]);
header = 0;
break;
case 22:
if (sp < 1) {
return false;
}
move(c, s[sp - 1], 0.0F);
header = 0;
break;
case 24:
if (sp < 8) {
return false;
}
while (i + 5 < sp - 2) {
curve(c, s[i], s[i + 1], s[i + 2], s[i + 3], s[i + 4], s[i + 5]);
i += 6;
}
if (i + 1 >= sp) {
return false;
}
line(c, s[i], s[i + 1]);
break;
case 25:
if (sp < 8) {
return false;
}
while (i + 1 < sp - 6) {
line(c, s[i], s[i + 1]);
i += 2;
}
if (i + 5 >= sp) {
return false;
}
curve(c, s[i], s[i + 1], s[i + 2], s[i + 3], s[i + 4], s[i + 5]);
break;
case 26:
case 27:
if (sp < 4) {
return false;
}
float first = 0.0F;
if ((sp & 1) != 0) {
first = s[i++];
}
while (i + 3 < sp) {
if (op == 27) {
curve(c, s[i], first, s[i + 1], s[i + 2], s[i + 3], 0.0F);
} else {
curve(c, first, s[i], s[i + 1], s[i + 2], 0.0F, s[i + 3]);
}
first = 0.0F;
i += 4;
}
break;
case 30:
case 31:
if (sp < 4) {
return false;
}
for (boolean horizontal = op == 31; i + 3 < sp; i += 4) {
if (horizontal) {
curve(c, s[i], 0.0F, s[i + 1], s[i + 2], sp - i == 5 ? s[i + 4] : 0.0F, s[i + 3]);
} else {
curve(c, 0.0F, s[i], s[i + 1], s[i + 2], s[i + 3], sp - i == 5 ? s[i + 4] : 0.0F);
}
horizontal = !horizontal;
}
}
if (clear) {
sp = 0;
}
}
return false;
}
private FontCff() {
}
public static final strictfp class Buffer {
public final byte[] data;
public final int offset;
public final int size;
public int cursor;
public Buffer(byte[] data) {
this(data, 0, data.length);
}
public Buffer(byte[] data, int offset, int size) {
this.data = data;
this.offset = offset;
this.size = size;
}
public strictfp FontCff.Buffer copy() {
FontCff.Buffer b = new FontCff.Buffer(this.data, this.offset, this.size);
b.cursor = this.cursor;
return b;
}
public strictfp int byteValue() {
return this.cursor >= this.size ? 0 : this.data[this.offset + this.cursor++] & 0xFF;
}
public strictfp int bytes(int count) {
int v = 0;
for (int i = 0; i < count; i++) {
v = v << 8 | this.byteValue();
}
return v;
}
public strictfp void seek(int at) {
this.cursor = at >= 0 && at <= this.size ? at : this.size;
}
public strictfp void skip(int count) {
this.seek(this.cursor + count);
}
public strictfp FontCff.Buffer range(int at, int count) {
return at >= 0 && count >= 0 && at <= this.size && count <= this.size - at
? new FontCff.Buffer(this.data, this.offset + at, count)
: new FontCff.Buffer(new byte[0]);
}
}
public static final strictfp class Context {
public final boolean bounds;
public boolean started;
public float firstX;
public float firstY;
public float x;
public float y;
public int minX;
public int maxX;
public int minY;
public int maxY;
public int count;
public final List<FontRasterization.Vertex> vertices = new ArrayList<>();
public Context(boolean bounds) {
this.bounds = bounds;
}
private strictfp void track(int x, int y) {
if (x > this.maxX || !this.started) {
this.maxX = x;
}
if (y > this.maxY || !this.started) {
this.maxY = y;
}
if (x < this.minX || !this.started) {
this.minX = x;
}
if (y < this.minY || !this.started) {
this.minY = y;
}
this.started = true;
}
private strictfp void vertex(int type, int x, int y, int cx, int cy, int cx1, int cy1) {
if (this.bounds) {
this.track(x, y);
if (type == 4) {
this.track(cx, cy);
this.track(cx1, cy1);
}
} else {
this.vertices.add(new FontRasterization.Vertex(type, x, y, cx, cy, cx1, cy1));
}
this.count++;
}
}
public static final strictfp class Info {
public FontCff.Buffer cff;
public FontCff.Buffer strings;
public FontCff.Buffer globalSubrs;
public FontCff.Buffer localSubrs;
public FontCff.Buffer fdArray;
public FontCff.Buffer fdSelect;
public Info(
FontCff.Buffer cff, FontCff.Buffer strings, FontCff.Buffer global, FontCff.Buffer local, FontCff.Buffer dictionaries, FontCff.Buffer selectors
) {
this.cff = cff;
this.strings = strings;
this.globalSubrs = global;
this.localSubrs = local;
this.fdArray = dictionaries;
this.fdSelect = selectors;
}
}
}
@@ -0,0 +1,180 @@
package dev.slinky.client.render.font;
import dev.slinky.client.render.RenderConstants;
import java.util.List;
public final strictfp class FontGeometry {
private static strictfp float c(int at) {
return RenderConstants.f(at);
}
public static strictfp void quadratic(List<float[]> points, float x0, float y0, float x1, float y1, float x2, float y2, float flatness, int depth) {
if (depth <= 16) {
float quarter = c(2841456);
float half = c(2841472);
float packed = c(2841488);
depth++;
do {
float mx = (x1 + x1 + x0 + x2) * quarter;
float my = (y1 + y1 + y0 + y2) * quarter;
float dx = (x2 + x0) * half - mx;
float dy = (y2 + y0) * half - my;
float distance = dy * dy + dx * dx;
if (!(distance > flatness)) {
points.add(new float[]{x2, y2});
return;
}
float lx = (x0 + x1) * half;
float ly = (y1 + y0) * half;
quadratic(points, x0, y0, lx, ly, mx, my, flatness, depth);
x1 = (x1 + x2) * packed;
y1 = (y1 + y2) * packed;
x0 = mx;
y0 = my;
} while (++depth != 18);
}
}
public static strictfp void cubic(
List<float[]> points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float flatness, int depth
) {
if (depth <= 16) {
float half = c(2841504);
float packed = c(2841520);
int terminal = depth >= 18 ? depth : 17;
depth++;
int old;
do {
float dx = x3 - x0;
float dy = y3 - y0;
float longLength = (float)StrictMath.sqrt((double)(dx * dx + dy * dy));
dx = x3 - x2;
dy = y3 - y2;
double l23 = StrictMath.sqrt((double)(dx * dx + dy * dy));
float dx01 = x1 - x0;
float dy01 = y1 - y0;
float dx12 = x2 - x1;
float dy12 = y2 - y1;
double l01 = StrictMath.sqrt((double)(dx01 * dx01 + dy01 * dy01));
double l12 = StrictMath.sqrt((double)(dx12 * dx12 + dy12 * dy12));
float length = (float)(l01 + l12 + l23);
if (!(length * length - longLength * longLength > flatness)) {
points.add(new float[]{x3, y3});
return;
}
float x01 = (x0 + x1) * packed;
float x23 = (x3 + x2) * packed;
float y01 = (y0 + y1) * packed;
float y23 = (y3 + y2) * packed;
float x12 = (x1 + x2) * half;
float y12 = (y1 + y2) * half;
float x012 = (x12 + x01) * packed;
float x123 = (x12 + x23) * packed;
float y012 = (y12 + y01) * packed;
float y123 = (y12 + y23) * packed;
float mx = (x123 + x012) * half;
float my = (y123 + y012) * half;
cubic(points, x0, y0, x01, y01, x012, y012, mx, my, flatness, depth);
x0 = mx;
y0 = my;
x1 = x123;
y1 = y123;
x2 = x23;
y2 = y23;
old = depth++;
} while (old - terminal + 1 != 1);
}
}
private static strictfp void swap(FontGeometry.Edge[] edges, int a, int b) {
FontGeometry.Edge p = edges[a];
edges[a] = edges[b];
edges[b] = p;
}
public static strictfp void quickSort(FontGeometry.Edge[] edges, int offset, int count) {
while (count > 12) {
int middle = count >>> 1;
float a = edges[offset].y0;
float b = edges[offset + middle].y0;
float c = edges[offset + count - 1].y0;
if (!(a < b) == b < c) {
int pivot = !(a < c) != b < c ? 0 : count - 1;
swap(edges, offset + pivot, offset + middle);
}
swap(edges, offset, offset + middle);
int i = 1;
int j = count - 1;
while (true) {
float pivot = edges[offset].y0;
while (pivot > edges[offset + i].y0) {
i++;
}
while (edges[offset + j].y0 > pivot) {
j--;
}
if (i >= j) {
if (count - i <= j) {
quickSort(edges, offset + i, count - i);
count = j;
break;
}
quickSort(edges, offset, j);
offset += i;
count -= i;
break;
}
swap(edges, offset + i, offset + j);
i++;
j--;
}
}
}
public static strictfp void sort(FontGeometry.Edge[] edges) {
quickSort(edges, 0, edges.length);
for (int i = 1; i < edges.length; i++) {
FontGeometry.Edge value = edges[i];
int j;
for (j = i; j > 0 && edges[j - 1].y0 > value.y0; j--) {
edges[j] = edges[j - 1];
}
if (j != i) {
edges[j] = value;
}
}
}
private FontGeometry() {
}
public static final strictfp class Edge {
public float x0;
public float y0;
public float x1;
public float y1;
public int direction;
public Edge(float x0, float y0, float x1, float y1, int direction) {
this.x0 = x0;
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
this.direction = direction;
}
}
}
@@ -0,0 +1,302 @@
package dev.slinky.client.render.font;
import dev.slinky.client.gui.GuiMath;
import dev.slinky.client.numeric.FloatNumeric;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.client.render.RenderConstants;
import java.util.ArrayList;
import java.util.List;
public final strictfp class FontGlyphCache implements FontServices.Face {
private final FontGlyphCache.Stash stash;
private final FontGlyphCache.Font font;
private final FontGlyphCache.Source source;
public FontGlyphCache(FontGlyphCache.Stash stash, FontGlyphCache.Font font) {
this(stash, font, null);
}
public FontGlyphCache(FontGlyphCache.Stash stash, FontGlyphCache.Font font, FontGlyphCache.Source source) {
this.stash = stash;
this.font = font;
this.source = source;
}
private static strictfp float c(int at) {
return RenderConstants.f(at);
}
@Override
public strictfp int kern(int a, int b) {
return this.source == null ? FontTables.kerning(this.font.tables, a, b) : this.source.kern(a, b);
}
@Override
public strictfp float ascender() {
return this.font.ascender;
}
@Override
public strictfp float descender() {
return this.font.descender;
}
@Override
public strictfp float lineHeight() {
return this.font.lineHeight;
}
@Override
public strictfp int unitsPerEm() {
return this.source == null ? FontTables.u16(this.font.tables.data, this.font.tables.head + 18) : this.source.unitsPerEm();
}
@Override
public strictfp FontServices.Glyph glyph(int cp, int sizeBits, short blur, int bitmap) {
short size = (short)sizeBits;
if (size < 2) {
return null;
} else {
short clamped = (short)Math.min(blur, 20);
(this.source == null ? this.font.tables.scratch : this.source.scratch()).used = 0;
int bucket = FontServices.hash(cp);
int index = this.font.heads[bucket];
FontGlyphCache.Row row = null;
while (index != -1) {
FontGlyphCache.Row candidate = this.font.glyphs.get(index);
if (candidate.codepoint == cp && candidate.size == size && candidate.blur == clamped) {
row = candidate;
break;
}
index = candidate.next;
}
if (row == null || bitmap != 1 && (row.glyph.x0 < 0 || row.glyph.y0 < 0)) {
float scaleSize = (float)size / c(2838208);
FontGlyphCache.Font render = this.font;
int glyphIndex = this.source == null ? FontTables.map(this.font.tables, cp) : this.source.map(cp);
if (glyphIndex == 0) {
for (FontGlyphCache.Font fallback : this.font.fallbacks) {
int mapped = FontTables.map(fallback.tables, cp);
if (mapped != 0) {
render = fallback;
glyphIndex = mapped;
break;
}
}
}
FontTables.Font table = render.tables;
float scale = scaleSize / (float)(this.source == null ? FontTables.u16(table.data, table.head + 18) : this.source.unitsPerEm());
int padding = clamped + 2;
int advance;
if (this.source == null) {
int metricCount = FontTables.u16(table.data, table.hhea + 34);
int metric = table.hmtx + (glyphIndex < metricCount ? glyphIndex : metricCount - 1) * 4;
advance = FontTables.u16(table.data, metric);
} else {
advance = this.source.advance(glyphIndex);
}
int[] box = this.source == null ? FontTables.box(table, glyphIndex) : this.source.box(glyphIndex);
int x0 = 0;
int y0 = 0;
int x1 = 0;
int y1 = 0;
if (box != null) {
x0 = NumericMath.truncateInt(GuiMath.floor((float)box[0] * scale + 0.0F));
y0 = NumericMath.truncateInt(GuiMath.floor((float)(-box[3]) * scale + 0.0F));
x1 = NumericMath.truncateInt(NumericMath.ceiling((float)box[2] * scale + 0.0F));
y1 = NumericMath.truncateInt(NumericMath.ceiling((float)(-box[1]) * scale + 0.0F));
}
int boxWidth = x1 - x0;
int boxHeight = y1 - y0;
int width = boxWidth + padding * 2;
int height = boxHeight + padding * 2;
int atlasX = -1;
int atlasY = -1;
if (bitmap == 2) {
int[] packed = this.stash.atlas.add(width, height);
if (packed == null) {
if (this.stash.atlasError == null) {
return null;
}
this.stash.atlasError.run();
packed = this.stash.atlas.add(width, height);
if (packed == null) {
return null;
}
}
atlasX = packed[0];
atlasY = packed[1];
}
if (row == null) {
row = new FontGlyphCache.Row();
row.codepoint = cp;
row.size = size;
row.blur = clamped;
row.next = this.font.heads[bucket];
this.font.heads[bucket] = this.font.glyphs.size();
this.font.glyphs.add(row);
}
FontServices.Glyph g = row.glyph;
g.index = glyphIndex;
g.x0 = (short)atlasX;
g.y0 = (short)atlasY;
g.x1 = (short)(atlasX + width);
g.y1 = (short)(atlasY + height);
g.advance = (short)NumericMath.truncateInt((float)((short)advance) * scale * c(2838208));
g.xOffset = (short)(x0 - padding);
g.yOffset = (short)(y0 - padding);
if (bitmap == 1) {
return g;
} else {
int destination = ((short)atlasY + padding) * this.stash.stride + (short)atlasX + padding;
if (this.source == null) {
makeBitmap(table, this.stash.pixels, destination, boxWidth, boxHeight, this.stash.stride, scale, scale, 0.0F, 0.0F, glyphIndex);
} else if (boxWidth != 0 && boxHeight != 0) {
FontRasterization.rasterize(
this.stash.pixels,
destination,
boxWidth,
boxHeight,
this.stash.stride,
c(2837800),
this.source.shape(glyphIndex),
scale,
scale,
0.0F,
0.0F,
x0,
y0,
true,
this.source.scratch()
);
}
int border = g.y0 * this.stash.stride + g.x0;
for (int y = 0; y < height; y++) {
this.stash.pixels[border + this.stash.stride * y] = 0;
this.stash.pixels[border + this.stash.stride * y + width - 1] = 0;
}
for (int x = 0; x < width; x++) {
this.stash.pixels[border + x] = 0;
this.stash.pixels[border + (height - 1) * this.stash.stride + x] = 0;
}
if (blur > 0) {
(this.source == null ? this.font.tables.scratch : this.source.scratch()).used = 0;
float sigma = (float)clamped * c(2838212) + c(2838216);
float exponential = FloatNumeric.exp(c(2838220) / sigma);
int coefficient = NumericMath.truncateInt((c(2838216) - exponential) * c(2838224));
FontServices.blur(this.stash.pixels, border, width, height, this.stash.stride, coefficient);
}
this.stash.dirtyX = Math.min(this.stash.dirtyX, g.x0);
this.stash.dirtyY = Math.min(this.stash.dirtyY, g.y0);
this.stash.dirtyRight = Math.max(this.stash.dirtyRight, g.x1);
this.stash.dirtyBottom = Math.max(this.stash.dirtyBottom, g.y1);
return g;
}
} else {
return row.glyph;
}
}
}
public static strictfp void makeBitmap(
FontTables.Font font, byte[] output, int start, int width, int height, int stride, float scaleX, float scaleY, float shiftX, float shiftY, int glyph
) {
List<FontRasterization.Vertex> shape = FontTables.shape(font, glyph);
int[] box = FontTables.box(font, glyph);
int offsetX = 0;
int offsetY = 0;
if (box != null) {
offsetX = NumericMath.truncateInt(GuiMath.floor((float)box[0] * scaleX + shiftX));
offsetY = NumericMath.truncateInt(GuiMath.floor((float)(-box[3]) * scaleY + shiftY));
}
if (width != 0 && height != 0) {
FontRasterization.rasterize(
output, start, width, height, stride, c(2837800), shape, scaleX, scaleY, shiftX, shiftY, offsetX, offsetY, true, font.scratch
);
}
}
public static final strictfp class Font {
public final FontTables.Font tables;
public final int[] heads;
public final List<FontGlyphCache.Row> glyphs = new ArrayList<>();
public final List<FontGlyphCache.Font> fallbacks = new ArrayList<>();
public final float ascender;
public final float descender;
public final float lineHeight;
public Font(FontTables.Font tables, int[] heads, float ascender, float descender, float lineHeight) {
if (heads.length != 256) {
throw new IllegalArgumentException("Font hash table must contain 256 supplied entries");
} else {
this.tables = tables;
this.heads = heads;
this.ascender = ascender;
this.descender = descender;
this.lineHeight = lineHeight;
}
}
}
public static final class Row {
public final FontServices.Glyph glyph = new FontServices.Glyph();
public int codepoint;
public int next;
public short size;
public short blur;
}
public interface Source {
int unitsPerEm();
int map(int var1);
int advance(int var1);
int[] box(int var1);
int kern(int var1, int var2);
List<FontRasterization.Vertex> shape(int var1);
FontRasterization.Scratch scratch();
}
public static final strictfp class Stash {
public FontServices.Atlas atlas;
public byte[] pixels;
public int stride;
public int dirtyX;
public int dirtyY;
public int dirtyRight;
public int dirtyBottom;
public Runnable atlasError;
public Stash(FontServices.Atlas atlas, byte[] pixels, int stride, int dirtyX, int dirtyY, int dirtyRight, int dirtyBottom, Runnable atlasError) {
this.atlas = atlas;
this.pixels = pixels;
this.stride = stride;
this.dirtyX = dirtyX;
this.dirtyY = dirtyY;
this.dirtyRight = dirtyRight;
this.dirtyBottom = dirtyBottom;
this.atlasError = atlasError;
}
}
}
@@ -0,0 +1,408 @@
package dev.slinky.client.render.font;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.client.render.RenderConstants;
import dev.slinky.client.render.RenderMath;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
public final strictfp class FontRasterization {
private static strictfp float c(int at) {
return RenderConstants.f(at);
}
private static strictfp float neg(float value) {
return Float.intBitsToFloat(Float.floatToRawIntBits(value) ^ RenderConstants.i(2837712));
}
private static strictfp List<List<float[]>> contours(List<FontRasterization.Vertex> vertices, float flatness) {
List<List<float[]>> result = new ArrayList<>();
List<float[]> current = null;
float x = 0.0F;
float y = 0.0F;
for (FontRasterization.Vertex vertex : vertices) {
if (vertex.type == 1) {
current = new ArrayList<>();
result.add(current);
x = (float)vertex.x;
y = (float)vertex.y;
current.add(new float[]{x, y});
} else if (vertex.type == 2) {
x = (float)vertex.x;
y = (float)vertex.y;
current.add(new float[]{x, y});
} else if (vertex.type == 3) {
FontGeometry.quadratic(current, x, y, (float)vertex.cx, (float)vertex.cy, (float)vertex.x, (float)vertex.y, flatness, 0);
x = (float)vertex.x;
y = (float)vertex.y;
} else if (vertex.type == 4) {
FontGeometry.cubic(
current, x, y, (float)vertex.cx, (float)vertex.cy, (float)vertex.cx1, (float)vertex.cy1, (float)vertex.x, (float)vertex.y, flatness, 0
);
x = (float)vertex.x;
y = (float)vertex.y;
}
}
return result;
}
private static strictfp void clipped(float[] scan, int x, FontRasterization.Active edge, float x0, float y0, float x1, float y1) {
if (y0 != y1 && !(y0 > edge.end) && !(y1 < edge.start)) {
if (y0 < edge.start) {
x0 += (x1 - x0) * (edge.start - y0) / (y1 - y0);
y0 = edge.start;
}
if (y1 > edge.end) {
x1 += (x1 - x0) * (edge.end - y1) / (y1 - y0);
y1 = edge.end;
}
float left = (float)x;
float right = (float)(x + 1);
float height = (y1 - y0) * edge.direction;
if (x0 <= left && x1 <= left) {
scan[x] += height;
} else if (!(x0 >= right) || !(x1 >= right)) {
float fraction = (x1 - left + (x0 - left)) * c(2837740) + c(2837728);
scan[x] += fraction * height;
}
}
}
private static strictfp void clippedFill(float[] scan, float[] fill, int x, FontRasterization.Active edge, float x0, float y0, float x1, float y1) {
clipped(fill, x, edge, x0, y0, x1, y1);
}
private static strictfp void fill(float[] scan, float[] values, List<FontRasterization.Active> active, float top, float bottom) {
int width = scan.length;
float one = c(2837728);
float negativeHalf = c(2837740);
float half = c(2837744);
for (FontRasterization.Active edge : active) {
float x0 = edge.x;
float dx = edge.dx;
if (dx != 0.0F) {
float unclip = dx + x0;
float xTop = edge.start > top ? (edge.start - top) * dx + x0 : x0;
float xBottom = edge.end < bottom ? (edge.end - top) * dx + x0 : unclip;
if (xTop >= 0.0F && xBottom >= 0.0F && xTop < (float)width && xBottom < (float)width) {
float sy0 = RenderMath.max(edge.start, top);
float sy1 = RenderMath.min(edge.end, bottom);
int first = NumericMath.truncateInt(xTop);
int last = NumericMath.truncateInt(xBottom);
if (first == last) {
float fraction = ((xBottom - (float)first + (xTop - (float)first)) * negativeHalf + one) * edge.direction;
scan[first] += fraction * (sy1 - sy0);
values[first + 1] = (sy1 - sy0) * edge.direction + values[first + 1];
} else {
float left = xTop;
float right = xBottom;
float dy = edge.dy;
float origin = x0;
if (xTop > xBottom) {
float flipped0 = top - sy0 + bottom;
float flipped1 = top - sy1 + bottom;
sy1 = flipped0;
sy0 = flipped1;
dy = neg(dy);
left = xBottom;
right = xTop;
origin = unclip;
first = NumericMath.truncateInt(xBottom);
last = NumericMath.truncateInt(xTop);
}
float cross = ((float)(first + 1) - origin) * dy + top;
float area = (cross - sy0) * edge.direction;
float fraction = (left - (float)first + one) * negativeHalf + one;
scan[first] += fraction * area;
int span = last - (first + 1);
if (span > 0) {
float step = dy * edge.direction;
float halfStep = step * half;
for (int x = first + 1; x < last; x++) {
scan[x] += halfStep + area;
area += step;
}
}
float finalY = dy * (float)span + cross;
float lastFraction = ((right - (float)last) * negativeHalf + one) * edge.direction;
scan[last] += (sy1 - finalY) * lastFraction + area;
values[last + 1] = (sy1 - sy0) * edge.direction + values[last + 1];
}
} else {
for (int x = 0; x < width; x++) {
float leftx = (float)x;
float rightx = (float)(x + 1);
float yLeft = (leftx - x0) / dx + top;
float yRight = (rightx - x0) / dx + top;
if (x0 < leftx && unclip > rightx) {
clipped(scan, x, edge, x0, top, leftx, yLeft);
clipped(scan, x, edge, leftx, yLeft, rightx, yRight);
clipped(scan, x, edge, rightx, yRight, unclip, bottom);
} else if (unclip < leftx && x0 > rightx) {
clipped(scan, x, edge, x0, top, rightx, yRight);
clipped(scan, x, edge, rightx, yRight, leftx, yLeft);
clipped(scan, x, edge, leftx, yLeft, unclip, bottom);
} else if ((!(x0 < leftx) || !(unclip > leftx)) && (!(unclip < leftx) || !(x0 > leftx))) {
if ((!(x0 < rightx) || !(unclip > rightx)) && (!(unclip < rightx) || !(x0 > rightx))) {
clipped(scan, x, edge, x0, top, unclip, bottom);
} else {
clipped(scan, x, edge, x0, top, rightx, yRight);
clipped(scan, x, edge, rightx, yRight, unclip, bottom);
}
} else {
clipped(scan, x, edge, x0, top, leftx, yLeft);
clipped(scan, x, edge, leftx, yLeft, unclip, bottom);
}
}
}
} else if (x0 < (float)width) {
if (x0 >= 0.0F) {
int xx = NumericMath.truncateInt(x0);
clipped(scan, xx, edge, x0, top, x0, bottom);
clippedFill(scan, values, xx + 1, edge, x0, top, x0, bottom);
} else {
clippedFill(scan, values, 0, edge, x0, top, x0, bottom);
}
}
}
}
public static strictfp void rasterize(
byte[] output,
int offset,
int width,
int height,
int stride,
float flatness,
List<FontRasterization.Vertex> vertices,
float scaleX,
float scaleY,
float shiftX,
float shiftY,
int offsetX,
int offsetY,
boolean invert,
FontRasterization.Scratch scratch
) {
if (!vertices.isEmpty()) {
int contourCount = 0;
for (FontRasterization.Vertex v : vertices) {
if (v.type == 1) {
contourCount++;
}
}
if (contourCount != 0 && scratch.reserve(contourCount * 4)) {
float tolerance = flatness / RenderMath.min(scaleY, scaleX);
flatness = tolerance * tolerance;
List<List<float[]>> curves = contours(vertices, flatness);
int total = 0;
for (List<float[]> curve : curves) {
total += curve.size();
}
if (scratch.reserve(total * 8)) {
curves = contours(vertices, flatness);
if (invert) {
scaleY = neg(scaleY);
}
if (scratch.reserve(total * 20 + 20)) {
List<FontGeometry.Edge> edges = new ArrayList<>();
for (List<float[]> curve : curves) {
float[] previous = curve.get(curve.size() - 1);
for (float[] point : curve) {
if (previous[1] != point[1]) {
boolean forward = invert ? previous[1] > point[1] : point[1] > previous[1];
float[] first = forward ? previous : point;
float[] last = forward ? point : previous;
edges.add(
new FontGeometry.Edge(
first[0] * scaleX + shiftX, first[1] * scaleY + shiftY, last[0] * scaleX + shiftX, last[1] * scaleY + shiftY, forward ? 1 : 0
)
);
}
previous = point;
}
}
FontGeometry.Edge[] sorted = edges.toArray(new FontGeometry.Edge[0]);
FontGeometry.sort(sorted);
if (width < 65 || scratch.reserve(width * 8 + 4)) {
if (height > 0) {
float[] scan = new float[width];
float[] fill = new float[width + 1];
List<FontRasterization.Active> active = new ArrayList<>();
FontRasterization.Pool pool = new FontRasterization.Pool(scratch);
int edgeIndex = 0;
float one = c(2837728);
int row = 0;
for (int y = offsetY; row < height; y++) {
float top = (float)y;
float bottom = top + one;
Arrays.fill(scan, 0.0F);
Arrays.fill(fill, 0.0F);
Iterator<FontRasterization.Active> it = active.iterator();
while (it.hasNext()) {
FontRasterization.Active a = it.next();
if (top >= a.end) {
it.remove();
pool.release();
}
}
while (edgeIndex < sorted.length && bottom >= sorted[edgeIndex].y0) {
FontGeometry.Edge e = sorted[edgeIndex++];
if (e.y0 != e.y1 && pool.allocate()) {
FontRasterization.Active a = new FontRasterization.Active();
a.dx = (e.x1 - e.x0) / (e.y1 - e.y0);
a.dy = a.dx == 0.0F ? 0.0F : one / a.dx;
a.x = a.dx * (top - e.y0) + e.x0 - (float)offsetX;
a.direction = c(e.direction != 0 ? 2837732 : 2837736);
a.start = e.y0;
a.end = e.y1;
if (row == 0 && offsetY != 0 && top > a.end) {
a.end = top;
}
active.add(0, a);
}
}
fill(scan, fill, active, top, bottom);
float sum = 0.0F;
for (int x = 0; x < width; x++) {
sum += fill[x];
float coverage = Float.intBitsToFloat(Float.floatToRawIntBits(scan[x] + sum) & RenderConstants.i(2837760));
int value = NumericMath.truncateInt(coverage * c(2837776) + c(2837744));
if (value >= 255) {
value = 255;
}
output[offset + stride * row + x] = (byte)value;
}
for (FontRasterization.Active a : active) {
a.x = a.dx + a.x;
}
row++;
}
}
}
}
}
}
}
}
private FontRasterization() {
}
private static final strictfp class Active {
float x;
float dx;
float dy;
float direction;
float start;
float end;
private Active() {
}
}
private static final strictfp class Pool {
final FontRasterization.Scratch scratch;
int remaining;
int free;
Pool(FontRasterization.Scratch s) {
this.scratch = s;
}
strictfp boolean allocate() {
if (this.free > 0) {
this.free--;
return true;
} else if (this.remaining > 0) {
this.remaining--;
return true;
} else if (this.scratch.used >= 70385) {
this.scratch.error.run();
return false;
} else {
this.scratch.used += 25616;
if (!this.scratch.available) {
return false;
} else {
this.remaining = 799;
return true;
}
}
}
strictfp void release() {
this.free++;
}
}
public static final strictfp class Scratch {
public int used;
public boolean available = true;
public final Runnable error;
public Scratch(Runnable error) {
this.error = Objects.requireNonNull(error);
}
public strictfp boolean reserve(int bytes) {
int next = this.used + (bytes + 15 & -16);
if (next >= 96001) {
this.error.run();
return false;
} else {
this.used = next;
return this.available;
}
}
}
public static final strictfp class Vertex {
public short x;
public short y;
public short cx;
public short cy;
public short cx1;
public short cy1;
public int type;
public Vertex(int type, int x, int y, int cx, int cy, int cx1, int cy1) {
this.type = type;
this.x = (short)x;
this.y = (short)y;
this.cx = (short)cx;
this.cy = (short)cy;
this.cx1 = (short)cx1;
this.cy1 = (short)cy1;
}
}
}
@@ -0,0 +1,647 @@
package dev.slinky.client.render.font;
import dev.slinky.client.gui.GuiMath;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.client.render.RenderConstants;
import dev.slinky.client.render.RenderMath;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final strictfp class FontServices {
private static strictfp float c(int at) {
return RenderConstants.f(at);
}
private static strictfp int byteAt(int address) {
return RenderConstants.i(address & -4) >>> (address & 3) * 8 & 0xFF;
}
public static strictfp FontServices.Quad quad(
FontServices.Face face,
FontServices.Glyph glyph,
int previous,
float scale,
float spacing,
FontServices.Cursor cursor,
boolean downward,
float inverseWidth,
float inverseHeight
) {
if (previous != -1) {
int kern = face.kern(previous, glyph.index);
float delta = (float)NumericMath.truncateInt((float)kern * scale + spacing + c(2838228));
cursor.x += delta;
}
float xoff = (float)((short)(glyph.xOffset + 1));
float yoff = (float)((short)(glyph.yOffset + 1));
float top = (float)(glyph.y0 + 1);
float bottom = (float)(glyph.y1 - 1);
float left = (float)(glyph.x0 + 1);
float right = (float)(glyph.x1 - 1);
FontServices.Quad q = new FontServices.Quad();
q.x0 = GuiMath.floor(xoff + cursor.x);
q.y0 = GuiMath.floor(downward ? cursor.y + yoff : cursor.y - yoff);
q.x1 = q.x0 + right - left;
q.y1 = downward ? q.y0 + bottom - top : q.y0 - bottom + top;
q.s0 = left * inverseWidth;
q.t0 = top * inverseHeight;
q.s1 = right * inverseWidth;
q.t1 = bottom * inverseHeight;
float advance = (float)NumericMath.truncateInt((float)glyph.advance / c(2838232) + c(2838228));
cursor.x += advance;
return q;
}
private static strictfp float vertical(
FontServices.Face face, boolean downward, int alignment, float size, int ten, int half, int negativeTen, int negativeHalf, boolean sizeFirst
) {
boolean negate = false;
float metric;
if ((alignment & 8) != 0) {
metric = face.ascender();
negate = !downward;
} else if ((alignment & 16) != 0) {
metric = (face.ascender() + face.descender()) * c(downward ? half : negativeHalf);
} else {
if ((alignment & 96) != 32) {
return 0.0F;
}
metric = face.descender();
negate = !downward;
}
return (sizeFirst ? size * metric : metric * size) / c(negate ? negativeTen : ten);
}
public static strictfp float bounds(
FontServices.Face face,
float x,
float y,
byte[] text,
int end,
float fontSize,
float blur,
float spacing,
int alignment,
boolean downward,
float inverseWidth,
float inverseHeight,
float[] bounds
) {
int sizeBits = NumericMath.truncateInt(fontSize * c(2838192));
float size = (float)((short)sizeBits);
y += vertical(face, downward, alignment, size, 2838192, 2838196, 2838200, 2838204, false);
float minX = x;
float maxX = x;
float minY = y;
float maxY = y;
float scale = size / c(2838192) / (float)face.unitsPerEm();
FontServices.Cursor cursor = new FontServices.Cursor(x, y);
FontServices.Utf8 utf8 = new FontServices.Utf8();
int previous = -1;
for (int i = 0; i < end; i++) {
if (utf8.next(text[i])) {
FontServices.Glyph glyph = face.glyph(utf8.codepoint, sizeBits, (short)NumericMath.truncateInt(blur), 1);
if (glyph == null) {
previous = -1;
} else {
FontServices.Quad q = quad(face, glyph, previous, scale, spacing, cursor, downward, inverseWidth, inverseHeight);
float low = downward ? q.y0 : q.y1;
float high = downward ? q.y1 : q.y0;
minY = RenderMath.min(low, minY);
maxY = high > maxY ? high : maxY;
minX = RenderMath.min(q.x0, minX);
maxX = RenderMath.max(q.x1, maxX);
previous = glyph.index;
}
}
}
float advance = cursor.x - x;
if ((alignment & 1) == 0) {
if ((alignment & 4) != 0) {
minX -= advance;
maxX -= advance;
} else if ((alignment & 2) != 0) {
float offset = c(2838204) * advance;
minX += offset;
maxX += offset;
}
}
if (bounds != null) {
bounds[0] = minX;
bounds[1] = minY;
bounds[2] = maxX;
bounds[3] = maxY;
}
return advance;
}
public static strictfp float[] lineBounds(FontServices.Face face, float y, float fontSize, int alignment, boolean downward) {
float size = (float)((short)NumericMath.truncateInt(fontSize * c(2838324)));
y += vertical(face, downward, alignment, size, 2838324, 2838328, 2838332, 2838336, false);
if (downward) {
y -= face.ascender() * size / c(2838324);
return new float[]{y, size * face.lineHeight() / c(2838324) + y};
} else {
y = face.descender() * size / c(2838324) + y;
return new float[]{y - size * face.lineHeight() / c(2838324), y};
}
}
public static strictfp int hash(int codepoint) {
int h = ~(codepoint << 15) + codepoint;
h ^= h >>> 10;
h *= 9;
h ^= h >>> 6;
h = ~(h << 11) + h;
h ^= h >>> 16;
return h & 0xFF;
}
private static strictfp int blurStep(byte[] pixels, int at, int accumulator, int coefficient) {
int difference = ((pixels[at] & 255) << 7) - accumulator;
accumulator += coefficient * difference >> 16;
pixels[at] = (byte)(accumulator >>> 7);
return accumulator;
}
public static strictfp void blur(byte[] pixels, int offset, int width, int height, int stride, int coefficient) {
for (int pass = 0; pass < 2; pass++) {
for (int x = 0; x < width; x++) {
int base = offset + x;
int a = 0;
for (int y = 1; y < height; y++) {
a = blurStep(pixels, base + y * stride, a, coefficient);
}
pixels[base + (height - 1) * stride] = 0;
a = 0;
for (int y = height - 2; y >= 0; y--) {
a = blurStep(pixels, base + y * stride, a, coefficient);
}
pixels[base] = 0;
}
for (int y = 0; y < height; y++) {
int base = offset + y * stride;
int a = 0;
for (int x = 1; x < width; x++) {
a = blurStep(pixels, base + x, a, coefficient);
}
pixels[base + width - 1] = 0;
a = 0;
for (int x = width - 2; x >= 0; x--) {
a = blurStep(pixels, base + x, a, coefficient);
}
pixels[base] = 0;
}
}
}
public static strictfp float transformScale(float[] matrix, float bias, float quantum, float max, float deviceRatio) {
float x = (float)StrictMath.sqrt((double)(matrix[0] * matrix[0] + matrix[2] * matrix[2]));
float y = (float)StrictMath.sqrt((double)(matrix[1] * matrix[1] + matrix[3] * matrix[3]));
float average = y + x;
average = average * bias / quantum + bias;
float quantized = (float)NumericMath.truncateInt(average);
return RenderMath.min(quantized * quantum, max) * deviceRatio;
}
public static strictfp float canvasBounds(
FontServices.Face face,
int font,
float x,
float y,
byte[] text,
int end,
float fontSize,
float blur,
float spacing,
int alignment,
boolean downward,
float inverseWidth,
float inverseHeight,
float[] transform,
float deviceRatio,
float[] output
) {
if (font == -1) {
return 0.0F;
} else {
float scale = transformScale(transform, c(2840336), c(2840340), c(2840344), deviceRatio);
float inverse = c(2840348) / scale;
float scaledY = y * scale;
float result = bounds(
face, x * scale, scaledY, text, end, fontSize * scale, blur * scale, spacing * scale, alignment, downward, inverseWidth, inverseHeight, output
);
if (output != null) {
float[] line = lineBounds(face, scaledY, fontSize * scale, alignment, downward);
output[1] = line[0];
output[3] = line[1];
for (int i = 0; i < 4; i++) {
output[i] = inverse * output[i];
}
}
return inverse * result;
}
}
public static strictfp float[][] textVertices(FontServices.Quad q, float inverse, float[] transform) {
float y0 = q.y0;
float y1 = q.y1;
float t0 = q.t0;
float t1 = q.t1;
float determinant = transform[3] * transform[0] - transform[1] * transform[2];
if (0.0F > determinant) {
float swap = y0;
y0 = y1;
y1 = swap;
swap = t0;
t0 = t1;
t1 = swap;
}
float x0 = q.x0 * inverse;
float x1 = q.x1 * inverse;
y0 *= inverse;
y1 *= inverse;
float[] p0 = transform(transform, x0, y0);
float[] p1 = transform(transform, x1, y0);
float[] p2 = transform(transform, x1, y1);
float[] p3 = transform(transform, x0, y1);
return new float[][]{
{p0[0], p0[1], q.s0, t0},
{p2[0], p2[1], q.s1, t1},
{p1[0], p1[1], q.s1, t0},
{p0[0], p0[1], q.s0, t0},
{p3[0], p3[1], q.s0, t1},
{p2[0], p2[1], q.s1, t1}
};
}
private static strictfp float[] transform(float[] matrix, float x, float y) {
return new float[]{x * matrix[0] + y * matrix[2] + matrix[4], x * matrix[1] + y * matrix[3] + matrix[5]};
}
public static strictfp float drawText(
FontServices.Iterator iterator, float originalX, float scale, float[] transform, FontServices.TextBackend backend, FontServices.TextStats stats
) {
int length = iterator.bytes.length;
int capacity = (length >= 3 ? length : 2) * 6;
if (!backend.reserve(capacity + 255 & 2147483392)) {
return originalX;
} else {
List<float[]> vertices = new ArrayList<>();
FontServices.Quad quad = new FontServices.Quad();
int[] previous = iteratorState(iterator);
float[] previousPosition = new float[]{iterator.cursor.x, iterator.cursor.y};
float inverse = c(2840212) / scale;
while (iterator.next(quad)) {
if (iterator.previous == -1) {
if (!vertices.isEmpty()) {
backend.draw(vertices);
stats.drawCalls++;
stats.triangles = stats.triangles + vertices.size() / 3;
}
if (!backend.allocateTextAtlas()) {
vertices.clear();
break;
}
restoreIterator(iterator, previous, previousPosition);
iterator.next(quad);
vertices.clear();
if (iterator.previous == -1) {
break;
}
}
previous = iteratorState(iterator);
previousPosition = new float[]{iterator.cursor.x, iterator.cursor.y};
if (vertices.size() + 6 <= capacity) {
Collections.addAll(vertices, textVertices(quad, inverse, transform));
}
}
backend.uploadDirtyTexture();
backend.draw(vertices);
stats.drawCalls++;
stats.triangles = stats.triangles + vertices.size() / 3;
return iterator.cursor.x / scale;
}
}
private static strictfp int[] iteratorState(FontServices.Iterator i) {
return new int[]{i.next, i.start, i.previous, i.utf8.state, i.utf8.codepoint, Float.floatToRawIntBits(i.x), Float.floatToRawIntBits(i.y)};
}
private static strictfp void restoreIterator(FontServices.Iterator i, int[] state, float[] cursor) {
i.next = state[0];
i.start = state[1];
i.previous = state[2];
i.utf8.state = state[3];
i.utf8.codepoint = state[4];
i.x = Float.intBitsToFloat(state[5]);
i.y = Float.intBitsToFloat(state[6]);
i.cursor.x = cursor[0];
i.cursor.y = cursor[1];
}
public static final strictfp class Atlas {
public final int width;
public final int height;
public final List<FontServices.Atlas.Node> nodes = new ArrayList<>();
public Atlas(int width, int height) {
this.width = width;
this.height = height;
this.nodes.add(new FontServices.Atlas.Node(0, 0, width));
}
public strictfp int[] add(int w, int h) {
int count = this.nodes.size();
int best = -1;
int bestX = -1;
int bestY = -1;
int bestHeight = this.height;
int bestWidth = this.width;
for (int index = 0; index < count; index++) {
FontServices.Atlas.Node first = this.nodes.get(index);
int x = first.x;
if (w + x <= this.width) {
int y = first.y;
int remaining = w;
int scan = index;
boolean fit = true;
while (remaining > 0) {
if (scan >= count) {
fit = false;
break;
}
FontServices.Atlas.Node node = this.nodes.get(scan++);
y = Math.max(y, node.y);
if (y + h > this.height) {
fit = false;
break;
}
remaining -= node.width;
}
if (fit && y != -1) {
int bottom = y + h;
if (bottom < bestHeight || bottom == bestHeight && first.width < bestWidth) {
best = index;
bestX = x;
bestY = y;
bestHeight = bottom;
bestWidth = first.width;
}
}
}
}
if (best == -1) {
return null;
} else {
this.nodes.add(best, new FontServices.Atlas.Node(bestX, bestY + h, w));
int indexx = best + 1;
while (indexx < this.nodes.size()) {
FontServices.Atlas.Node previous = this.nodes.get(indexx - 1);
FontServices.Atlas.Node current = this.nodes.get(indexx);
int end = previous.x + previous.width;
int x = current.x;
if (end <= x) {
break;
}
current.x = (short)end;
current.width = (short)(x - end + current.width);
if (current.width > 0) {
break;
}
this.nodes.remove(indexx);
}
int i = 0;
while (i < this.nodes.size() - 1) {
FontServices.Atlas.Node a = this.nodes.get(i);
FontServices.Atlas.Node b = this.nodes.get(i + 1);
if (a.y != b.y) {
i++;
} else {
a.width = (short)(a.width + b.width);
this.nodes.remove(i + 1);
}
}
return new int[]{bestX, bestY};
}
}
public static final strictfp class Node {
public short x;
public short y;
public short width;
Node(int x, int y, int width) {
this.x = (short)x;
this.y = (short)y;
this.width = (short)width;
}
}
}
public static final strictfp class Cursor {
public float x;
public float y;
public Cursor(float x, float y) {
this.x = x;
this.y = y;
}
}
public interface Face {
FontServices.Glyph glyph(int var1, int var2, short var3, int var4);
int kern(int var1, int var2);
float ascender();
float descender();
float lineHeight();
int unitsPerEm();
}
public static final strictfp class Glyph {
public int index;
public short x0;
public short y0;
public short x1;
public short y1;
public short advance;
public short xOffset;
public short yOffset;
}
public static final strictfp class Iterator {
public final FontServices.Face face;
public final FontServices.Cursor cursor;
public final byte[] bytes;
public final FontServices.Utf8 utf8 = new FontServices.Utf8();
public int next;
public int start;
public int previous = -1;
public short size;
public short blur;
public float scale;
public float spacing;
public final int bitmapRequired;
public float x;
public float y;
public final boolean downward;
public float inverseWidth;
public float inverseHeight;
public Iterator(
FontServices.Face face,
float x,
float y,
byte[] text,
float fontSize,
float blur,
float spacing,
int alignment,
int bitmapRequired,
boolean downward,
float inverseWidth,
float inverseHeight
) {
this.face = face;
this.bytes = (byte[])text.clone();
int raw = NumericMath.truncateInt(fontSize * FontServices.c(2838236));
this.size = (short)raw;
this.blur = (short)NumericMath.truncateInt(blur);
this.scale = (float)raw / FontServices.c(2838236) / (float)face.unitsPerEm();
this.spacing = spacing;
this.bitmapRequired = bitmapRequired;
this.downward = downward;
this.inverseWidth = inverseWidth;
this.inverseHeight = inverseHeight;
if ((alignment & 1) == 0) {
if ((alignment & 4) != 0) {
x -= FontServices.bounds(face, x, y, text, text.length, fontSize, blur, spacing, alignment, downward, inverseWidth, inverseHeight, null);
} else if ((alignment & 2) != 0) {
x += FontServices.bounds(face, x, y, text, text.length, fontSize, blur, spacing, alignment, downward, inverseWidth, inverseHeight, null)
* FontServices.c(2838240);
}
}
y += FontServices.vertical(face, downward, alignment, (float)this.size, 2838236, 2838244, 2838248, 2838240, true);
this.cursor = new FontServices.Cursor(x, y);
this.x = x;
this.y = y;
}
public strictfp boolean next(FontServices.Quad output) {
this.start = this.next;
if (this.next == this.bytes.length) {
return false;
} else {
do {
int index = this.next++;
if (this.utf8.next(this.bytes[index])) {
this.x = this.cursor.x;
this.y = this.cursor.y;
FontServices.Glyph glyph = this.face.glyph(this.utf8.codepoint, this.size & '\uffff', this.blur, this.bitmapRequired);
if (glyph == null) {
this.previous = -1;
} else {
FontServices.Quad q = FontServices.quad(
this.face, glyph, this.previous, this.scale, this.spacing, this.cursor, this.downward, this.inverseWidth, this.inverseHeight
);
output.x0 = q.x0;
output.y0 = q.y0;
output.x1 = q.x1;
output.y1 = q.y1;
output.s0 = q.s0;
output.t0 = q.t0;
output.s1 = q.s1;
output.t1 = q.t1;
this.previous = glyph.index;
}
break;
}
} while (this.next != this.bytes.length);
return true;
}
}
}
public static final strictfp class Quad {
public float x0;
public float y0;
public float x1;
public float y1;
public float s0;
public float t0;
public float s1;
public float t1;
}
public interface TextBackend {
boolean reserve(int var1);
boolean allocateTextAtlas();
void draw(List<float[]> var1);
void uploadDirtyTexture();
}
public static final strictfp class TextStats {
public int drawCalls;
public int triangles;
}
public static final strictfp class Utf8 {
public int state;
public int codepoint;
public strictfp boolean next(int b) {
b &= 255;
int type = FontServices.byteAt(2842960 + b);
this.codepoint = this.state != 0 ? this.codepoint << 6 | b & 63 : 255 >>> (type & 31) & b;
this.state = FontServices.byteAt(2843216 + this.state + type);
return this.state == 0;
}
}
}
@@ -0,0 +1,534 @@
package dev.slinky.client.render.font;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.client.render.RenderConstants;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public final strictfp class FontTables {
public static strictfp int u16(byte[] b, int p) {
return (b[p] & 0xFF) << 8 | b[p + 1] & 0xFF;
}
public static strictfp int s16(byte[] b, int p) {
return (short)u16(b, p);
}
public static strictfp int u32(byte[] b, int p) {
return (b[p] & 0xFF) << 24 | (b[p + 1] & 0xFF) << 16 | (b[p + 2] & 0xFF) << 8 | b[p + 3] & 0xFF;
}
public static strictfp int offset(FontTables.Font f, int glyph) {
if (glyph < f.glyphCount && f.locaFormat <= 1) {
int first = f.locaFormat == 0 ? u16(f.data, f.loca + glyph * 2) * 2 : u32(f.data, f.loca + glyph * 4);
int second = f.locaFormat == 0 ? u16(f.data, f.loca + glyph * 2 + 2) * 2 : u32(f.data, f.loca + glyph * 4 + 4);
return first == second ? -1 : f.glyf + first;
} else {
return -1;
}
}
public static strictfp int map(FontTables.Font f, int cp) {
byte[] b = f.data;
int at = f.cmap;
int format = u16(b, at);
if (format == 0) {
return cp >= u16(b, at + 2) - 6 ? 0 : b[at + 6 + cp] & 0xFF;
} else if (format == 6) {
int first = u16(b, at + 6);
int count = u16(b, at + 8);
return Integer.compareUnsigned(cp, first) >= 0 && Integer.compareUnsigned(cp, first + count) < 0 ? u16(b, at + 10 + (cp - first) * 2) : 0;
} else if (format == 4) {
if (cp > 65535) {
return 0;
} else {
int segments = u16(b, at + 6) >>> 1;
int range = u16(b, at + 8) >>> 1;
int selector = u16(b, at + 10);
int shift = u16(b, at + 12) >>> 1;
int endCounts = at + 14;
int search = endCounts;
if (cp >= u16(b, endCounts + shift * 2)) {
search = endCounts + shift * 2;
}
for (search -= 2; selector != 0; selector--) {
range >>>= 1;
if (cp > u16(b, search + range * 2)) {
search += range * 2;
}
}
search += 2;
int item = search - endCounts >>> 1 & 65535;
int first = u16(b, at + 16 + segments * 2 + item * 2);
if (cp < first) {
return 0;
} else {
int word = at + 16 + segments * 6 + item * 2;
int relative = u16(b, word);
return relative == 0 ? cp + u16(b, at + 16 + segments * 4 + item * 2) & 65535 : u16(b, word + relative + (cp - first) * 2);
}
}
} else {
if (format == 12 || format == 13) {
int low = 0;
int high = u32(b, at + 12);
while (low < high) {
int mid = low + (high - low >>> 1);
int group = at + 16 + mid * 12;
int first = u32(b, group);
int last = u32(b, group + 4);
if (Integer.compareUnsigned(cp, first) < 0) {
high = mid;
} else {
if (Integer.compareUnsigned(cp, last) <= 0) {
return u32(b, group + 8) + (format == 12 ? cp - first : 0);
}
low = mid + 1;
}
}
}
return 0;
}
}
public static strictfp int coverage(byte[] b, int at, int glyph) {
int format = u16(b, at);
int low = 0;
int high = u16(b, at + 2) - 1;
while (low <= high) {
int mid = low + high >> 1;
if (format == 1) {
int value = u16(b, at + 4 + mid * 2);
if (glyph < value) {
high = mid - 1;
} else {
if (Integer.compareUnsigned(glyph, value) <= 0) {
return mid;
}
low = mid + 1;
}
} else {
if (format != 2) {
break;
}
int row = at + 4 + mid * 6;
int first = u16(b, row);
int last = u16(b, row + 2);
if (glyph < first) {
high = mid - 1;
} else {
if (Integer.compareUnsigned(glyph, last) <= 0) {
return glyph - first + u16(b, row + 4);
}
low = mid + 1;
}
}
}
return -1;
}
public static strictfp int glyphClass(byte[] b, int at, int glyph) {
int format = u16(b, at);
if (format == 1) {
int first = u16(b, at + 2);
int count = u16(b, at + 4);
return glyph >= first && Integer.compareUnsigned(glyph, first + count) < 0 ? u16(b, at + 6 + (glyph - first) * 2) : -1;
} else {
if (format == 2) {
int low = 0;
int high = u16(b, at + 2) - 1;
while (low <= high) {
int mid = low + high >> 1;
int row = at + 4 + mid * 6;
if (glyph < u16(b, row)) {
high = mid - 1;
} else {
if (Integer.compareUnsigned(glyph, u16(b, row + 2)) <= 0) {
return u16(b, row + 4);
}
low = mid + 1;
}
}
}
return -1;
}
}
public static strictfp int kerning(FontTables.Font f, int previous, int current) {
byte[] b = f.data;
if (f.gpos == 0) {
if (f.kern == 0) {
return 0;
} else {
int at = f.kern;
if ((b[at + 2] | b[at + 3]) != 0 && u16(b, at + 8) == 1) {
int key = previous << 16 | current;
int low = 0;
int high = u16(b, at + 10) - 1;
while (low <= high) {
int mid = low + high >> 1;
int row = at + 18 + mid * 6;
int value = u32(b, row);
if (Integer.compareUnsigned(key, value) < 0) {
high = mid - 1;
} else {
if (Integer.compareUnsigned(key, value) <= 0) {
return s16(b, row + 4);
}
low = mid + 1;
}
}
return 0;
} else {
return 0;
}
}
} else {
int table = f.gpos;
if (u16(b, table) == 1 && (b[table + 2] | b[table + 3]) == 0) {
int lookups = table + u16(b, table + 8);
int count = u16(b, lookups);
for (int i = 0; i < count; i++) {
int lookup = lookups + u16(b, lookups + 2 + i * 2);
if (u16(b, lookup) == 2) {
int subtables = u16(b, lookup + 4);
for (int j = 0; j < subtables; j++) {
int pair = lookup + u16(b, lookup + 6 + j * 2);
int format = u16(b, pair);
int covered = coverage(b, pair + u16(b, pair + 2), previous);
if (covered != -1) {
if (format == 1) {
if (u16(b, pair + 4) != 4 || (b[pair + 6] | b[pair + 7]) != 0) {
return 0;
}
int values = pair + u16(b, pair + 10 + covered * 2);
int low = 0;
int high = u16(b, values) - 1;
while (low <= high) {
int mid = low + high >> 1;
int record = values + 2 + mid * 4;
if (current < u16(b, record)) {
high = mid - 1;
} else {
if (Integer.compareUnsigned(current, u16(b, record)) <= 0) {
return s16(b, record + 2);
}
low = mid + 1;
}
}
} else if (format == 2) {
int a = glyphClass(b, pair + u16(b, pair + 8), previous);
int c = glyphClass(b, pair + u16(b, pair + 10), current);
if (u16(b, pair + 4) != 4 || (b[pair + 6] | b[pair + 7]) != 0) {
return 0;
}
int ac = u16(b, pair + 12);
int cc = u16(b, pair + 14);
if (a >= 0 && Integer.compareUnsigned(a, ac) < 0 && c >= 0 && Integer.compareUnsigned(c, cc) < 0) {
return s16(b, pair + 16 + a * cc * 2 + c * 2);
}
}
}
}
}
}
return 0;
} else {
return 0;
}
}
}
public static strictfp int[] box(FontTables.Font f, int glyph) {
if (f.cff != null) {
FontCff.Context context = new FontCff.Context(true);
boolean success = FontCff.run(f.cff, glyph, context);
return success ? new int[]{context.minX, context.minY, context.maxX, context.maxY} : new int[4];
} else {
int p = offset(f, glyph);
return p < 0 ? null : new int[]{s16(f.data, p + 2), s16(f.data, p + 4), s16(f.data, p + 6), s16(f.data, p + 8)};
}
}
private static strictfp void vertex(List<FontRasterization.Vertex> out, int type, int x, int y, int cx, int cy) {
out.add(new FontRasterization.Vertex(type, x, y, cx, cy, 0, 0));
}
private static strictfp void close(List<FontRasterization.Vertex> out, boolean off, boolean firstOff, int x, int y, int firstCX, int firstCY, int cx, int cy) {
if (firstOff) {
if (off) {
vertex(out, 3, cx + firstCX >> 1, cy + firstCY >> 1, cx, cy);
}
vertex(out, 3, x, y, firstCX, firstCY);
} else if (off) {
vertex(out, 3, x, y, cx, cy);
} else {
vertex(out, 2, x, y, 0, 0);
}
}
private static strictfp short transformed(float value, boolean saturate) {
int i = NumericMath.truncateInt(value);
return (short)(saturate ? (i > 32767 ? 32767 : (i < -32768 ? -32768 : i)) : i);
}
public static strictfp List<FontRasterization.Vertex> shape(FontTables.Font f, int glyph) {
if (f.cff != null) {
FontCff.Context count = new FontCff.Context(true);
if (FontCff.run(f.cff, glyph, count) && f.scratch.reserve(count.count * 14 + 15 & -16)) {
FontCff.Context draw = new FontCff.Context(false);
return FontCff.run(f.cff, glyph, draw) ? draw.vertices : Collections.emptyList();
} else {
return Collections.emptyList();
}
} else {
int at = offset(f, glyph);
if (at < 0) {
return Collections.emptyList();
} else {
byte[] b = f.data;
int contours = s16(b, at);
List<FontRasterization.Vertex> output = new ArrayList<>();
if (contours > 0) {
int ends = at + 10;
int last = u16(b, ends + contours * 2 - 2);
int n = last + 1;
if (!f.scratch.reserve((last + 2 * contours) * 14 + 29 & 2147483632)) {
return output;
} else {
int p = at + 12 + contours * 2 + u16(b, at + 10 + contours * 2);
int[] flags = new int[n];
int[] xs = new int[n];
int[] ys = new int[n];
int flag = 0;
int repeats = 0;
for (int i = 0; i < n; i++) {
if (repeats == 0) {
flag = b[p++] & 255;
if ((flag & 8) != 0) {
repeats = b[p++] & 255;
}
} else {
repeats--;
}
flags[i] = flag;
}
int coordinate = 0;
for (int i = 0; i < n; i++) {
flag = flags[i];
if ((flag & 2) != 0) {
int delta = b[p++] & 255;
coordinate += (flag & 16) != 0 ? delta : -delta;
} else if ((flag & 16) == 0) {
coordinate += s16(b, p);
p += 2;
}
xs[i] = (short)coordinate;
}
coordinate = 0;
for (int i = 0; i < n; i++) {
flag = flags[i];
if ((flag & 4) != 0) {
int delta = b[p++] & 255;
coordinate += (flag & 32) != 0 ? delta : -delta;
} else if ((flag & 32) == 0) {
coordinate += s16(b, p);
p += 2;
}
ys[i] = (short)coordinate;
}
int nextMove = 0;
int contour = 0;
int x = 0;
int y = 0;
int cx = 0;
int cy = 0;
int firstCX = 0;
int firstCY = 0;
boolean off = false;
boolean firstOff = false;
for (int i = 0; i < n; i++) {
int px = xs[i];
int py = ys[i];
if (nextMove == i) {
if (i != 0) {
close(output, off, firstOff, x, y, firstCX, firstCY, cx, cy);
}
firstOff = (flags[i] & 1) == 0;
if (firstOff) {
firstCX = px;
firstCY = py;
if ((flags[i + 1] & 1) == 0) {
x = px + xs[i + 1] >> 1;
y = py + ys[i + 1] >> 1;
} else {
x = xs[i + 1];
y = ys[i + 1];
i++;
}
} else {
x = px;
y = py;
}
vertex(output, 1, x, y, 0, 0);
off = false;
nextMove = 1 + u16(b, ends + contour++ * 2);
} else if ((flags[i] & 1) == 0) {
if (off) {
vertex(output, 3, cx + px >> 1, cy + py >> 1, cx, cy);
}
cx = px;
cy = py;
off = true;
} else {
vertex(output, off ? 3 : 2, px, py, off ? cx : 0, off ? cy : 0);
off = false;
}
}
close(output, off, firstOff, x, y, firstCX, firstCY, cx, cy);
return output;
}
} else if (contours == 0) {
return output;
} else {
int p = at + 10;
float unit = RenderConstants.f(2837572);
float fraction = RenderConstants.f(2837568);
int flags;
do {
flags = u16(b, p);
int childGlyph = u16(b, p + 2);
p += 4;
float a = unit;
float c = 0.0F;
float d = unit;
float bb = 0.0F;
float tx = 0.0F;
float ty = 0.0F;
if ((flags & 2) != 0) {
if ((flags & 1) != 0) {
tx = (float)s16(b, p);
ty = (float)s16(b, p + 2);
p += 4;
} else {
tx = (float)b[p];
ty = (float)b[p + 1];
p += 2;
}
}
if ((flags & 8) != 0) {
a = d = (float)s16(b, p) * fraction;
p += 2;
} else if ((flags & 64) != 0) {
a = (float)s16(b, p) * fraction;
d = (float)s16(b, p + 2) * fraction;
p += 4;
} else if ((flags & 128) != 0) {
a = (float)s16(b, p) * fraction;
bb = (float)s16(b, p + 2) * fraction;
c = (float)s16(b, p + 4) * fraction;
d = (float)s16(b, p + 6) * fraction;
p += 8;
}
List<FontRasterization.Vertex> child = shape(f, childGlyph);
if (!child.isEmpty()) {
float sx = (float)StrictMath.sqrt((double)(a * a + bb * bb));
float sy = (float)StrictMath.sqrt((double)(c * c + d * d));
int n = child.size();
int vectorCount = n >= 9 ? n - (n % 8 != 0 ? n % 8 : 8) : 0;
for (int ix = 0; ix < n; ix++) {
FontRasterization.Vertex v = child.get(ix);
boolean packed = ix >= vectorCount || ix % 8 >= 1 && ix % 8 <= 3;
boolean packedCX = packed && (ix >= vectorCount || ix % 8 != 2);
float x = (float)v.x;
float y = (float)v.y;
float cx = (float)v.cx;
float cy = (float)v.cy;
v.x = transformed(sx * (c * y + a * x + tx), packed);
v.y = transformed(sy * (bb * x + d * y + ty), packed);
v.cx = transformed(sx * (c * cy + a * cx + tx), packedCX);
v.cy = transformed(sy * (bb * cx + d * cy + ty), packed);
}
if (!f.scratch.reserve((output.size() + n) * 14)) {
return Collections.emptyList();
}
output.addAll(child);
}
} while ((flags & 32) != 0);
return output;
}
}
}
}
private FontTables() {
}
public static final strictfp class Font {
public final byte[] data;
public int glyphCount;
public int loca;
public int head;
public int glyf;
public int hhea;
public int hmtx;
public int kern;
public int gpos;
public int cmap;
public int locaFormat;
public FontCff.Info cff;
public FontRasterization.Scratch scratch;
public Font(byte[] data, FontRasterization.Scratch scratch) {
this.data = Objects.requireNonNull(data);
this.scratch = Objects.requireNonNull(scratch);
}
}
}
@@ -0,0 +1,183 @@
package dev.slinky.client.render.font;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.imageio.ImageIO;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
public final class GameFontProvider implements FontGlyphCache.Source {
private final Minecraft game;
private final boolean bold;
private final boolean unicode;
private final IResourceManager resources;
private final BufferedImage ascii;
private final byte[] sizes = new byte[65536];
private final Map<Integer, BufferedImage> pages = new HashMap<>();
private final Map<Integer, List<FontRasterization.Vertex>> outlines = new HashMap<>();
private final FontRasterization.Scratch scratch = new FontRasterization.Scratch(() -> {
throw new IllegalStateException("Game glyph scratch budget exceeded");
});
public GameFontProvider(Minecraft game) throws IOException {
this(game, false);
}
public GameFontProvider(Minecraft game, boolean bold) throws IOException {
this.game = game;
this.bold = bold;
this.unicode = game.fontRendererObj.getUnicodeFlag();
this.resources = game.getResourceManager();
this.ascii = this.image("textures/font/ascii.png");
try (InputStream input = this.resources.getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream()) {
int offset = 0;
while (offset < this.sizes.length) {
int read = input.read(this.sizes, offset, this.sizes.length - offset);
if (read < 0) {
throw new IOException("Incomplete game glyph-size resource");
}
offset += read;
}
}
}
private BufferedImage image(String path) throws IOException {
BufferedImage var5;
try (InputStream input = this.resources.getResource(new ResourceLocation(path)).getInputStream()) {
BufferedImage result = ImageIO.read(input);
if (result == null) {
throw new IOException("Unreadable game font bitmap " + path);
}
var5 = result;
}
return var5;
}
@Override
public int unitsPerEm() {
return 18;
}
boolean isCurrent() {
return this.unicode == this.game.fontRendererObj.getUnicodeFlag();
}
@Override
public int map(int codepoint) {
return codepoint >= 0 && codepoint <= 65535 ? codepoint : 0;
}
@Override
public int advance(int glyph) {
return (Math.max(0, this.game.fontRendererObj.getCharWidth((char)glyph)) + (this.bold && glyph != 167 ? 1 : 0)) * 2;
}
@Override
public int kern(int previous, int current) {
return 0;
}
@Override
public FontRasterization.Scratch scratch() {
return this.scratch;
}
@Override
public int[] box(int glyph) {
List<FontRasterization.Vertex> vertices = this.shape(glyph);
if (vertices.isEmpty()) {
return null;
} else {
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
for (FontRasterization.Vertex vertex : vertices) {
minX = Math.min(minX, vertex.x);
minY = Math.min(minY, vertex.y);
maxX = Math.max(maxX, vertex.x);
maxY = Math.max(maxY, vertex.y);
}
return new int[]{minX, minY, maxX, maxY};
}
}
@Override
public List<FontRasterization.Vertex> shape(int glyph) {
List<FontRasterization.Vertex> existing = this.outlines.get(glyph);
if (existing != null) {
return existing;
} else {
List<FontRasterization.Vertex> result = new ArrayList<>();
if (glyph != 32 && glyph != 167) {
boolean basic = glyph >= 33 && glyph <= 126 && !this.unicode;
BufferedImage bitmap;
try {
if (basic) {
bitmap = this.ascii;
} else {
int page = glyph >>> 8;
bitmap = this.pages.get(page);
if (bitmap == null && this.sizes[glyph] != 0) {
bitmap = this.image(String.format(Locale.ROOT, "textures/font/unicode_page_%02x.png", page));
this.pages.put(page, bitmap);
}
if (bitmap == null) {
this.outlines.put(glyph, result);
return result;
}
}
} catch (IOException var20) {
throw new IllegalStateException("Game font resource failed", var20);
}
int slot = glyph & 0xFF;
int cellWidth = bitmap.getWidth() / 16;
int cellHeight = bitmap.getHeight() / 16;
int left = (slot & 15) * cellWidth;
int top = (slot >>> 4) * cellHeight;
int start = basic ? 0 : (this.sizes[glyph] & 255) >>> 4;
int units = basic ? 2 : 1;
int end = basic ? cellWidth : Math.min(cellWidth, (this.sizes[glyph] & 15) + 1);
for (int y = 0; y < cellHeight; y++) {
for (int x = start; x < end; x++) {
if (bitmap.getRGB(left + x, top + y) >>> 24 != 0) {
int x0 = (x - start) * units;
int x1 = x0 + units + (this.bold ? 2 : 0);
int y1 = 16 - y * units;
int y0 = y1 - units;
result.add(new FontRasterization.Vertex(1, x0, y0, 0, 0, 0, 0));
result.add(new FontRasterization.Vertex(2, x1, y0, 0, 0, 0, 0));
result.add(new FontRasterization.Vertex(2, x1, y1, 0, 0, 0, 0));
result.add(new FontRasterization.Vertex(2, x0, y1, 0, 0, 0, 0));
result.add(new FontRasterization.Vertex(2, x0, y0, 0, 0, 0, 0));
}
}
}
this.outlines.put(glyph, result);
return result;
} else {
this.outlines.put(glyph, result);
return result;
}
}
}
}
@@ -0,0 +1,75 @@
package dev.slinky.client.render.hook;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import net.minecraft.client.renderer.GlStateManager;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
final class GlCacheResync {
private static final ByteBuffer MASK = BufferUtils.createByteBuffer(16);
private static final FloatBuffer COLOR = BufferUtils.createFloatBuffer(16);
private static final int BLEND_DST_RGB = 32968;
private static final int BLEND_SRC_RGB = 32969;
private static final int BLEND_DST_ALPHA = 32970;
private static final int BLEND_SRC_ALPHA = 32971;
private static int spareTexture;
private GlCacheResync() {
}
static void run() {
flag(3042, GlStateManager::enableBlend, GlStateManager::disableBlend);
flag(3553, GlStateManager::enableTexture2D, GlStateManager::disableTexture2D);
flag(3008, GlStateManager::enableAlpha, GlStateManager::disableAlpha);
flag(2929, GlStateManager::enableDepth, GlStateManager::disableDepth);
flag(2884, GlStateManager::enableCull, GlStateManager::disableCull);
flag(2896, GlStateManager::enableLighting, GlStateManager::disableLighting);
int src = GL11.glGetInteger(32969);
int dst = GL11.glGetInteger(32968);
int srcAlpha = GL11.glGetInteger(32971);
int dstAlpha = GL11.glGetInteger(32970);
GlStateManager.tryBlendFuncSeparate(other(src), other(dst), other(srcAlpha), other(dstAlpha));
GlStateManager.tryBlendFuncSeparate(src, dst, srcAlpha, dstAlpha);
((Buffer)MASK).clear();
GL11.glGetBoolean(3107, MASK);
boolean r = MASK.get(0) != 0;
boolean g = MASK.get(1) != 0;
boolean b = MASK.get(2) != 0;
boolean a = MASK.get(3) != 0;
GlStateManager.colorMask(!r, !g, !b, !a);
GlStateManager.colorMask(r, g, b, a);
boolean depthMask = GL11.glGetBoolean(2930);
GlStateManager.depthMask(!depthMask);
GlStateManager.depthMask(depthMask);
int shade = GL11.glGetInteger(2900);
GlStateManager.shadeModel(shade == 7424 ? 7425 : 7424);
GlStateManager.shadeModel(shade);
((Buffer)COLOR).clear();
GL11.glGetFloat(2816, COLOR);
GlStateManager.resetColor();
GlStateManager.color(COLOR.get(0), COLOR.get(1), COLOR.get(2), COLOR.get(3));
int bound = GL11.glGetInteger(32873);
if (bound == 0 && spareTexture == 0) {
spareTexture = GL11.glGenTextures();
}
GlStateManager.bindTexture(bound == 0 ? spareTexture : 0);
GlStateManager.bindTexture(bound);
}
private static void flag(int capability, Runnable enable, Runnable disable) {
if (GL11.glIsEnabled(capability)) {
disable.run();
enable.run();
} else {
enable.run();
disable.run();
}
}
private static int other(int factor) {
return factor == 0 ? 1 : 0;
}
}
@@ -0,0 +1,128 @@
package dev.slinky.client.render.hook;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
import net.minecraft.client.renderer.GlStateManager;
public final class GlStateCacheSnapshot {
private final List<GlStateCacheSnapshot.Value> values = new ArrayList<>();
private static List<Object[]> layout;
private GlStateCacheSnapshot() {
}
public static GlStateCacheSnapshot capture() {
List<Object[]> fields = layout;
if (fields == null) {
layout = fields = discover();
}
GlStateCacheSnapshot snapshot = new GlStateCacheSnapshot();
try {
for (Object[] entry : fields) {
snapshot.values.add(new GlStateCacheSnapshot.Value((Field)entry[0], entry[1]));
}
return snapshot;
} catch (ReflectiveOperationException var4) {
throw new IllegalStateException("Cannot snapshot Minecraft's GL state cache", var4);
}
}
private static List<Object[]> discover() {
GlStateCacheSnapshot found = new GlStateCacheSnapshot();
Set<Object> visited = Collections.newSetFromMap(new IdentityHashMap<>());
try {
for (Field field : GlStateManager.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
if (field.getType().isPrimitive()) {
if (!Modifier.isFinal(field.getModifiers())) {
found.values.add(new GlStateCacheSnapshot.Value(field, null));
}
} else {
found.captureObject(field.get(null), visited);
}
}
}
} catch (ReflectiveOperationException var6) {
throw new IllegalStateException("Cannot snapshot Minecraft's GL state cache", var6);
}
if (found.values.isEmpty()) {
throw new IllegalStateException("Minecraft's GL cache layout is unsupported");
} else {
List<Object[]> result = new ArrayList<>(found.values.size());
for (GlStateCacheSnapshot.Value value : found.values) {
result.add(new Object[]{value.field, value.owner});
}
return result;
}
}
private void captureObject(Object object, Set<Object> visited) throws IllegalAccessException {
if (object != null && visited.add(object)) {
Class<?> type = object.getClass();
if (!type.isArray()) {
if (type.getName().startsWith(GlStateManager.class.getName() + "$")) {
for (Field field : type.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
if (field.getType().isPrimitive()) {
if (!Modifier.isFinal(field.getModifiers())) {
this.values.add(new GlStateCacheSnapshot.Value(field, object));
}
} else {
this.captureObject(field.get(object), visited);
}
}
}
}
} else if (!type.getComponentType().isPrimitive()) {
for (int i = 0; i < Array.getLength(object); i++) {
this.captureObject(Array.get(object, i), visited);
}
}
}
}
public int valueCount() {
return this.values.size();
}
public void restore() {
try {
for (GlStateCacheSnapshot.Value value : this.values) {
value.restore();
}
} catch (IllegalAccessException var3) {
throw new IllegalStateException("Cannot restore Minecraft's GL state cache", var3);
}
}
private static final class Value {
final Field field;
final Object owner;
final Object value;
Value(Field field, Object owner) throws IllegalAccessException {
this.field = field;
this.owner = owner;
this.value = field.get(owner);
}
void restore() throws IllegalAccessException {
this.field.set(this.owner, this.value);
}
}
}
@@ -0,0 +1,120 @@
package dev.slinky.client.render.hook;
import org.lwjgl.opengl.GL11;
public final class GlStateScope implements AutoCloseable {
private static final GlStateScope.Attributes OPENGL = new GlStateScope.Attributes() {
@Override
public void pushServer() {
GL11.glPushAttrib(1048575);
}
@Override
public void pushClient() {
GL11.glPushClientAttrib(3);
}
@Override
public void popClient() {
GL11.glPopClientAttrib();
}
@Override
public void popServer() {
GL11.glPopAttrib();
}
};
private static int depth;
private final GlStateScope.Attributes attributes;
private final Runnable restoreCache;
private boolean server;
private boolean client;
private boolean closed;
private boolean counted;
GlStateScope(GlStateScope.Attributes attributes, Runnable restoreCache) {
this.attributes = attributes;
this.restoreCache = restoreCache;
try {
attributes.pushServer();
this.server = true;
attributes.pushClient();
this.client = true;
} catch (Error | RuntimeException var6) {
try {
this.close();
} catch (Error | RuntimeException var5) {
var6.addSuppressed(var5);
}
throw var6;
}
}
public static GlStateScope open() {
GlStateCacheSnapshot cache = GlStateCacheSnapshot.capture();
GlStateScope scope = new GlStateScope(OPENGL, cache::restore);
depth++;
scope.counted = true;
return scope;
}
public static boolean active() {
return depth > 0;
}
@Override
public void close() {
if (!this.closed) {
this.closed = true;
if (this.counted) {
depth--;
}
Throwable failure = null;
if (this.client) {
failure = restore(this.attributes::popClient, failure);
}
if (this.server) {
failure = restore(this.attributes::popServer, failure);
}
failure = restore(this.restoreCache, failure);
if (this.counted && depth == 0 && failure == null) {
failure = restore(GlCacheResync::run, failure);
}
if (failure instanceof RuntimeException) {
throw (RuntimeException)failure;
} else if (failure instanceof Error) {
throw (Error)failure;
}
}
}
private static Throwable restore(Runnable action, Throwable failure) {
try {
action.run();
} catch (Error | RuntimeException var3) {
if (failure == null) {
return var3;
}
failure.addSuppressed(var3);
}
return failure;
}
interface Attributes {
void pushServer();
void pushClient();
void popClient();
void popServer();
}
}
@@ -0,0 +1,300 @@
package dev.slinky.client.render.hook;
import dev.slinky.client.MinecraftFields;
import dev.slinky.client.event.SemanticEvent;
import dev.slinky.client.render.RenderEventPayload;
import dev.slinky.module.ModuleManager;
import java.nio.ByteBuffer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.texture.TextureUtil;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
final class MinecraftUiSurface implements UiFrameDriver.Surface {
private static final int GL_FRAMEBUFFER_BINDING = 36006;
private final ModuleManager modules;
private int framebuffer = -1;
private int texture = -1;
private int width;
private int height;
private int previousMatrix;
private int previousFramebuffer;
private boolean drawing;
private boolean direct;
private double previousDirectDraw = -1.0;
private double canvasScale;
private GlStateScope state;
private boolean projectionPushed;
private boolean modelPushed;
MinecraftUiSurface(ModuleManager modules) {
this.modules = modules;
}
@Override
public boolean resize(int width, int height) {
boolean sameSize = this.width == width && this.height == height;
if (!sameSize || this.framebuffer == -1 && !this.direct) {
this.width = width;
this.height = height;
this.deleteBuffer();
this.direct = !OpenGlHelper.framebufferSupported || !this.createBuffer();
this.previousDirectDraw = -1.0;
return true;
} else {
return false;
}
}
private boolean createBuffer() {
int previous = GL11.glGetInteger(36006);
boolean complete = false;
try (GlStateScope allocation = GlStateScope.open()) {
this.texture = TextureUtil.glGenTextures();
GlStateManager.bindTexture(this.texture);
GL11.glTexParameteri(3553, 10241, 9728);
GL11.glTexParameteri(3553, 10240, 9728);
GL11.glTexParameteri(3553, 10242, 10496);
GL11.glTexParameteri(3553, 10243, 10496);
GL11.glTexImage2D(3553, 0, 32856, this.width, this.height, 0, 6408, 5121, (ByteBuffer)null);
this.framebuffer = OpenGlHelper.glGenFramebuffers();
OpenGlHelper.glBindFramebuffer(OpenGlHelper.GL_FRAMEBUFFER, this.framebuffer);
OpenGlHelper.glFramebufferTexture2D(OpenGlHelper.GL_FRAMEBUFFER, OpenGlHelper.GL_COLOR_ATTACHMENT0, 3553, this.texture, 0);
complete = OpenGlHelper.glCheckFramebufferStatus(OpenGlHelper.GL_FRAMEBUFFER) == OpenGlHelper.GL_FRAMEBUFFER_COMPLETE;
if (complete) {
this.clearBuffer();
}
} finally {
OpenGlHelper.glBindFramebuffer(OpenGlHelper.GL_FRAMEBUFFER, previous);
if (!complete) {
this.deleteBuffer();
}
}
return complete;
}
private void clearBuffer() {
GL11.glViewport(0, 0, this.width, this.height);
GL11.glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
GL11.glClear(16384);
}
@Override
public void scale(double value) {
this.canvasScale = value;
}
@Override
public int update(float elapsed) {
SemanticEvent event = new SemanticEvent(9);
event.elapsedSeconds = (double)elapsed;
RenderEventPayload payload = RenderEventPayload.attach(event);
int var4;
try {
this.modules.dispatch(event);
var4 = payload.redrawFlags;
} finally {
RenderEventPayload.detach(event);
}
return var4;
}
@Override
public void begin(double now) {
if (!this.direct) {
this.open(true);
}
}
private void open(boolean offscreen) {
this.previousMatrix = GL11.glGetInteger(2976);
this.previousFramebuffer = GL11.glGetInteger(36006);
this.state = GlStateScope.open();
this.drawing = true;
try {
GL11.glMatrixMode(5889);
GL11.glPushMatrix();
this.projectionPushed = true;
GL11.glLoadIdentity();
GL11.glOrtho(0.0, (double)this.width, (double)this.height, 0.0, -3000.0, 3000.0);
GL11.glMatrixMode(5888);
GL11.glPushMatrix();
this.modelPushed = true;
GL11.glLoadIdentity();
if (offscreen) {
OpenGlHelper.glBindFramebuffer(OpenGlHelper.GL_FRAMEBUFFER, this.framebuffer);
this.clearBuffer();
}
GlStateManager.disableDepth();
} catch (Error | RuntimeException var5) {
try {
this.end();
} catch (Error | RuntimeException var4) {
var5.addSuppressed(var4);
}
throw var5;
}
}
@Override
public void draw(float elapsed) {
if (this.drawing) {
Minecraft game = this.modules.context().minecraft();
SemanticEvent event = new SemanticEvent(10);
event.elapsedSeconds = (double)elapsed;
event.renderWidth = (float)this.width;
event.renderHeight = (float)this.height;
event.timerRenderPartialTicks = MinecraftFields.timer(game).renderPartialTicks;
RenderEventPayload payload = RenderEventPayload.attach(event);
if (game.currentScreen instanceof RenderHooks.GuiFrame) {
RenderHooks.GuiFrame screen = (RenderHooks.GuiFrame)game.currentScreen;
payload.guiDraw = () -> screen.drawRecoveredFrame(event.timerRenderPartialTicks);
}
try {
this.modules.dispatch(event);
} finally {
RenderEventPayload.detach(event);
}
}
}
@Override
public void end() {
if (this.drawing) {
this.drawing = false;
try {
OpenGlHelper.glBindFramebuffer(OpenGlHelper.GL_FRAMEBUFFER, this.previousFramebuffer);
if (this.modelPushed) {
GL11.glMatrixMode(5888);
GL11.glPopMatrix();
}
if (this.projectionPushed) {
GL11.glMatrixMode(5889);
GL11.glPopMatrix();
}
GL11.glMatrixMode(this.previousMatrix);
} finally {
this.modelPushed = this.projectionPushed = false;
try {
this.state.close();
} finally {
this.state = null;
}
}
}
}
@Override
public void present(double now) {
if (this.direct) {
this.presentDirect(now);
} else {
int mode = GL11.glGetInteger(2976);
try (GlStateScope presentation = GlStateScope.open()) {
GL11.glMatrixMode(5889);
GL11.glPushMatrix();
GL11.glLoadIdentity();
GL11.glOrtho(0.0, (double)this.width, (double)this.height, 0.0, 1000.0, 3000.0);
GL11.glMatrixMode(5888);
GL11.glPushMatrix();
GL11.glLoadIdentity();
GL11.glTranslatef(0.0F, 0.0F, -2000.0F);
try {
GlStateManager.colorMask(true, true, true, false);
GlStateManager.disableDepth();
GlStateManager.depthMask(false);
GlStateManager.disableLighting();
GlStateManager.disableAlpha();
GlStateManager.enableTexture2D();
GlStateManager.enableBlend();
GlStateManager.tryBlendFuncSeparate(1, 771, 1, 771);
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
GlStateManager.bindTexture(this.texture);
GL11.glViewport(0, 0, this.width, this.height);
GL11.glBegin(7);
GL11.glTexCoord2f(0.0F, 0.0F);
GL11.glVertex3f(0.0F, (float)this.height, 0.0F);
GL11.glTexCoord2f(1.0F, 0.0F);
GL11.glVertex3f((float)this.width, (float)this.height, 0.0F);
GL11.glTexCoord2f(1.0F, 1.0F);
GL11.glVertex3f((float)this.width, 0.0F, 0.0F);
GL11.glTexCoord2f(0.0F, 1.0F);
GL11.glVertex3f(0.0F, 0.0F, 0.0F);
GL11.glEnd();
} finally {
GL11.glMatrixMode(5888);
GL11.glPopMatrix();
GL11.glMatrixMode(5889);
GL11.glPopMatrix();
GL11.glMatrixMode(mode);
}
}
}
}
private void presentDirect(double now) {
float elapsed = this.previousDirectDraw < 0.0 ? 0.0F : (float)(now - this.previousDirectDraw);
this.previousDirectDraw = now;
this.open(false);
try {
this.draw(elapsed);
} finally {
this.end();
}
}
private void deleteBuffer() {
try {
if (this.framebuffer != -1) {
OpenGlHelper.glDeleteFramebuffers(this.framebuffer);
}
} finally {
this.framebuffer = -1;
try {
if (this.texture != -1) {
TextureUtil.deleteTexture(this.texture);
}
} finally {
this.texture = -1;
}
}
}
public double canvasScale() {
return this.canvasScale;
}
void close() {
this.end();
if (this.framebuffer != -1 || this.texture != -1) {
try {
if (Display.isCreated() && Display.isCurrent()) {
this.deleteBuffer();
}
} catch (LWJGLException var5) {
throw new IllegalStateException("Could not release the canvas framebuffer", var5);
} finally {
this.framebuffer = this.texture = -1;
}
}
}
}
@@ -0,0 +1,214 @@
package dev.slinky.client.render.hook;
import dev.slinky.client.ClientContext;
import dev.slinky.client.MinecraftFields;
import dev.slinky.client.event.SemanticEvent;
import dev.slinky.client.render.RenderAccess;
import dev.slinky.client.render.RenderEventPayload;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import dev.slinky.module.render.ChamsModule;
import dev.slinky.module.render.GuiModule;
import dev.slinky.module.render.NameTagsModule;
import java.util.ArrayDeque;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.BlockPos;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
public final strictfp class RenderHooks {
private static ModuleManager modules;
private static MinecraftUiSurface uiSurface;
private static UiFrameDriver uiFrames;
private static final ArrayDeque<Boolean> livingRenders = new ArrayDeque<>();
private RenderHooks() {
}
public static strictfp void install(ModuleManager manager) {
if (manager != null && modules != manager) {
if (uiSurface != null) {
clear();
}
livingRenders.clear();
}
modules = manager;
uiFrames = new UiFrameDriver(-1.0, 0, false, true);
}
public static strictfp void beforeDisplay() {
if (modules != null) {
initializeGuiBinding(modules);
if (hasListeners(10)) {
Minecraft game = modules.context().minecraft();
if (uiSurface == null) {
uiSurface = new MinecraftUiSurface(modules);
}
Module gui = modules.find("gui");
double scale = gui != null && gui.findSetting("scale").isConfigured() ? (double)((GuiModule)gui).scale() : 1.0;
uiFrames.frame(uiSurface, game.displayWidth, game.displayHeight, scale, RenderAccess.now());
}
}
}
public static strictfp void initializeGuiBinding(ModuleManager manager) {
Module gui = manager.find("GUI");
if (gui != null && gui.getBinding() == 0) {
manager.setBinding(gui, 99);
}
}
public static strictfp boolean hasListeners(int type) {
if (modules == null) {
return false;
} else {
for (Module module : modules.all()) {
if (module.isEnabled() && module.listens(type)) {
return true;
}
}
return false;
}
}
public static strictfp double canvasScale() {
return uiSurface == null ? 1.0 : uiSurface.canvasScale();
}
public static strictfp void clearWorld(int mask) {
if (mask == 256 && modules != null && modules.context().ready() && (hasListeners(6) || hasListeners(7))) {
ClientContext context = modules.context();
Minecraft game = context.minecraft();
Entity view = game.getRenderViewEntity();
if (view != null) {
float partial = MinecraftFields.timer(game).renderPartialTicks;
SemanticEvent world = worldEvent(6, view, partial, game.displayWidth, game.displayHeight);
GlStateScope state = GlStateScope.open();
int previousMatrix = GL11.glGetInteger(2976);
GL11.glMatrixMode(5889);
GL11.glPushMatrix();
GL11.glMatrixMode(5888);
GL11.glPushMatrix();
try {
GlStateManager.disableDepth();
GlStateManager.disableTexture2D();
GlStateManager.disableLighting();
GlStateManager.enableBlend();
GlStateManager.blendFunc(770, 771);
GL11.glEnable(2848);
if (hasListeners(6)) {
modules.dispatch(world);
}
if (hasListeners(7)) {
RenderAccess.captureWorldProjection();
GL11.glMatrixMode(5889);
GL11.glLoadIdentity();
GL11.glOrtho(0.0, (double)game.displayWidth, (double)game.displayHeight, 0.0, -3000.0, 3000.0);
GL11.glMatrixMode(5888);
GL11.glLoadIdentity();
modules.dispatch(worldEvent(7, view, partial, game.displayWidth, game.displayHeight));
}
} finally {
GL11.glMatrixMode(5888);
GL11.glPopMatrix();
GL11.glMatrixMode(5889);
GL11.glPopMatrix();
GL11.glMatrixMode(previousMatrix);
state.close();
}
}
}
GlStateManager.clear(mask);
}
public static strictfp SemanticEvent worldEvent(int type, Entity view, float partial, int width, int height) {
SemanticEvent event = new SemanticEvent(type);
double fraction = (double)partial;
event.cameraX = fraction * (view.posX - view.prevPosX) + view.prevPosX;
event.cameraY = fraction * (view.posY - view.prevPosY) + view.prevPosY;
event.cameraZ = (view.posZ - view.prevPosZ) * fraction + view.prevPosZ;
event.timerRenderPartialTicks = partial;
event.renderWidth = (float)width;
event.renderHeight = (float)height;
return event;
}
public static strictfp boolean suppressVanillaName(Entity entity) {
Module module = modules == null ? null : modules.find("name_tags");
return module instanceof NameTagsModule && ((NameTagsModule)module).suppressVanilla(entity);
}
public static strictfp void beginLiving(EntityLivingBase entity) {
Module module = modules == null ? null : modules.find("chams");
livingRenders.push(module instanceof ChamsModule && ((ChamsModule)module).begin(entity));
}
public static strictfp void endLiving() {
Boolean selected = livingRenders.poll();
Module module = modules == null ? null : modules.find("chams");
if (selected != null && module instanceof ChamsModule) {
((ChamsModule)module).end(selected);
}
}
public static strictfp void beforeSetBlock(World world, BlockPos position, IBlockState state) {
blockEvent(world, 2, position.getX(), position.getY(), position.getZ(), Block.getIdFromBlock(state.getBlock()));
}
public static strictfp void afterUnloadChunk(int x, int z) {
blockEvent(localWorld(), 0, x, 0, z, 0);
}
public static strictfp void afterInvalidate(World world, int firstX, int firstZ) {
blockEvent(world, 1, firstX >> 4, 0, firstZ >> 4, 0);
}
private static strictfp World localWorld() {
return modules == null ? null : modules.context().world();
}
private static strictfp void blockEvent(World world, int kind, int x, int y, int z, int identifier) {
if (modules != null) {
SemanticEvent event = new SemanticEvent(20);
RenderEventPayload payload = RenderEventPayload.attach(event);
payload.regionKind = kind;
payload.world = world;
payload.x = x;
payload.y = y;
payload.z = z;
payload.blockId = identifier;
try {
modules.dispatch(event);
} finally {
RenderEventPayload.detach(event);
}
}
}
public static strictfp void clear() {
try {
if (uiSurface != null) {
uiSurface.close();
}
} finally {
uiSurface = null;
uiFrames = new UiFrameDriver(-1.0, 0, false, true);
RenderAccess.clearProjection();
}
}
public interface GuiFrame {
void drawRecoveredFrame(float var1);
}
}
@@ -0,0 +1,88 @@
package dev.slinky.client.render.hook;
public final strictfp class UiFrameDriver {
public static final double INTERVAL = Double.longBitsToDouble(4575957461383581969L);
private double previous;
private int resizeFrames;
private boolean visible;
private boolean previouslyChanging;
public UiFrameDriver(double previous, int resizeFrames, boolean visible, boolean previouslyChanging) {
this.previous = previous;
this.resizeFrames = resizeFrames;
this.visible = visible;
this.previouslyChanging = previouslyChanging;
}
public strictfp void frame(UiFrameDriver.Surface surface, int width, int height, double scale, double now) {
if (this.previous < 0.0) {
this.previous = now;
}
if (surface.resize(width, height)) {
this.resizeFrames = 10;
} else if (this.resizeFrames > 0) {
this.resizeFrames--;
}
surface.scale(scale);
double elapsed = now - this.previous;
if (elapsed > INTERVAL) {
float frameElapsed = (float)elapsed;
int flags = surface.update(frameElapsed);
this.visible = (flags & 1) != 0;
boolean changing = (flags & 2) != 0;
if (this.visible && (changing || this.previouslyChanging || this.resizeFrames > 0)) {
surface.begin(now);
try {
surface.draw(frameElapsed);
} finally {
surface.end();
}
this.previouslyChanging = changing;
}
this.previous = now;
}
if (this.visible) {
surface.present(now);
} else {
this.previouslyChanging = true;
}
}
public strictfp double previousSeconds() {
return this.previous;
}
public strictfp int resizeFrames() {
return this.resizeFrames;
}
public strictfp boolean visible() {
return this.visible;
}
public strictfp boolean previouslyChanging() {
return this.previouslyChanging;
}
public interface Surface {
boolean resize(int var1, int var2);
void scale(double var1);
int update(float var1);
void begin(double var1);
void draw(float var1);
void end();
void present(double var1);
}
}
@@ -0,0 +1,59 @@
package dev.slinky.client.render.text;
import java.nio.charset.StandardCharsets;
public final class Utf8Buffer {
private final byte[] bytes;
private int count;
public Utf8Buffer(int capacity) {
this.bytes = new byte[capacity];
}
public boolean append(String value) {
byte[] source = value.getBytes(StandardCharsets.UTF_8);
if (this.count < this.bytes.length && source.length < this.bytes.length - this.count) {
System.arraycopy(source, 0, this.bytes, this.count, source.length);
this.count += source.length;
return true;
} else {
return false;
}
}
public boolean formatResult(String value) {
byte[] source = value.getBytes(StandardCharsets.UTF_8);
if (this.count >= this.bytes.length) {
return false;
} else if (source.length >= this.bytes.length - this.count) {
this.count = this.bytes.length;
return false;
} else {
System.arraycopy(source, 0, this.bytes, this.count, source.length);
this.count += source.length;
return true;
}
}
public boolean appendByte(int value) {
if (this.count >= this.bytes.length) {
return false;
} else {
this.bytes[this.count++] = (byte)value;
return true;
}
}
public String finish() {
if (this.count >= this.bytes.length) {
return null;
} else {
this.bytes[this.count++] = 0;
return new String(this.bytes, 0, this.count - 1, StandardCharsets.UTF_8);
}
}
public int count() {
return this.count;
}
}
@@ -0,0 +1,148 @@
package dev.slinky.client.render.world;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.client.render.RenderConstants;
import dev.slinky.setting.ColorSetting;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.SortedMap;
public final strictfp class BlockMesh {
private BlockMesh() {
}
public static strictfp int compareRegion(int x, int z, int otherX, int otherZ) {
int difference = x - otherX;
return difference == 0 ? z - otherZ : difference;
}
public static strictfp List<BlockMesh.Position> scanByte(int section, byte[] ids, SortedMap<Integer, ColorSetting.Value> policy) {
List<BlockMesh.Position> result = new ArrayList<>();
for (Entry<Integer, ColorSetting.Value> entry : policy.entrySet()) {
for (int group = 0; group < 256; group++) {
for (int bit = 0; bit < 16; bit++) {
if (ids[group * 16 + bit] == (byte)entry.getKey().intValue()) {
result.add(new BlockMesh.Position(bit, (group >> 4) + section * 16, group & 15, entry.getKey(), entry.getValue()));
}
}
}
}
return result;
}
public static strictfp List<BlockMesh.Position> scanPacked(int section, char[] ids, SortedMap<Integer, ColorSetting.Value> policy) {
List<BlockMesh.Position> result = new ArrayList<>();
for (Entry<Integer, ColorSetting.Value> entry : policy.entrySet()) {
for (int group = 0; group < 512; group++) {
for (int bit = 0; bit < 8; bit++) {
if ((short)ids[group * 8 + bit] >> 4 == (short)entry.getKey().intValue()) {
result.add(new BlockMesh.Position(((group & 1) << 3) + bit, (group >> 5) + section * 16, group >> 1 & 15, entry.getKey(), entry.getValue()));
}
}
}
}
return result;
}
public static strictfp void rebuild(BlockMesh.Region region) {
float scale = RenderConstants.f(1393000);
region.vertices = new float[region.blocks.size() * 8 * 3];
region.colors = new int[region.blocks.size() * 8];
region.indices = new short[region.blocks.size() * 24];
int vertex = 0;
int index = 0;
for (BlockMesh.Position p : region.blocks) {
int first = vertex;
int red = NumericMath.truncateInt(p.color.red * scale) & 0xFF;
int green = NumericMath.truncateInt(p.color.green * scale) & 0xFF;
int blue = NumericMath.truncateInt(p.color.blue * scale) & 0xFF;
int color = red << 24 | green << 16 | blue << 8 | 0xFF;
for (int corner = 0; corner < 8; corner++) {
region.vertices[vertex * 3] = RenderConstants.f(1393072 + corner * 12) + (float)p.x;
region.vertices[vertex * 3 + 1] = RenderConstants.f(1393076 + corner * 12) + (float)p.y;
region.vertices[vertex * 3 + 2] = RenderConstants.f(1393080 + corner * 12) + (float)p.z;
region.colors[vertex++] = color;
}
for (int n = 0; n < 24; n++) {
region.indices[index++] = (short)(RenderConstants.u16(1393168 + n * 2) + (first & 65535));
}
}
}
public static strictfp long renderLimit(boolean limited, float value) {
if (!limited) {
return 4096L;
} else {
float amount = value * RenderConstants.f(1392984);
long first = NumericMath.truncateLong(amount);
long second = NumericMath.truncateLong(amount - RenderConstants.f(1392988));
return first | second & (first < 0L ? -1L : 0L);
}
}
public static strictfp void change(BlockMesh.Region region, int x, int y, int z, int id, ColorSetting.Value policy) {
for (int i = 0; i < region.blocks.size(); i++) {
BlockMesh.Position p = region.blocks.get(i);
if (p.x == (x & 0xFF) && p.y == (y & 0xFF) && p.z == (z & 0xFF)) {
if (policy == null) {
region.blocks.remove(i);
} else {
region.blocks.set(i, new BlockMesh.Position(x, y, z, id, policy));
}
rebuild(region);
return;
}
}
if (policy != null) {
region.blocks.add(new BlockMesh.Position(x, y, z, id, policy));
rebuild(region);
}
}
public static final strictfp class Position {
public final int x;
public final int y;
public final int z;
public final int id;
public ColorSetting.Value color;
public Position(int x, int y, int z, int id, ColorSetting.Value color) {
this.x = x & 0xFF;
this.y = y & 0xFF;
this.z = z & 0xFF;
this.id = id;
this.color = color;
}
}
public static final strictfp class Region {
public final int x;
public final int z;
public final List<BlockMesh.Position> blocks = new ArrayList<>();
public float[] vertices = new float[0];
public int[] colors = new int[0];
public short[] indices = new short[0];
public Region(int x, int z) {
this.x = x;
this.z = z;
}
public strictfp void release() {
this.blocks.clear();
this.vertices = new float[0];
this.colors = new int[0];
this.indices = new short[0];
}
}
}
@@ -0,0 +1,36 @@
package dev.slinky.client.render.world;
import dev.slinky.client.ClientContext;
import dev.slinky.client.inventory.ItemPolicyService;
import dev.slinky.inject.ReflectionHelper;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.client.multiplayer.ChunkProviderClient;
import net.minecraft.world.chunk.Chunk;
public final class LoadedChunkAccess {
private static final Field LIST = ReflectionHelper.findField(ChunkProviderClient.class, "chunkListing", "field_73237_c");
private LoadedChunkAccess() {
}
public static List<Chunk> loaded(ClientContext c) {
try {
return new ArrayList<>((List)LIST.get(c.world().getChunkProvider()));
} catch (IllegalAccessException var2) {
throw new IllegalStateException(var2);
}
}
public static int blockIdentifier(String name) {
int id = ItemPolicyService.identifier(name);
if (id >= 0 && id < 256 && Block.getBlockById(id) != null) {
return id;
} else {
Block block = Block.getBlockFromName(name);
return block == null ? -1 : Block.getIdFromBlock(block);
}
}
}
@@ -0,0 +1,65 @@
package dev.slinky.client.render.world;
import dev.slinky.client.event.SemanticEvent;
import dev.slinky.client.rotation.RotationMath;
import dev.slinky.setting.ColorSetting;
import net.minecraft.util.Vec3;
import org.lwjgl.opengl.GL11;
public final class RemoteBoxRenderer {
private static final int[] FILLED = new int[]{3, 7, 1, 5, 4, 7, 6, 3, 2, 1, 0, 4, 2, 6};
private static final int[] WIRE = new int[]{0, 1, 1, 5, 5, 4, 4, 0, 2, 3, 3, 7, 7, 6, 6, 2, 0, 2, 1, 3, 4, 6, 5, 7};
private RemoteBoxRenderer() {
}
public static void draw(
SemanticEvent e,
Vec3 previous,
Vec3 current,
float previousYaw,
float currentYaw,
boolean rotate,
boolean filled,
float lineWidth,
ColorSetting.Value color
) {
Vec3 position = RotationMath.interpolate(previous, current, (double)e.timerRenderPartialTicks);
GL11.glPushAttrib(1048575);
GL11.glPushMatrix();
try {
GL11.glDisable(3553);
GL11.glDisable(2896);
GL11.glEnable(3042);
GL11.glBlendFunc(770, 771);
GL11.glLineWidth(lineWidth);
GL11.glTranslatef((float)(position.xCoord - e.cameraX), (float)(position.yCoord - e.cameraY + 1.0), (float)(position.zCoord - e.cameraZ));
GL11.glScalef(0.3F, 1.0F, 0.3F);
if (rotate) {
GL11.glRotatef(180.0F - ((currentYaw - previousYaw) * e.timerRenderPartialTicks + previousYaw), 0.0F, 1.0F, 0.0F);
}
if (filled) {
GL11.glColor4f(color.red, color.green, color.blue, 0.4F);
vertices(5, FILLED);
}
GL11.glColor3f(color.red, color.green, color.blue);
vertices(1, WIRE);
} finally {
GL11.glPopMatrix();
GL11.glPopAttrib();
}
}
private static void vertices(int mode, int[] indices) {
GL11.glBegin(mode);
for (int i : indices) {
GL11.glVertex3f((i & 4) == 0 ? -1.0F : 1.0F, (i & 2) == 0 ? -1.0F : 1.0F, (i & 1) == 0 ? -1.0F : 1.0F);
}
GL11.glEnd();
}
}
@@ -0,0 +1,184 @@
package dev.slinky.client.render.world;
import dev.slinky.client.numeric.FloatTrigonometry;
import dev.slinky.client.packet.PredictionGeometry;
import dev.slinky.client.render.RenderConstants;
import dev.slinky.client.render.RenderMath;
import java.util.List;
public final strictfp class TrajectoryPrediction {
private TrajectoryPrediction() {
}
public static strictfp float charge(int ticks) {
float t = (float)ticks / RenderConstants.f(1396232);
return (t * t + (t + t)) / RenderConstants.f(1396236);
}
public static strictfp float speed(float strength) {
double value = (double)RenderMath.min(RenderConstants.f(1396248), strength) * RenderConstants.d(1396256);
return (float)(value + value);
}
public static strictfp TrajectoryPrediction.Launch launch(
double[] input, float yawDegrees, float pitchDegrees, float fraction, float previousSpeed, float speed, boolean positionModeZero, boolean sneaking
) {
double radians = RenderConstants.d(1396304);
float yaw = (float)((double)yawDegrees * radians);
float pitch = (float)((double)pitchDegrees * radians);
float cy = FloatTrigonometry.cosine(yaw);
float cp = FloatTrigonometry.cosine(pitch);
float sy = FloatTrigonometry.sine(yaw);
float sp = FloatTrigonometry.sine(pitch);
int sign = RenderConstants.i(1396320);
double[] direction = new double[]{
(double)(Float.intBitsToFloat(Float.floatToRawIntBits(sy) ^ sign) * cp),
(double)Float.intBitsToFloat(Float.floatToRawIntBits(sp) ^ sign),
(double)(cp * cy)
};
float currentSpeed = RenderMath.lerp(previousSpeed, speed, fraction);
double[] position = (double[])input.clone();
if (!positionModeZero) {
position[1] += RenderConstants.d(1304656);
if (sneaking) {
position[1] += RenderConstants.d(1396336);
}
}
double offset = RenderConstants.d(1396344);
position[0] -= (double)cy * offset;
position[1] += RenderConstants.d(1396352);
position[2] -= (double)sy * offset;
return new TrajectoryPrediction.Launch(position, RenderMath.scale(direction, (double)currentSpeed));
}
public static strictfp double[] sortedBox(double[] a, double[] b) {
double[] out = new double[6];
for (int i = 0; i < 3; i++) {
out[i] = Double.isNaN(a[i]) ? b[i] : (b[i] < a[i] ? b[i] : a[i]);
out[i + 3] = Double.isNaN(a[i]) ? b[i] : (b[i] > a[i] ? b[i] : a[i]);
}
return out;
}
public static strictfp double[] broad(TrajectoryPrediction.Launch launch) {
double h = RenderConstants.d(1396264);
return sortedBox(
new double[]{launch.position[0], -100.0, launch.position[2]},
new double[]{
launch.velocity[0] * h + launch.position[0], launch.position[1] + RenderConstants.d(1396272), h * launch.velocity[2] + launch.position[2]
}
);
}
public static strictfp boolean intersects(double[] a, double[] b) {
return a[0] < b[3] && b[0] < a[3] && a[1] < b[4] && b[1] < a[4] && a[2] < b[5] && b[2] < a[5];
}
public static strictfp TrajectoryPrediction.Result predict(
TrajectoryPrediction.Launch launch, List<TrajectoryPrediction.Candidate> candidates, TrajectoryPrediction.World world, float gravity, boolean pearl
) {
double[] position = (double[])launch.position.clone();
double[] velocity = (double[])launch.velocity.clone();
double drag = RenderConstants.d(1396296);
double blockSize = RenderConstants.d(1396288);
for (int step = 0; step < 100; step++) {
double[] old = (double[])position.clone();
position = RenderMath.add(old, velocity);
TrajectoryPrediction.BlockHit hit = world.trace(old, position);
double[] box = null;
if (hit != null) {
position = (double[])hit.point.clone();
velocity = RenderMath.subtract(position, old);
box = new double[]{hit.corner[0], hit.corner[1], hit.corner[2], hit.corner[0] + blockSize, hit.corner[1] + blockSize, hit.corner[2] + blockSize};
}
for (TrajectoryPrediction.Candidate entity : candidates) {
if (PredictionGeometry.ray(entity.box, old, velocity)) {
return new TrajectoryPrediction.Result(step + 1, true, !pearl, (double[])entity.box.clone(), (double[])entity.motion.clone());
}
}
if (hit != null) {
return new TrajectoryPrediction.Result(step + 1, true, pearl, box, new double[3]);
}
velocity = RenderMath.scale(velocity, drag);
velocity[1] -= (double)gravity;
}
return new TrajectoryPrediction.Result(100, false, false, null, new double[3]);
}
public static strictfp float[] renderPoints(TrajectoryPrediction.Launch launch, int steps, float gravity, double[] camera) {
double[] position = (double[])launch.position.clone();
double[] velocity = (double[])launch.velocity.clone();
float[] points = new float[steps * 3];
for (int i = 0; i < steps; i++) {
position = RenderMath.add(position, velocity);
for (int lane = 0; lane < 3; lane++) {
points[i * 3 + lane] = (float)(position[lane] - camera[lane]);
}
velocity = RenderMath.scale(velocity, RenderConstants.d(1396208));
velocity[1] -= (double)gravity;
}
return points;
}
public static final strictfp class BlockHit {
public final double[] point;
public final double[] corner;
public BlockHit(double[] point, double[] corner) {
this.point = (double[])point.clone();
this.corner = (double[])corner.clone();
}
}
public static final strictfp class Candidate {
public final double[] box;
public final double[] motion;
public Candidate(double[] box, double[] motion) {
this.box = (double[])box.clone();
this.motion = (double[])motion.clone();
}
}
public static final strictfp class Launch {
public final double[] position;
public final double[] velocity;
public Launch(double[] p, double[] v) {
this.position = p;
this.velocity = v;
}
}
public static final strictfp class Result {
public final int steps;
public final boolean marker;
public final boolean hitColor;
public final double[] box;
public final double[] motion;
public Result(int steps, boolean marker, boolean hitColor, double[] box, double[] motion) {
this.steps = steps;
this.marker = marker;
this.hitColor = hitColor;
this.box = box;
this.motion = motion;
}
}
public interface World {
TrajectoryPrediction.BlockHit trace(double[] var1, double[] var2);
}
}
@@ -0,0 +1,286 @@
package dev.slinky.client.rotation;
import dev.slinky.client.numeric.NumericMath;
public final strictfp class AimNoise {
private static final int[] PERMUTATION = new int[]{
151,
160,
137,
91,
90,
15,
131,
13,
201,
95,
96,
53,
194,
233,
7,
225,
140,
36,
103,
30,
69,
142,
8,
99,
37,
240,
21,
10,
23,
190,
6,
148,
247,
120,
234,
75,
0,
26,
197,
62,
94,
252,
219,
203,
117,
35,
11,
32,
57,
177,
33,
88,
237,
149,
56,
87,
174,
20,
125,
136,
171,
168,
68,
175,
74,
165,
71,
134,
139,
48,
27,
166,
77,
146,
158,
231,
83,
111,
229,
122,
60,
211,
133,
230,
220,
105,
92,
41,
55,
46,
245,
40,
244,
102,
143,
54,
65,
25,
63,
161,
1,
216,
80,
73,
209,
76,
132,
187,
208,
89,
18,
169,
200,
196,
135,
130,
116,
188,
159,
86,
164,
100,
109,
198,
173,
186,
3,
64,
52,
217,
226,
250,
124,
123,
5,
202,
38,
147,
118,
126,
255,
82,
85,
212,
207,
206,
59,
227,
47,
16,
58,
17,
182,
189,
28,
42,
223,
183,
170,
213,
119,
248,
152,
2,
44,
154,
163,
70,
221,
153,
101,
155,
167,
43,
172,
9,
129,
22,
39,
253,
19,
98,
108,
110,
79,
113,
224,
232,
178,
185,
112,
104,
218,
246,
97,
228,
251,
34,
242,
193,
238,
210,
144,
12,
191,
179,
162,
241,
81,
51,
145,
235,
249,
14,
239,
107,
49,
192,
214,
31,
181,
199,
106,
157,
184,
84,
204,
176,
115,
121,
50,
45,
127,
4,
150,
254,
138,
236,
205,
93,
222,
114,
67,
29,
24,
72,
243,
141,
128,
195,
78,
66,
215,
61,
156,
180
};
private AimNoise() {
}
private static strictfp float gradient(int index) {
int permutation = PERMUTATION[index & 0xFF];
float value = (float)((permutation & 7) + 1);
return (permutation & 8) != 0 ? -value : value;
}
public static strictfp float sample(float input) {
int truncated = NumericMath.truncateInt(input);
int index = truncated - (input > (float)truncated ? 0 : 1);
float phase = input - (float)index;
float cube = phase * phase * phase;
float polynomial = (6.0F * phase + -15.0F) * phase + 10.0F;
float left = gradient(index) * phase;
float rightPhase = -1.0F + phase;
float right = rightPhase * gradient(index + 1);
float weight = cube * polynomial;
return (weight * (right - left) + left) * 0.188F;
}
}
@@ -0,0 +1,128 @@
package dev.slinky.client.rotation;
import dev.slinky.client.numeric.FloatNumeric;
import dev.slinky.client.numeric.FloatTrigonometry;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.client.numeric.NumericServices;
import net.minecraft.util.Vec3;
public final strictfp class RotationMath {
private RotationMath() {
}
public static strictfp float sine(float radians) {
return FloatTrigonometry.sine(radians);
}
public static strictfp float cosine(float radians) {
return FloatTrigonometry.cosine(radians);
}
public static strictfp double atan2(double first, double second) {
return NumericServices.atan2(first, second);
}
public static strictfp float atan2(float first, float second) {
return NumericServices.atan2f(first, second);
}
public static strictfp float atan2f(float first, float second) {
return NumericServices.atan2f(first, second);
}
public static strictfp double hypot(double first, double second) {
return NumericServices.hypot(first, second);
}
public static strictfp float abs(float value) {
return Float.intBitsToFloat(Float.floatToRawIntBits(value) & 2147483647);
}
public static strictfp float min(float a, float b) {
return a < b ? a : b;
}
public static strictfp float max(float a, float b) {
return a > b ? a : b;
}
public static strictfp double min(double a, double b) {
return a < b ? a : b;
}
public static strictfp double max(double a, double b) {
return a > b ? a : b;
}
public static strictfp float clamp(float low, float high, float value) {
return value < low ? low : min(high, value);
}
public static strictfp double clamp(double low, double high, double value) {
return value < low ? low : min(high, value);
}
public static strictfp float wrap(float angle) {
float result = FloatNumeric.remainder(angle, 360.0F);
if (result >= 180.0F) {
result += -360.0F;
} else if (result < -180.0F) {
result += 360.0F;
}
return result;
}
public static strictfp float[] angles(Vec3 from, Vec3 to) {
double x = to.xCoord - from.xCoord;
double y = to.yCoord - from.yCoord;
double z = to.zCoord - from.zCoord;
double horizontal = Math.sqrt(x * x + z * z);
float yaw = (float)(atan2(z, x) * 180.0 / Math.PI + -90.0);
float pitch = (float)(atan2(y, horizontal) * -180.0 / Math.PI);
return new float[]{yaw, pitch};
}
public static strictfp Vec3 interpolate(Vec3 a, Vec3 b, double fraction) {
return new Vec3(fraction * (b.xCoord - a.xCoord) + a.xCoord, fraction * (b.yCoord - a.yCoord) + a.yCoord, (b.zCoord - a.zCoord) * fraction + a.zCoord);
}
public static strictfp double distance(Vec3 a, Vec3 b) {
double x = a.xCoord - b.xCoord;
double y = a.yCoord - b.yCoord;
double z = a.zCoord - b.zCoord;
return Math.sqrt(z * z + (x * x + y * y));
}
public static strictfp double[] movement(float yaw, float forward, float sideways, float speed) {
float f = forward * 0.98F;
float s = sideways * 0.98F;
float squared = s * s + f * f;
if (1.0E-4F > squared) {
return new double[]{0.0, 0.0};
} else {
float scale = speed * Float.intBitsToFloat(1065353213);
float length = max(1.0F, (float)Math.sqrt((double)squared));
scale /= length;
s *= scale;
f *= scale;
float angle = (float)((double)yaw * Math.PI / 180.0);
float sin = sine(angle);
float cos = cosine(angle);
return new double[]{(double)(s * cos - f * sin), (double)(f * cos + s * sin)};
}
}
public static strictfp float magnitude(float yaw, float pitch) {
return NumericMath.floatDistance(yaw, pitch);
}
public static strictfp int tieHash(int entityId, long counter) {
int x = entityId + (int)counter;
x ^= x >>> 16;
x *= 73244475;
x ^= x >>> 16;
x *= 73244475;
return x ^ x >>> 16;
}
}
@@ -0,0 +1,173 @@
package dev.slinky.client.rotation;
import dev.slinky.client.numeric.FloatNumeric;
import dev.slinky.client.numeric.NumericConstants;
import dev.slinky.client.numeric.NumericMath;
import java.security.SecureRandom;
public final strictfp class RotationRandom {
public static final RotationRandom SHARED = fresh();
private int a;
private int b;
private int c;
private int d;
public RotationRandom(int a, int b, int c, int d) {
if ((a | b | c | d) == 0) {
throw new IllegalArgumentException("zero random state");
} else {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
}
private static strictfp RotationRandom fresh() {
SecureRandom seed = new SecureRandom();
int a = seed.nextInt();
int b = seed.nextInt();
int c = seed.nextInt();
int d = seed.nextInt();
if ((a | b | c | d) == 0) {
d = 1;
}
return new RotationRandom(a, b, c, d);
}
public static strictfp double now() {
return (double)System.nanoTime() / 1.0E9;
}
private strictfp int next() {
int sum = this.d + this.a;
int rotated = Integer.rotateLeft(this.b ^ this.d, 11);
int nextB = this.c ^ this.a ^ this.b;
int nextA = this.a ^ this.b ^ this.d;
int nextC = this.c ^ this.a ^ this.b << 9;
this.a = nextA;
this.b = nextB;
this.c = nextC;
this.d = rotated;
return sum;
}
public strictfp float interval(float low, float high) {
float width = high - low;
float value = (float)(this.next() >>> 8);
value *= 5.9604645E-8F;
value *= width;
return value + low;
}
public strictfp boolean chance(float threshold) {
return threshold > this.interval(0.0F, 1.0F);
}
public strictfp int[] state() {
return new int[]{this.a, this.b, this.c, this.d};
}
public static final class Gaussian {
private boolean valid;
private float cached;
public strictfp float sample(RotationRandom random) {
if (this.valid) {
this.valid = false;
return this.cached;
} else {
float x;
float y;
float radius;
do {
float value = (float)(random.next() >>> 8) * 5.9604645E-8F;
x = value + value + -1.0F;
value = (float)(random.next() >>> 8) * 5.9604645E-8F;
y = value + value + -1.0F;
radius = NumericMath.floatDistance(x, y);
} while (radius >= 1.0F || radius == 0.0F);
float ratio = FloatNumeric.log(radius) * -2.0F / radius;
float factor = FloatNumeric.sqrt(ratio);
this.cached = y * factor;
this.valid = true;
return x * factor;
}
}
}
public static final class Segment {
private double elapsed;
private float left;
private float right;
public strictfp float sample(float low, float high, double duration, double delta, RotationRandom random) {
this.elapsed += delta;
if (this.elapsed > duration) {
this.elapsed = 0.0;
if (this.left == 0.0F && this.right == 0.0F) {
do {
this.left = draw(random, 1304864, 1304868);
this.right = draw(random, 1304864, 1304868);
} while (NumericConstants.doubleAt(1304896) > this.maskedDifference(1304880));
} else {
this.left = this.right;
do {
this.right = draw(random, 1304912, 1304916);
} while (NumericConstants.doubleAt(1304944) > this.maskedDifference(1304928));
}
}
double phase = this.elapsed / duration;
double curve = (double)(this.right - this.left) * phase + (double)this.left;
return (float)((double)(high - low) * curve + (double)low);
}
private strictfp double maskedDifference(int mask) {
return (double)Float.intBitsToFloat(Float.floatToRawIntBits(this.left - this.right) & NumericConstants.intAt(mask));
}
private static strictfp float draw(RotationRandom random, int upper, int scale) {
float value = (float)(random.next() >>> 8);
value *= NumericConstants.floatAt(scale);
value *= NumericConstants.floatAt(upper);
return value + 0.0F;
}
}
public static final class Smooth {
private double timestamp;
private float left;
private float right;
public strictfp float sample(float low, float high, double duration, double now, RotationRandom random) {
if (now > this.timestamp + duration) {
this.timestamp = now;
if (this.left == 0.0F && this.right == 0.0F) {
do {
this.left = random.interval(0.0F, 1.0F);
this.right = random.interval(0.0F, 1.0F);
} while (0.2 > (double)RotationMath.abs(this.left - this.right));
} else {
this.left = this.right;
do {
this.right = random.interval(0.0F, 1.0F);
} while (0.2 > (double)RotationMath.abs(this.left - this.right));
}
}
double phase = (now - this.timestamp) / duration;
double curve = (double)(this.right - this.left) * phase + (double)this.left;
return (float)((double)(high - low) * curve + (double)low);
}
public strictfp boolean gate(float low, float high, double duration, double now, RotationRandom random) {
float comparison = random.interval(0.0F, 1.0F);
return this.sample(low, high, duration, now, random) > comparison;
}
}
}
@@ -0,0 +1,456 @@
package dev.slinky.client.rotation;
import dev.slinky.client.ClientContext;
import dev.slinky.client.event.SemanticEvent;
import dev.slinky.client.numeric.FloatNumeric;
import dev.slinky.client.numeric.NumericMath;
import dev.slinky.inject.ReflectionHelper;
import java.lang.reflect.Field;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.entity.Entity;
public final strictfp class RotationService {
private static int priority;
private static int deltaAge;
private static float quantum;
private static float pendingYaw;
private static float pendingPitch;
private static float manualYaw;
private static float manualPitch;
private static float cameraYaw;
private static float cameraPitch;
private static float retainedYaw;
private static float retainedPitch;
private static float savedMouseYaw;
private static float savedMousePitch;
private static float displayYaw;
private static float displayPitch;
private static boolean active;
private static boolean useRetained;
private static boolean remap;
private static boolean keysRemapped;
private static boolean restoreCameraPending;
private static boolean mousePending;
private static int savedKeys;
private static KeyBinding[] remappedKeys;
private static Entity mouseEntity;
private static final RotationService.RotationSnapshot camera = new RotationService.RotationSnapshot();
private static final RotationService.RotationSnapshot renderInfo = new RotationService.RotationSnapshot();
private static final Field PRESSED = ReflectionHelper.findField(KeyBinding.class, "pressed", "field_74513_e");
private RotationService() {
}
public static strictfp void beginFrame(ClientContext context, double elapsedSeconds, float partialTicks) {
if (!context.ready()) {
reset(context);
} else {
float sensitivity = context.minecraft().gameSettings.mouseSensitivity * 0.6F + 0.2F;
quantum = sensitivity * sensitivity * sensitivity * 8.0F * 0.15F;
int previousAge = deltaAge++;
if (previousAge > 0) {
pendingPitch = 0.0F;
pendingYaw = 0.0F;
}
priority = 0;
}
}
public static strictfp boolean claim(int requested) {
if (Integer.compareUnsigned(requested, priority) < 0) {
return false;
} else {
priority = requested;
return true;
}
}
public static strictfp boolean storedRotationValid() {
return active;
}
public static strictfp float storedYaw() {
return retainedYaw;
}
public static strictfp float storedPitch() {
return retainedPitch;
}
public static strictfp float cameraYaw() {
return cameraYaw;
}
public static strictfp float cameraPitch() {
return cameraPitch;
}
public static strictfp float pendingYaw() {
return pendingYaw;
}
public static strictfp float pendingPitch() {
return pendingPitch;
}
public static strictfp float manualYaw() {
return manualYaw;
}
public static strictfp float manualPitch() {
return manualPitch;
}
public static strictfp void clearManual() {
manualPitch = 0.0F;
manualYaw = 0.0F;
}
public static strictfp void retain(ClientContext context, boolean retained, float yaw, float pitch) {
endMovement(context);
if (!active && context.player() != null) {
active = true;
cameraYaw = context.player().rotationYaw;
cameraPitch = context.player().rotationPitch;
}
useRetained = retained;
retainedYaw = yaw;
retainedPitch = pitch;
remap = false;
}
public static strictfp void applyPending(ClientContext context) {
applyPending(context, false);
}
public static strictfp void applyPending(ClientContext context, boolean alsoArm) {
if (context.player() != null) {
if (quantum != 0.0F) {
float yawStep = quantize(pendingYaw, quantum);
float pitchStep = quantize(pendingPitch, quantum);
pendingYaw -= yawStep;
pendingPitch -= pitchStep;
if (yawStep != 0.0F || pitchStep != 0.0F) {
float yaw = yawStep + context.player().rotationYaw;
float pitch = pitchStep + context.player().rotationPitch;
setRotation(context.player(), yaw, pitch, true);
if (alsoArm) {
setArms(context, yaw, pitch);
}
}
}
}
}
public static strictfp void add(float yaw, float pitch) {
pendingYaw += yaw;
pendingPitch += pitch;
deltaAge = 0;
}
public static strictfp void manualDelta(float yaw, float pitch) {
manualYaw += yaw;
manualPitch += pitch;
}
public static strictfp boolean mouseAngles(Entity entity, ClientContext context, float yaw, float pitch) {
return false;
}
public static strictfp void prepareMouse(ClientContext context) {
closeMouse();
Entity player = context.player();
if (player != null) {
savedMouseYaw = player.rotationYaw;
savedMousePitch = player.rotationPitch;
if (restoreCameraPending) {
restoreCameraPending = false;
setRotation(player, cameraYaw, cameraPitch, true);
}
if (active) {
mousePending = true;
mouseEntity = player;
setRotation(player, cameraYaw, cameraPitch, false);
}
}
}
private static strictfp void finishMouse(Entity player) {
if (mousePending) {
if (player != mouseEntity) {
closeMouse();
} else {
cameraYaw = player.rotationYaw;
cameraPitch = player.rotationPitch;
closeMouse();
}
}
}
private static strictfp void closeMouse() {
Entity player = mousePending ? mouseEntity : null;
mousePending = false;
mouseEntity = null;
if (player != null) {
setRotation(player, savedMouseYaw, savedMousePitch, true);
}
}
public static strictfp void endRender() {
endRenderInfo();
endCamera();
closeMouse();
}
public static strictfp void beforeDisplay(ClientContext context) {
if (context.ready()) {
float yaw = context.player().rotationYaw - displayYaw;
float pitch = context.player().rotationPitch - displayPitch;
if (((Float.floatToRawIntBits(yaw) | Float.floatToRawIntBits(pitch)) & 2147483647) != 0) {
manualDelta(yaw, pitch);
}
}
}
public static strictfp void afterDisplay(ClientContext context) {
if (context.ready()) {
displayYaw = context.player().rotationYaw;
displayPitch = context.player().rotationPitch;
}
}
public static strictfp void restoreCamera(float yaw, float pitch) {
cameraYaw = RotationMath.wrap(yaw - cameraYaw) + cameraYaw;
cameraPitch = pitch;
restoreCameraPending = true;
}
public static strictfp float quantize(float value, float quantum) {
if (quantum != 0.0F && (-quantum >= value || value >= quantum)) {
float quotient = value / quantum;
float whole = FloatNumeric.truncate(quotient);
return whole * quantum;
} else {
return 0.0F;
}
}
public static strictfp void finishFrame(ClientContext context, SemanticEvent event) {
endMovement(context);
if (context.ready()) {
boolean requested = event.active;
if (requested) {
if (!active) {
cameraYaw = context.player().rotationYaw;
cameraPitch = context.player().rotationPitch;
}
active = true;
useRetained = event.overrideRotation;
retainedYaw = event.yaw;
retainedPitch = event.pitch;
remap = event.remapMovement;
} else if (active) {
active = false;
float yaw = useRetained ? retainedYaw : cameraYaw;
float pitch = useRetained ? retainedPitch : cameraPitch;
yaw = RotationMath.wrap(yaw - context.player().rotationYaw) + context.player().rotationYaw;
setRotation(context.player(), yaw, pitch, true);
setArms(context, yaw, pitch);
}
}
}
public static strictfp int remapMask(int mask, float yawDifference) {
int forward = (mask & 1) - (mask >>> 1 & 1);
int sideways = (mask >>> 2 & 1) - (mask >>> 3 & 1);
if (forward == 0 && sideways == 0) {
return 0;
} else {
int sector = forward == 0 ? (sideways == 1 ? 2 : 6) : (forward == -1 ? 5 - (sideways + 1) : (sideways == 1 ? 1 : (sideways == -1 ? 7 : 0)));
float divided = RotationMath.wrap(yawDifference) / 45.0F;
float rounded = FloatNumeric.round(divided);
int[] table = new int[]{1, 5, 4, 6, 2, 10, 8, 9};
return table[NumericMath.truncateInt(rounded) + sector & 7];
}
}
private static strictfp KeyBinding[] movementKeys(ClientContext context) {
GameSettings g = context.minecraft().gameSettings;
return new KeyBinding[]{g.keyBindForward, g.keyBindBack, g.keyBindLeft, g.keyBindRight};
}
public static strictfp void beginMovement(ClientContext context) {
if (remap && !keysRemapped) {
if (context.inputReady()) {
captureMovement(movementKeys(context), context.player().rotationYaw - retainedYaw);
}
keysRemapped = true;
}
}
private static strictfp void captureMovement(KeyBinding[] keys, float yawDifference) {
remappedKeys = keys;
savedKeys = 0;
for (int i = 0; i < 4; i++) {
if (keys[i].isKeyDown()) {
savedKeys |= 1 << i;
}
}
int mask = remapMask(savedKeys, yawDifference);
setKeys(keys, mask);
keysRemapped = true;
}
public static strictfp void endMovement(ClientContext context) {
if (context == null) {
if (remappedKeys != null) {
setKeys(remappedKeys, savedKeys);
}
remappedKeys = null;
}
if (keysRemapped) {
if (context != null && context.inputReady()) {
setKeys(movementKeys(context), savedKeys);
remappedKeys = null;
}
remap = false;
keysRemapped = false;
}
}
public static strictfp void beginCamera(ClientContext context) {
if (mousePending && mouseEntity != context.player()) {
closeMouse();
}
if (active && camera.entity == null && context.player() != null) {
if (context.minecraft().renderGlobal != null) {
context.minecraft().renderGlobal.setDisplayListEntitiesDirty();
}
finishMouse(context.player());
captureCamera(context.player());
}
}
private static strictfp void captureCamera(Entity entity) {
camera.capture(entity);
}
public static strictfp void endCamera() {
camera.restore();
}
public static strictfp void beginRenderInfo(ClientContext context) {
if (active && renderInfo.entity == null) {
renderInfo.capture(context.player());
}
}
public static strictfp void endRenderInfo() {
renderInfo.restore();
}
public static strictfp float renderViewPitch(float vanilla) {
return active ? cameraPitch : vanilla;
}
public static strictfp float renderViewYaw(float vanilla) {
return active ? cameraYaw : vanilla;
}
private static strictfp void setRotation(Entity entity, float yaw, float pitch, boolean previous) {
entity.rotationYaw = yaw;
entity.rotationPitch = RotationMath.clamp(-90.0F, 90.0F, pitch);
if (previous) {
entity.prevRotationYaw = yaw;
entity.prevRotationPitch = entity.rotationPitch;
}
}
private static strictfp void setArms(ClientContext context, float yaw, float pitch) {
context.player().prevRenderArmYaw = yaw;
context.player().prevRenderArmPitch = pitch;
context.player().renderArmYaw = yaw;
context.player().renderArmPitch = pitch;
}
private static strictfp void setKeys(KeyBinding[] keys, int mask) {
try {
for (int i = 0; i < 4; i++) {
PRESSED.setBoolean(keys[i], (mask & 1 << i) != 0);
}
} catch (IllegalAccessException var3) {
throw new IllegalStateException(var3);
}
}
public static strictfp void reset(ClientContext context) {
endRender();
endMovement(null);
remap = false;
useRetained = false;
active = false;
deltaAge = 0;
priority = 0;
quantum = 0.0F;
mousePending = false;
restoreCameraPending = false;
mouseEntity = null;
manualPitch = 0.0F;
manualYaw = 0.0F;
pendingPitch = 0.0F;
pendingYaw = 0.0F;
displayPitch = 0.0F;
displayYaw = 0.0F;
retainedPitch = 0.0F;
retainedYaw = 0.0F;
cameraPitch = 0.0F;
cameraYaw = 0.0F;
savedMousePitch = 0.0F;
savedMouseYaw = 0.0F;
savedKeys = 0;
}
private static final strictfp class RotationSnapshot {
Entity entity;
float yaw;
float pitch;
float prevYaw;
float prevPitch;
private RotationSnapshot() {
}
strictfp void capture(Entity value) {
if (this.entity == null && value != null) {
this.entity = value;
this.yaw = value.rotationYaw;
this.pitch = value.rotationPitch;
this.prevYaw = value.prevRotationYaw;
this.prevPitch = value.prevRotationPitch;
RotationService.setRotation(value, RotationService.cameraYaw, RotationService.cameraPitch, true);
}
}
strictfp void restore() {
if (this.entity != null) {
RotationService.setRotation(this.entity, this.yaw, this.pitch, false);
this.entity.prevRotationYaw = this.prevYaw;
this.entity.prevRotationPitch = RotationMath.clamp(-90.0F, 90.0F, this.prevPitch);
this.entity = null;
}
}
}
}
@@ -0,0 +1,124 @@
package dev.slinky.client.target;
import dev.slinky.client.ClientContext;
import dev.slinky.client.event.SemanticEvent;
import dev.slinky.client.rotation.RotationMath;
import dev.slinky.module.ModuleManager;
import dev.slinky.module.combat.PiercingModule;
import dev.slinky.module.world.GhostHandModule;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItemFrame;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.MovingObjectPosition.MovingObjectType;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.Vec3;
public final strictfp class RayQueryAdapter {
private RayQueryAdapter() {
}
public static strictfp boolean update(ModuleManager modules, SemanticEvent query) {
if (modules != null && query.queryOverride && modules.context().ready()) {
ClientContext context = modules.context();
Minecraft mc = context.minecraft();
Entity viewer = mc.getRenderViewEntity();
if (viewer != context.player()) {
return false;
} else {
PiercingModule piercing = (PiercingModule)modules.find("piercing");
GhostHandModule ghost = (GhostHandModule)modules.find("ghosthand");
boolean throughBlocks = piercing != null && piercing.pierceBlocks(context);
double entityDistance = query.distance;
double rayLength = entityDistance + 1.5;
MovingObjectPosition block = viewer.rayTrace(rayLength, 1.0F);
Vec3 origin = viewer.getPositionEyes(1.0F);
if (!throughBlocks && block != null) {
rayLength = origin.distanceTo(block.hitVec);
}
Vec3 direction = direction(viewer.rotationYaw, viewer.rotationPitch);
Vec3 end = origin.addVector(direction.xCoord * rayLength, direction.yCoord * rayLength, direction.zCoord * rayLength);
Entity selected = null;
Vec3 selectedPoint = null;
double selectedDistance = entityDistance;
boolean insideSeen = false;
boolean allSelectedAllowThrough = block != null && block.typeOfHit == MovingObjectType.BLOCK;
for (Object item : context.world().loadedEntityList) {
Entity candidate = (Entity)item;
if (candidate != viewer && candidate.canBeCollidedWith()) {
AxisAlignedBB bounds = candidate.getEntityBoundingBox().expand(query.expand, query.expand, query.expand);
MovingObjectPosition hit = bounds.calculateIntercept(origin, end);
if (bounds.isVecInside(origin)) {
insideSeen = true;
if (!(selectedDistance < 0.0)) {
int rank = selected != null && piercing != null ? piercing.rank(context, selected, candidate, true) : 2;
if (rank != 1) {
selected = candidate;
selectedPoint = hit == null ? origin : hit.hitVec;
selectedDistance = 0.0;
}
}
} else {
if (hit == null) {
continue;
}
double distance = origin.distanceTo(hit.hitVec);
int rank = piercing == null ? 2 : piercing.rank(context, selected, candidate, insideSeen);
if (choose(rank, entityDistance, selectedDistance, distance)) {
selected = candidate;
selectedPoint = hit.hitVec;
selectedDistance = distance;
}
}
allSelectedAllowThrough = allSelectedAllowThrough && ghost != null && ghost.canInteractThrough(context, selected);
}
}
mc.pointedEntity = null;
mc.objectMouseOver = block;
if (allSelectedAllowThrough) {
return true;
} else {
if (selected != null && (block == null || !(selectedDistance > rayLength) || throughBlocks)) {
AxisAlignedBB original = selected.getEntityBoundingBox().expand(0.1F, 0.1F, 0.1F);
Vec3 point = new Vec3(
clamp(selectedPoint.xCoord, original.minX, original.maxX),
clamp(selectedPoint.yCoord, original.minY, original.maxY),
clamp(selectedPoint.zCoord, original.minZ, original.maxZ)
);
mc.objectMouseOver = new MovingObjectPosition(selected, point);
if (selected instanceof EntityLivingBase || selected instanceof EntityItemFrame) {
mc.pointedEntity = selected;
}
}
return true;
}
}
} else {
return false;
}
}
public static strictfp boolean choose(int rank, double reach, double previous, double candidate) {
return rank == 2 ? previous == 0.0 || previous > candidate : rank == 0 && reach > candidate;
}
private static strictfp double clamp(double value, double low, double high) {
return value < low ? low : (value > high ? high : value);
}
public static strictfp Vec3 direction(float yaw, float pitch) {
float yawRadians = (float)((double)yaw * -Math.PI / 180.0 + -Math.PI);
float pitchRadians = (float)((double)pitch * -Math.PI / 180.0);
float negativeCosine = -RotationMath.cosine(pitchRadians);
float x = RotationMath.sine(yawRadians) * negativeCosine;
float z = negativeCosine * RotationMath.cosine(yawRadians);
return new Vec3((double)x, (double)RotationMath.sine(pitchRadians), (double)z);
}
}
@@ -0,0 +1,41 @@
package dev.slinky.client.target;
import java.util.ArrayList;
import java.util.Map;
import java.util.WeakHashMap;
import net.minecraft.entity.Entity;
public final class RelationshipService {
private static final Map<Entity, Integer> FLAGS = new WeakHashMap<>();
private RelationshipService() {
}
public static int flags(Entity entity) {
Integer value = FLAGS.get(entity);
return value == null ? 0 : value;
}
public static void set(Entity entity, int mask, boolean value) {
int next = value ? flags(entity) | mask : flags(entity) & ~mask;
if (next == 0) {
FLAGS.remove(entity);
} else {
FLAGS.put(entity, next);
}
}
public static void clear(int mask) {
for (Entity entity : new ArrayList<>(FLAGS.keySet())) {
set(entity, mask, false);
}
}
public static void clear() {
FLAGS.clear();
}
public static boolean hostile(Entity entity) {
return (flags(entity) & 7) == 0;
}
}
@@ -0,0 +1,162 @@
package dev.slinky.client.target;
import dev.slinky.client.ClientContext;
import dev.slinky.client.event.SemanticEvent;
import dev.slinky.client.rotation.RotationMath;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import dev.slinky.module.combat.AimAssistModule;
import net.minecraft.client.entity.EntityOtherPlayerMP;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.MovingObjectPosition.MovingObjectType;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.Vec3;
public final strictfp class TargetCombatStatus {
private boolean valid;
private boolean reducing;
private EntityPlayer target;
private double distanceSquared;
public strictfp EntityPlayer target() {
return this.target;
}
public strictfp double distanceSquared() {
return this.distanceSquared;
}
public strictfp void select(ClientContext c, ModuleManager modules) {
this.valid = false;
if (!c.ready()) {
this.target = null;
} else {
Vec3 eye = new Vec3(c.player().posX, c.player().posY + 1.62, c.player().posZ);
EntityPlayer chosen = null;
MovingObjectPosition hit = c.minecraft().objectMouseOver;
if (hit != null && hit.typeOfHit == MovingObjectType.ENTITY && hit.entityHit instanceof EntityOtherPlayerMP) {
EntityPlayer p = (EntityPlayer)hit.entityHit;
if (RelationshipService.hostile(p) && !(0.0F >= p.getHealth())) {
chosen = p;
}
}
if (chosen == null) {
Module aim = modules.find("aimassist");
if (aim instanceof AimAssistModule) {
chosen = ((AimAssistModule)aim).currentTarget();
}
}
if (chosen != null) {
this.target = chosen;
this.distanceSquared = boxDistance(eye, chosen);
} else {
double best = 225.0;
for (EntityPlayer p : c.world().playerEntities) {
if (p != c.player() && RelationshipService.hostile(p) && !(0.0F >= p.getHealth()) && !p.isPlayerSleeping()) {
double distance = boxDistance(eye, p);
if (!(distance > best)) {
float[] angle = RotationMath.angles(eye, new Vec3(p.posX, p.posY, p.posZ));
float yaw = RotationMath.wrap(angle[0] - c.player().rotationYaw);
float pitch = angle[1] - c.player().rotationPitch;
float metric = yaw * yaw;
metric += pitch;
metric += pitch;
if (!(metric > 4900.0F)) {
best = distance;
chosen = p;
}
}
}
}
this.target = chosen;
if (chosen != null) {
this.distanceSquared = best;
}
}
}
}
private static strictfp double boxDistance(Vec3 eye, Entity entity) {
AxisAlignedBB box = entity.getEntityBoundingBox().expand(0.1, 0.1, 0.1);
double x = RotationMath.clamp(box.minX, box.maxX, eye.xCoord) - eye.xCoord;
double y = RotationMath.clamp(box.minY, box.maxY, eye.yCoord) - eye.yCoord;
double z = RotationMath.clamp(box.minZ, box.maxZ, eye.zCoord) - eye.zCoord;
return z * z + (x * x + y * y);
}
public strictfp void clear() {
this.valid = false;
this.target = null;
this.distanceSquared = 0.0;
}
public strictfp void reset() {
this.valid = false;
}
public strictfp void prepare(ClientContext context) {
if (!this.valid) {
MovingObjectPosition hit = context.minecraft().objectMouseOver;
if (context.ready() && hit != null && hit.typeOfHit == MovingObjectType.ENTITY) {
this.sample(
true,
new Vec3(context.player().posX, context.player().posY + 1.62, context.player().posZ),
hit.hitVec,
context.player().motionX,
context.player().motionY,
context.player().motionZ,
context.player().rotationYaw,
context.player().moveForward,
context.player().moveStrafing,
context.player().onGround ? context.player().getAIMoveSpeed() : 0.026F
);
} else {
this.sample(false, null, null, 0.0, 0.0, 0.0, 0.0F, 0.0F, 0.0F, 0.0F);
}
}
}
public strictfp void sample(boolean entityHit, Vec3 eye, Vec3 hit, double x, double y, double z, float yaw, float forward, float sideways, float speed) {
if (!this.valid) {
this.reducing = true;
if (entityHit) {
double[] movement = RotationMath.movement(yaw, forward, sideways, speed);
double normalX = x + movement[0];
double normalY = y + 0.0;
double normalZ = movement[1] + z;
double reducedX = x * 0.6 + movement[0];
double reducedY = y + 0.0;
double reducedZ = movement[1] + 0.6 * z;
double reducedDistance = squared(eye.xCoord + reducedX, eye.yCoord + reducedY, reducedZ + eye.zCoord, hit);
double normalDistance = squared(eye.xCoord + normalX, eye.yCoord + normalY, normalZ + eye.zCoord, hit);
this.reducing = normalDistance > reducedDistance;
}
this.valid = true;
}
}
private static strictfp double squared(double x, double y, double z, Vec3 hit) {
x -= hit.xCoord;
y -= hit.yCoord;
z -= hit.zCoord;
return z * z + (x * x + y * y);
}
public strictfp SemanticEvent event(int type, boolean use) {
if (!this.valid && !use) {
throw new IllegalStateException("Attack status must be prepared before dispatch");
} else {
SemanticEvent event = new SemanticEvent(type);
event.active = !use;
event.reducing = this.reducing;
return event;
}
}
}
@@ -0,0 +1,199 @@
package dev.slinky.config;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import dev.slinky.setting.Setting;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class ConfigStore {
private final Path path;
private List<ModuleConfiguration.ProfileBinding> profileBindings = new ArrayList<>();
public ConfigStore(Path path) {
this.path = path;
}
public synchronized void load(ModuleManager manager) throws IOException {
if (Files.isRegularFile(this.path)) {
JsonObject root = read(this.path);
JsonObject modules = object(root, "modules");
if (modules == null) {
try {
ModuleConfiguration.apply(manager, root, ModuleConfiguration.Scope.GLOBAL);
this.profileBindings = ModuleConfiguration.profileBindings(root);
Path checkpoint = this.checkpointPath();
if (ModuleConfiguration.hasRecords(manager, root, ModuleConfiguration.Scope.PROFILE)) {
ModuleConfiguration.apply(manager, root, ModuleConfiguration.Scope.PROFILE);
} else if (Files.isRegularFile(checkpoint)) {
ModuleConfiguration.apply(manager, read(checkpoint), ModuleConfiguration.Scope.PROFILE);
}
} catch (RuntimeException var12) {
throw new IOException("Could not apply configuration", var12);
}
} else {
for (Module module : manager.all()) {
JsonObject record = object(modules, module.getId());
if (record != null) {
JsonObject settings = object(record, "settings");
if (settings != null) {
for (Setting<?> setting : module.getSettings()) {
JsonElement value = settings.get(setting.getKey());
if (value != null) {
try {
SettingCodec.decode(setting, value);
} catch (RuntimeException var13) {
}
}
}
}
JsonElement enabled = record.get("enabled");
JsonElement hold = record.get("__hold");
module.setHoldBinding(hold != null && hold.isJsonPrimitive() && hold.getAsJsonPrimitive().isBoolean() && hold.getAsBoolean());
JsonElement binding = record.get("__bind");
if (binding != null && binding.isJsonPrimitive() && binding.getAsJsonPrimitive().isNumber()) {
manager.setBinding(module, binding.getAsInt());
}
if (enabled != null && enabled.isJsonPrimitive()) {
try {
manager.setEnabled(module, enabled.getAsBoolean(), false);
} catch (IllegalArgumentException var14) {
throw new IOException("Incomplete configuration for " + module.getName(), var14);
}
}
}
}
}
}
}
public synchronized void save(ModuleManager manager) throws IOException {
this.write(this.path, ModuleConfiguration.serialize(manager, ModuleConfiguration.Scope.GLOBAL, this.profileBindings));
this.write(this.checkpointPath(), ModuleConfiguration.serialize(manager, ModuleConfiguration.Scope.PROFILE, Collections.emptyList()));
}
public synchronized List<ModuleConfiguration.ProfileBinding> profileBindings() {
return Collections.unmodifiableList(new ArrayList<>(this.profileBindings));
}
public synchronized void profileBindings(List<ModuleConfiguration.ProfileBinding> bindings) {
this.profileBindings = new ArrayList<>(bindings);
}
public synchronized void loadProfile(ModuleManager manager) throws IOException {
try {
ModuleConfiguration.apply(manager, read(this.path), ModuleConfiguration.Scope.PROFILE);
} catch (RuntimeException var3) {
throw new IOException("Could not apply profile", var3);
}
}
public static int importText(ModuleManager manager, byte[] text) throws IOException {
JsonObject root = ConfigurationJson.parse(text);
int records = 0;
for (Module module : manager.all()) {
if (object(root, module.getName()) != null) {
records++;
}
}
if (records == 0) {
throw new IOException("No module records in the configuration");
} else {
try {
if (ModuleConfiguration.hasRecords(manager, root, ModuleConfiguration.Scope.GLOBAL)) {
ModuleConfiguration.apply(manager, root, ModuleConfiguration.Scope.GLOBAL);
}
ModuleConfiguration.apply(manager, root, ModuleConfiguration.Scope.PROFILE);
return records;
} catch (RuntimeException var6) {
throw new IOException("Could not apply configuration", var6);
}
}
}
public synchronized void saveProfile(ModuleManager manager) throws IOException {
this.saveProfile(manager, true);
}
public synchronized void saveProfile(ModuleManager manager, boolean replace) throws IOException {
Path parent = this.path.toAbsolutePath().getParent();
if (parent != null && parent.getParent() != null) {
ensure(parent.getParent());
}
writeBytes(
this.path, ConfigurationJson.print(ModuleConfiguration.serialize(manager, ModuleConfiguration.Scope.PROFILE, Collections.emptyList())), replace
);
}
private Path checkpointPath() {
return this.path.resolveSibling(this.path.getFileName().toString() + ".active");
}
private static JsonObject read(Path target) throws IOException {
ConfigurationFiles.WidePath file = new ConfigurationFiles.WidePath();
file.text = ConfigurationFiles.wide(target.toString());
ConfigurationFiles.Text text = new ConfigurationFiles.Text();
JsonObject var6;
try (ConfigurationFiles.NioHost host = new ConfigurationFiles.NioHost(null)) {
int status = ConfigurationFiles.readText(file, text, host);
if (status != 0) {
throw new ConfigurationFiles.Failure(status);
}
try {
var6 = ConfigurationJson.parse(text.bytes);
} finally {
host.release(text.bytes);
}
}
return var6;
}
private void write(Path target, JsonObject root) throws IOException {
ensure(target.toAbsolutePath().getParent());
writeBytes(target, ConfigurationJson.print(root), true);
}
private static void ensure(Path directory) throws IOException {
ConfigurationFiles.WidePath path = new ConfigurationFiles.WidePath();
path.text = ConfigurationFiles.wide(directory.toString());
try (ConfigurationFiles.NioHost host = new ConfigurationFiles.NioHost(null)) {
int status = ConfigurationFiles.ensureDirectory(path, host);
if (status != 0) {
throw new ConfigurationFiles.Failure(status);
}
}
}
private static void writeBytes(Path target, byte[] text, boolean replace) throws IOException {
ConfigurationFiles.WidePath path = new ConfigurationFiles.WidePath();
path.text = ConfigurationFiles.wide(target.toString());
try (ConfigurationFiles.NioHost host = new ConfigurationFiles.NioHost(null)) {
int status = ConfigurationFiles.writeText(path, text, replace ? 1 : 0, 0, host);
if (status != 0) {
throw new ConfigurationFiles.Failure(status);
}
}
}
private static JsonObject object(JsonObject parent, String key) {
JsonElement element = parent.get(key);
return element != null && element.isJsonObject() ? element.getAsJsonObject() : null;
}
}
@@ -0,0 +1,393 @@
package dev.slinky.config;
import dev.slinky.config.json.JsonRuntime;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Set;
public final class ConfigurationFiles {
private ConfigurationFiles() {
}
public static int initializePath(ConfigurationFiles.WidePath path, ConfigurationFiles.Host host) {
char[] base = host.environment();
if (base != null && base[0] != 0) {
int length = length(base);
char[] copy = host.allocateWide((long)length + 1L);
if (copy == null) {
return 1005;
} else {
System.arraycopy(base, 0, copy, 0, length);
path.text = copy;
return 0;
}
} else {
return 1004;
}
}
public static int appendPath(ConfigurationFiles.WidePath path, byte[] component, ConfigurationFiles.Host host) {
int oldLength = length(path.text);
int componentLength = JsonRuntime.stringLength(component, 0);
char[] next = host.allocateWide(261L);
if (next == null) {
return 1005;
} else {
long capacity = (long)(oldLength + componentLength + 2);
host.formatPath(next, capacity, path.text, component);
host.release(path.text);
path.text = next;
return 0;
}
}
public static void parentPath(ConfigurationFiles.WidePath path) {
int offset = length(path.text);
int destination = 0;
do {
if (path.text[offset] == '\\') {
destination = offset;
break;
}
} while (--offset != 0);
path.text[destination] = 0;
}
public static int ensureDirectory(ConfigurationFiles.WidePath path, ConfigurationFiles.Host host) {
return host.createDirectory(path.text) == 0 && host.lastError() != 183 ? 1014 : 0;
}
public static int deletePath(ConfigurationFiles.WidePath path, ConfigurationFiles.Host host) {
return host.deleteFile(path.text) != 0 ? 0 : 1011;
}
public static int readText(ConfigurationFiles.WidePath path, ConfigurationFiles.Text output, ConfigurationFiles.Host host) {
Object handle = host.open(path.text, Integer.MIN_VALUE, 1, 3);
if (handle == null) {
return 1008;
} else {
int size = host.size(handle);
if (size == -1) {
host.close(handle);
return 1007;
} else {
byte[] buffer = host.allocateBytes(Integer.toUnsignedLong(size + 1));
if (buffer == null) {
return 1005;
} else {
long length;
int result;
length = Integer.toUnsignedLong(size);
long total = 0L;
result = 0;
label39:
if (size != 0) {
ConfigurationFiles.Count read = new ConfigurationFiles.Count();
while (host.read(handle, buffer, total, size - (int)total, read)) {
if (read.value == 0) {
result = 1018;
break label39;
}
total += Integer.toUnsignedLong(read.value);
if (Long.compareUnsigned(total, length) >= 0) {
break label39;
}
}
result = 1009;
}
if (result != 0) {
host.release(buffer);
} else {
if (length >= (long)buffer.length) {
throw new IllegalArgumentException("File service returned unbacked storage");
}
buffer[(int)length] = 0;
output.bytes = buffer;
}
host.close(handle);
return result;
}
}
}
}
public static int writeText(ConfigurationFiles.WidePath path, byte[] text, int replaceByte, int incomingCount, ConfigurationFiles.Host host) {
Object handle = host.open(path.text, 1073741824, 0, (replaceByte & 0xFF) + 1);
if (handle == null) {
return host.lastError() == 80 ? 1012 : 1008;
} else {
long length = (long)JsonRuntime.stringLength(text, 0);
long total = 0L;
ConfigurationFiles.Count written = new ConfigurationFiles.Count();
written.value = incomingCount;
int result = 0;
while (Long.compareUnsigned(total, length) < 0) {
boolean success = host.write(handle, text, total, (int)length - (int)total, written);
total += Integer.toUnsignedLong(written.value);
if (!success) {
result = 1010;
break;
}
}
host.close(handle);
return result;
}
}
public static void releasePath(ConfigurationFiles.WidePath path, ConfigurationFiles.Host host) {
host.release(path.text);
path.text = null;
}
public static int length(char[] text) {
for (int index = 0; index < text.length; index++) {
if (text[index] == 0) {
return index;
}
}
throw new IllegalArgumentException("Wide path lacks terminator");
}
public static char[] wide(String text) {
return Arrays.copyOf(text.toCharArray(), text.length() + 1);
}
public static final class Count {
public int value;
}
public static final class Failure extends IOException {
public final int code;
public Failure(int code) {
super("Configuration file service returned " + code);
this.code = code;
}
}
public interface Host {
char[] environment();
char[] allocateWide(long var1);
byte[] allocateBytes(long var1);
void release(Object var1);
int formatPath(char[] var1, long var2, char[] var4, byte[] var5);
int createDirectory(char[] var1);
int deleteFile(char[] var1);
int lastError();
Object open(char[] var1, int var2, int var3, int var4);
int size(Object var1);
boolean read(Object var1, byte[] var2, long var3, int var5, ConfigurationFiles.Count var6);
boolean write(Object var1, byte[] var2, long var3, int var5, ConfigurationFiles.Count var6);
void close(Object var1);
}
public static final class NioHost implements ConfigurationFiles.Host, AutoCloseable {
private final Path environment;
private final Set<FileChannel> opened = Collections.newSetFromMap(new IdentityHashMap<>());
private int error;
public NioHost(Path environment) {
this.environment = environment;
}
@Override
public char[] environment() {
return this.environment == null ? null : ConfigurationFiles.wide(this.environment.toString());
}
@Override
public char[] allocateWide(long count) {
return count >= 0L && count <= 2147483639L ? new char[(int)count] : null;
}
@Override
public byte[] allocateBytes(long count) {
return count >= 0L && count <= 2147483639L ? new byte[(int)count] : null;
}
@Override
public void release(Object allocation) {
}
@Override
public int formatPath(char[] destination, long capacity, char[] base, byte[] component) {
String result = new String(base, 0, ConfigurationFiles.length(base)) + "\\" + JsonRuntime.utf8CString(component);
if (capacity > 0L && capacity <= (long)destination.length && (long)result.length() < capacity) {
result.getChars(0, result.length(), destination, 0);
destination[result.length()] = 0;
return result.length();
} else {
destination[0] = 0;
return -1;
}
}
private Path path(char[] text) {
return Paths.get(new String(text, 0, ConfigurationFiles.length(text)));
}
@Override
public int lastError() {
return this.error;
}
private void failure(Exception failed) {
this.error = failed instanceof FileAlreadyExistsException ? 80 : (failed instanceof NoSuchFileException ? 2 : 5);
}
@Override
public int createDirectory(char[] path) {
try {
Files.createDirectory(this.path(path));
return 1;
} catch (FileAlreadyExistsException var3) {
this.error = 183;
return 0;
} catch (InvalidPathException | IOException var4) {
this.failure(var4);
return 0;
}
}
@Override
public int deleteFile(char[] path) {
try {
Files.delete(this.path(path));
return 1;
} catch (InvalidPathException | IOException var3) {
this.failure(var3);
return 0;
}
}
@Override
public Object open(char[] path, int access, int sharing, int creation) {
try {
Set<OpenOption> options = new HashSet<>();
options.add(access == Integer.MIN_VALUE ? StandardOpenOption.READ : StandardOpenOption.WRITE);
if (creation == 1) {
options.add(StandardOpenOption.CREATE_NEW);
} else if (creation == 2) {
options.add(StandardOpenOption.CREATE);
options.add(StandardOpenOption.TRUNCATE_EXISTING);
} else if (creation != 3) {
throw new IOException("Unsupported file creation disposition");
}
FileChannel channel = FileChannel.open(this.path(path), options);
this.opened.add(channel);
return channel;
} catch (InvalidPathException | IOException var7) {
this.failure(var7);
return null;
}
}
@Override
public int size(Object handle) {
try {
return (int)((FileChannel)handle).size();
} catch (IOException var3) {
this.failure(var3);
return -1;
}
}
private ByteBuffer buffer(byte[] bytes, long offset, int requested) {
long count = Integer.toUnsignedLong(requested);
if (offset >= 0L && offset <= (long)bytes.length && count <= (long)bytes.length - offset) {
return ByteBuffer.wrap(bytes, (int)offset, (int)count);
} else {
throw new IllegalArgumentException("Unbacked file transfer range");
}
}
@Override
public boolean read(Object handle, byte[] destination, long offset, int requested, ConfigurationFiles.Count count) {
try {
int read = ((FileChannel)handle).read(this.buffer(destination, offset, requested));
count.value = Math.max(read, 0);
return true;
} catch (IOException var8) {
this.failure(var8);
return false;
}
}
@Override
public boolean write(Object handle, byte[] source, long offset, int requested, ConfigurationFiles.Count count) {
try {
count.value = ((FileChannel)handle).write(this.buffer(source, offset, requested));
return true;
} catch (IOException var8) {
this.failure(var8);
return false;
}
}
@Override
public void close(Object handle) {
FileChannel channel = (FileChannel)handle;
try {
channel.close();
} catch (IOException var7) {
this.failure(var7);
} finally {
this.opened.remove(channel);
}
}
@Override
public void close() {
for (FileChannel channel : new ArrayList<>(this.opened)) {
this.close(channel);
}
}
}
public static final class Text {
public byte[] bytes;
}
public static final class WidePath {
public char[] text;
}
}
@@ -0,0 +1,165 @@
package dev.slinky.config;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import dev.slinky.config.json.ConfigJsonParser;
import dev.slinky.config.json.JsonJvmServices;
import dev.slinky.config.json.JsonRuntime;
import java.io.IOException;
import java.util.Map.Entry;
public final class ConfigurationJson {
private ConfigurationJson() {
}
public static JsonObject parse(byte[] bytes) throws IOException {
JsonElement value = parseValue(bytes);
return value.isJsonObject() ? value.getAsJsonObject() : new JsonObject();
}
public static JsonElement parseValue(byte[] bytes) throws IOException {
JsonRuntime.Context context = new JsonJvmServices().context();
JsonRuntime.Node root = ConfigJsonParser.parse_4da70(bytes, context);
if (root == null) {
throw new IOException("Invalid configuration bytes at " + context.errorOffset);
} else {
JsonElement var3;
try {
var3 = value(root);
} finally {
JsonRuntime.deleteTree_4d240(root, context);
}
return var3;
}
}
private static JsonElement value(JsonRuntime.Node node) {
switch (node.type & 0xFF) {
case 1:
return new JsonPrimitive(false);
case 2:
return new JsonPrimitive(true);
case 8:
return new JsonPrimitive(node.valueNumber);
case 16:
return new JsonPrimitive(JsonRuntime.utf8CString(node.valueString));
case 32:
JsonArray array = new JsonArray();
for (JsonRuntime.Node child = node.child; child != null; child = child.next) {
array.add(value(child));
}
return array;
case 64:
JsonObject object = new JsonObject();
for (JsonRuntime.Node child = node.child; child != null && child.name != null; child = child.next) {
String name = JsonRuntime.utf8CString(child.name);
if (!object.has(name)) {
object.add(name, value(child));
}
}
return object;
default:
return JsonNull.INSTANCE;
}
}
public static byte[] print(JsonObject value) throws IOException {
JsonRuntime.Context context = new JsonJvmServices().context();
JsonRuntime.Node root = JsonRuntime.createObject_4f590(context);
if (root == null) {
throw new ConfigurationFiles.Failure(5000);
} else {
byte[] var4;
try {
fill(root, value, context);
byte[] text = JsonRuntime.printCompact_4dbe0(root, context);
if (text == null) {
throw new ConfigurationFiles.Failure(5000);
}
var4 = text;
} finally {
JsonRuntime.deleteTree_4d240(root, context);
}
return var4;
}
}
private static void fill(JsonRuntime.Node node, JsonElement value, JsonRuntime.Context context) throws IOException {
if (value.isJsonObject()) {
for (Entry<String, JsonElement> member : value.getAsJsonObject().entrySet()) {
add(node, JsonRuntime.cString(member.getKey()), member.getValue(), context);
}
} else if (value.isJsonArray()) {
for (JsonElement child : value.getAsJsonArray()) {
add(node, null, child, context);
}
}
}
private static void add(JsonRuntime.Node parent, byte[] name, JsonElement value, JsonRuntime.Context context) throws IOException {
JsonRuntime.Node holder = name == null ? JsonRuntime.createObject_4f590(context) : parent;
byte[] key = name == null ? new byte[]{0} : name;
if (holder == null) {
throw new ConfigurationFiles.Failure(5000);
} else {
JsonRuntime.Node child = null;
try {
if (value.isJsonObject()) {
child = JsonRuntime.writeObject_4f480(holder, key, context);
} else if (value.isJsonArray()) {
child = JsonRuntime.writeArray_4f5d0(holder, key, context);
} else if (value.isJsonPrimitive()) {
JsonPrimitive primitive = value.getAsJsonPrimitive();
if (primitive.isBoolean()) {
child = JsonRuntime.writeBoolean_4ed40(holder, key, primitive.getAsBoolean() ? 1 : 0, context);
} else if (primitive.isNumber()) {
child = JsonRuntime.writeNumber_4eeb0(holder, key, primitive.getAsDouble(), context);
} else {
child = JsonRuntime.writeString_4f080(holder, key, JsonRuntime.cString(primitive.getAsString()), context);
}
} else {
child = JsonRuntime.createObject_4f590(context);
if (child != null) {
child.type = 4;
if (JsonRuntime.attachName_4e5b0(holder, key, child, context) == 0) {
JsonRuntime.deleteTree_4d240(child, context);
child = null;
}
}
}
if (child == null) {
throw new ConfigurationFiles.Failure(5000);
}
if (name == null) {
holder.child = null;
context.allocator.release(child.name);
child.name = null;
child.next = child.previous = null;
if (JsonRuntime.append_4e560(parent, child) == 0) {
JsonRuntime.deleteTree_4d240(child, context);
throw new ConfigurationFiles.Failure(5000);
}
}
fill(child, value, context);
} finally {
if (name == null) {
JsonRuntime.deleteTree_4d240(holder, context);
}
}
}
}
}
@@ -0,0 +1,23 @@
package dev.slinky.config;
public final class ConfigurationSession {
private volatile String currentProfile;
private volatile boolean pending;
public String currentProfile() {
return this.currentProfile;
}
public boolean pending() {
return this.pending;
}
public void controlChanged() {
this.pending = true;
}
public void profileApplied(String name) {
this.currentProfile = name;
this.pending = false;
}
}
@@ -0,0 +1,61 @@
package dev.slinky.config;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import dev.slinky.inject.Platform;
import dev.slinky.module.Module;
import dev.slinky.module.ModuleManager;
import dev.slinky.setting.Setting;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
public final class InitialSettings {
private static final JsonObject MODULES = read();
private static JsonObject read() {
InputStream stream = Platform.resource("/assets/slinkysemantic/InitialSettings.json");
if (stream == null) {
throw new IllegalStateException("Missing initial configuration");
} else {
try (Reader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) {
return new JsonParser().parse(reader).getAsJsonObject().getAsJsonObject("modules");
} catch (IOException var15) {
throw new IllegalStateException(var15);
}
}
}
public static int applyMissing(ModuleManager manager) {
int applied = 0;
for (Module module : manager.all()) {
JsonObject values = MODULES.getAsJsonObject(module.getName());
if (values != null) {
for (Setting<?> setting : module.getSettings()) {
if (!setting.isConfigured()) {
JsonObject row = values.getAsJsonObject(setting.getKey());
if (row == null) {
throw new IllegalStateException("Missing initial value: " + module.getName() + "." + setting.getKey());
}
SettingCodec.decode(setting, row.get("value"));
if (!setting.isConfigured()) {
throw new IllegalStateException("Invalid initial value: " + module.getName() + "." + setting.getKey());
}
setting.saveCurrent();
applied++;
}
}
}
}
return applied;
}
private InitialSettings() {
}
}
Loaded 100 of 353 files, more files were not shown because too many files have changed in this diff. Show more