added files.
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
+17
@@ -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
Reference in new issue
Block a user