added files.
This commit is contained in:
commit
e908ab2355
353 files changed
+130515
No files matched your search
+34
@@ -0,0 +1,34 @@
|
||||
# CMake build directories
|
||||
build/
|
||||
bin/
|
||||
lib/
|
||||
out/
|
||||
CMakeSettings.json
|
||||
|
||||
# Visual Studio files
|
||||
.vs/
|
||||
*.user
|
||||
*.suo
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.VC.db
|
||||
*.VC.opendb
|
||||
|
||||
# Gradle and JVM files
|
||||
.gradle/
|
||||
**/build/
|
||||
*.class
|
||||
*.log
|
||||
|
||||
# Eclipse / IDE metadata
|
||||
.settings/
|
||||
.classpath
|
||||
.project
|
||||
|
||||
# Temporary / backup files
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~
|
||||
@@ -0,0 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(Slinky VERSION 1.0.0 LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
|
||||
if(MSVC)
|
||||
add_compile_options(/W3 /utf-8 /MP)
|
||||
endif()
|
||||
|
||||
add_subdirectory(Slinky)
|
||||
add_subdirectory(SlinkyNative)
|
||||
|
||||
add_dependencies(Slinky SlinkyNative)
|
||||
@@ -0,0 +1,61 @@
|
||||
<p align="center">
|
||||
<img src="assets/ss.png" alt="Open Slinky Banner" width="350" />
|
||||
<br>
|
||||
<font size="6"><b>Open Slinky</b></font>
|
||||
<br>
|
||||
<font size="3">by Skid Solutions</font>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/jak.jpg" alt="jak" height="150" />
|
||||
<img src="assets/hack.jpg" alt="hack" height="150" />
|
||||
<img src="assets/deobf2.png" alt="deobf2" height="150" />
|
||||
<img src="assets/deobf.jpg" alt="deobf" height="150" />
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## How to build
|
||||
|
||||
### Prerequisites
|
||||
- **C++ Compiler**: Visual Studio 2019 / 2022 (MSVC with C++17 support)
|
||||
- **CMake**: Version 3.15 or newer
|
||||
- **Java Development Kit**:
|
||||
- **JDK 17**: Required by Gradle to compile `SlinkyClient`
|
||||
- **JDK 8**: Runtime environment for Minecraft 1.8.9
|
||||
|
||||
---
|
||||
|
||||
### 1. Building the Loader (`Slinky.exe` & `SlinkyNative.dll`)
|
||||
|
||||
The loader and native injector DLL are configured as a unified CMake project:
|
||||
|
||||
```powershell
|
||||
cmake -B build -A x64
|
||||
|
||||
cmake --build build --config Release
|
||||
```
|
||||
|
||||
Output binaries will be placed in `build/bin/Release/`:
|
||||
- `Slinky.exe`
|
||||
- `SlinkyNative.dll`
|
||||
|
||||
---
|
||||
|
||||
### 2. Building the Client (`SlinkyClient`)
|
||||
|
||||
The client is a standalone Gradle project using [Unimined](https://github.com/unimined/Unimined) to resolve Minecraft 1.8.9 dependencies and MCP mappings automatically:
|
||||
|
||||
```powershell
|
||||
cd SlinkyClient
|
||||
|
||||
$env:JAVA_HOME = "C:\Program Files\Java\jdk-17"
|
||||
|
||||
.\gradlew.bat clean build
|
||||
```
|
||||
|
||||
Generated `SlinkyClient/build/libs/`:
|
||||
- `Slinky-0.5.0-settings.jar`
|
||||
- `Slinky-0.5.0-settings-sources.jar`
|
||||
@@ -0,0 +1,22 @@
|
||||
add_executable(Slinky
|
||||
src/Main.cpp
|
||||
src/Injector.cpp
|
||||
src/ProcessUtil.cpp
|
||||
src/ConsoleUI.cpp
|
||||
Slinky.rc
|
||||
)
|
||||
|
||||
target_include_directories(Slinky PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_compile_definitions(Slinky PRIVATE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
WIN32_LEAN_AND_MEAN
|
||||
)
|
||||
|
||||
set_target_properties(Slinky PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
OUTPUT_NAME "Slinky"
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "include/Resource.h"
|
||||
|
||||
#ifdef EMBED_BUNDLED_DLL
|
||||
IDR_BUNDLED_DLL RCDATA "SlinkyNative.dll"
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
|
||||
namespace Slinky {
|
||||
|
||||
class ConsoleUI {
|
||||
public:
|
||||
static void Initialize();
|
||||
static void Shutdown();
|
||||
|
||||
static void PrintBanner();
|
||||
static void StartAnimation();
|
||||
static void StopAnimation();
|
||||
|
||||
static void SetStatusText(const char* text);
|
||||
static void PrintError(const char* action, DWORD errorCode);
|
||||
|
||||
private:
|
||||
static DWORD WINAPI AnimationThread(LPVOID lpParam);
|
||||
|
||||
static CRITICAL_SECTION s_CriticalSection;
|
||||
static char s_StatusText[512];
|
||||
static std::atomic<bool> s_Running;
|
||||
static HANDLE s_hAnimationThread;
|
||||
static DWORD s_OriginalConsoleMode;
|
||||
static HANDLE s_hConsole;
|
||||
};
|
||||
|
||||
} // namespace Slinky
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
|
||||
namespace Slinky {
|
||||
|
||||
class Injector {
|
||||
public:
|
||||
static bool ExtractBundledDll(std::wstring& outDllPath, std::wstring& outLogPath);
|
||||
static bool InjectDll(DWORD pid, const std::wstring& dllPath);
|
||||
static bool MonitorStartup(DWORD pid, const std::wstring& logPath);
|
||||
};
|
||||
|
||||
} // namespace Slinky
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slinky {
|
||||
|
||||
struct TargetWindow {
|
||||
HWND hWnd = nullptr;
|
||||
DWORD pid = 0;
|
||||
std::wstring title;
|
||||
};
|
||||
|
||||
class ProcessUtil {
|
||||
public:
|
||||
static std::vector<DWORD> GetJavaProcessIds();
|
||||
static std::vector<TargetWindow> FindMinecraftWindows();
|
||||
static bool IsModuleLoaded(DWORD pid, const wchar_t* moduleName);
|
||||
static bool WaitForModuleLoad(DWORD pid, const wchar_t* moduleName, DWORD timeoutMs);
|
||||
};
|
||||
|
||||
} // namespace Slinky
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define IDR_BUNDLED_DLL 200
|
||||
@@ -0,0 +1,144 @@
|
||||
#include "ConsoleUI.hpp"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace Slinky {
|
||||
|
||||
CRITICAL_SECTION ConsoleUI::s_CriticalSection;
|
||||
char ConsoleUI::s_StatusText[512] = { 0 };
|
||||
std::atomic<bool> ConsoleUI::s_Running(false);
|
||||
HANDLE ConsoleUI::s_hAnimationThread = nullptr;
|
||||
DWORD ConsoleUI::s_OriginalConsoleMode = 0;
|
||||
HANDLE ConsoleUI::s_hConsole = INVALID_HANDLE_VALUE;
|
||||
|
||||
void ConsoleUI::PrintBanner() {
|
||||
const char* bannerLines[] = {
|
||||
" _____ __ _ __",
|
||||
" / ___// /__ (_)___/ /",
|
||||
" \\__ \\/ //_// / __ / ",
|
||||
" ___/ / ,< / / /_/ / ",
|
||||
"/____/_/|_|/_/\\__,_/ ",
|
||||
" _____ __ __ _ ",
|
||||
" / ___/____ / /_ __/ /_(_)___ ____ _____",
|
||||
" \\__ \\/ __ \\/ / / / / __/ / __ \\/ __ \\/ ___/",
|
||||
" ___/ / /_/ / / /_/ / /_/ / /_/ / / / (__ ) ",
|
||||
"/____/\\____/_/\\__,_/\\__/_/\\____/_/ /_/____/ "
|
||||
};
|
||||
|
||||
for (size_t lineIdx = 0; lineIdx < 10; ++lineIdx) {
|
||||
const char* line = bannerLines[lineIdx];
|
||||
size_t blockWidth = (lineIdx < 5) ? 24 : 45;
|
||||
size_t len = strlen(line);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
float ratio = (float)i / (float)(blockWidth - 1);
|
||||
if (ratio > 1.0f) ratio = 1.0f;
|
||||
int val = (int)(255 - ratio * 200.0f);
|
||||
if (val < 45) val = 45;
|
||||
printf("\x1b[38;2;%d;%d;%dm%c", val, val, val, line[i]);
|
||||
}
|
||||
printf("\x1b[0m\n");
|
||||
}
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void ConsoleUI::Initialize() {
|
||||
InitializeCriticalSection(&s_CriticalSection);
|
||||
s_hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
||||
if (s_hConsole != INVALID_HANDLE_VALUE) {
|
||||
GetConsoleMode(s_hConsole, &s_OriginalConsoleMode);
|
||||
SetConsoleMode(s_hConsole, s_OriginalConsoleMode | 0x0004);
|
||||
}
|
||||
|
||||
SetConsoleTitleW(L"Slinky");
|
||||
strcpy_s(s_StatusText, "SCANNING");
|
||||
|
||||
PrintBanner();
|
||||
}
|
||||
|
||||
void ConsoleUI::Shutdown() {
|
||||
StopAnimation();
|
||||
if (s_hConsole != INVALID_HANDLE_VALUE && s_OriginalConsoleMode != 0) {
|
||||
SetConsoleMode(s_hConsole, s_OriginalConsoleMode);
|
||||
}
|
||||
DeleteCriticalSection(&s_CriticalSection);
|
||||
}
|
||||
|
||||
void ConsoleUI::SetStatusText(const char* text) {
|
||||
EnterCriticalSection(&s_CriticalSection);
|
||||
strncpy_s(s_StatusText, text, _TRUNCATE);
|
||||
LeaveCriticalSection(&s_CriticalSection);
|
||||
}
|
||||
|
||||
void ConsoleUI::PrintError(const char* action, DWORD errorCode) {
|
||||
wchar_t* msgBuf = nullptr;
|
||||
FormatMessageW(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPWSTR)&msgBuf, 0, nullptr);
|
||||
|
||||
char errorStr[512] = "unknown";
|
||||
if (msgBuf) {
|
||||
WideCharToMultiByte(CP_UTF8, 0, msgBuf, -1, errorStr, 512, nullptr, nullptr);
|
||||
LocalFree(msgBuf);
|
||||
|
||||
size_t len = strlen(errorStr);
|
||||
while (len > 0 && (errorStr[len - 1] == '\r' || errorStr[len - 1] == '\n')) {
|
||||
errorStr[--len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
EnterCriticalSection(&s_CriticalSection);
|
||||
printf("\n[!] %s failed with error %lu: %s\n", action, errorCode, errorStr);
|
||||
fflush(stdout);
|
||||
LeaveCriticalSection(&s_CriticalSection);
|
||||
}
|
||||
|
||||
void ConsoleUI::StartAnimation() {
|
||||
if (s_Running) return;
|
||||
s_Running = true;
|
||||
s_hAnimationThread = CreateThread(nullptr, 0, AnimationThread, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
void ConsoleUI::StopAnimation() {
|
||||
if (!s_Running) return;
|
||||
s_Running = false;
|
||||
if (s_hAnimationThread) {
|
||||
WaitForSingleObject(s_hAnimationThread, 1000);
|
||||
CloseHandle(s_hAnimationThread);
|
||||
s_hAnimationThread = nullptr;
|
||||
}
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
DWORD WINAPI ConsoleUI::AnimationThread(LPVOID lpParam) {
|
||||
const char* spinner[] = { "-", "\\", "|", "/" };
|
||||
int frame = 0;
|
||||
|
||||
while (s_Running) {
|
||||
char currentStatus[512] = { 0 };
|
||||
EnterCriticalSection(&s_CriticalSection);
|
||||
strncpy_s(currentStatus, s_StatusText, _TRUNCATE);
|
||||
LeaveCriticalSection(&s_CriticalSection);
|
||||
|
||||
printf("\r\x1b[2K [%s] %s", spinner[frame % 4], currentStatus);
|
||||
fflush(stdout);
|
||||
|
||||
frame++;
|
||||
Sleep(160);
|
||||
}
|
||||
|
||||
char finalStatus[512] = { 0 };
|
||||
EnterCriticalSection(&s_CriticalSection);
|
||||
strncpy_s(finalStatus, s_StatusText, _TRUNCATE);
|
||||
LeaveCriticalSection(&s_CriticalSection);
|
||||
|
||||
printf("\r\x1b[2K [+] %s\n", finalStatus);
|
||||
fflush(stdout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Slinky
|
||||
@@ -0,0 +1,201 @@
|
||||
#include "Injector.hpp"
|
||||
#include "ProcessUtil.hpp"
|
||||
#include "ConsoleUI.hpp"
|
||||
#include "Resource.h"
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
namespace Slinky {
|
||||
|
||||
bool Injector::ExtractBundledDll(std::wstring& outDllPath, std::wstring& outLogPath) {
|
||||
wchar_t tempPath[MAX_PATH] = { 0 };
|
||||
if (!GetTempPathW(MAX_PATH, tempPath)) {
|
||||
ConsoleUI::PrintError("Finding temp folder", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
wchar_t slinkyTempDir[MAX_PATH] = { 0 };
|
||||
swprintf_s(slinkyTempDir, L"%lsslinky-%lu", tempPath, GetCurrentProcessId());
|
||||
|
||||
if (!CreateDirectoryW(slinkyTempDir, nullptr) && GetLastError() != ERROR_ALREADY_EXISTS) {
|
||||
ConsoleUI::PrintError("Creating temp folder", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
outDllPath = std::wstring(slinkyTempDir) + L"\\SlinkyNative.dll";
|
||||
outLogPath = std::wstring(slinkyTempDir) + L"\\slinky-native.log";
|
||||
|
||||
DeleteFileW(outLogPath.c_str());
|
||||
|
||||
HRSRC hRes = FindResourceW(nullptr, MAKEINTRESOURCEW(IDR_BUNDLED_DLL), (LPCWSTR)10);
|
||||
if (hRes) {
|
||||
DWORD resSize = SizeofResource(nullptr, hRes);
|
||||
HGLOBAL hLoad = LoadResource(nullptr, hRes);
|
||||
if (hLoad && resSize > 0) {
|
||||
const void* pData = LockResource(hLoad);
|
||||
if (pData) {
|
||||
HANDLE hFile = CreateFileW(outDllPath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
DWORD written = 0;
|
||||
WriteFile(hFile, pData, resSize, &written, nullptr);
|
||||
CloseHandle(hFile);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wchar_t exePath[MAX_PATH] = { 0 };
|
||||
if (GetModuleFileNameW(nullptr, exePath, MAX_PATH) > 0) {
|
||||
wchar_t* lastSlash = wcsrchr(exePath, L'\\');
|
||||
if (lastSlash) {
|
||||
*lastSlash = L'\0';
|
||||
std::wstring localDll = std::wstring(exePath) + L"\\SlinkyNative.dll";
|
||||
if (GetFileAttributesW(localDll.c_str()) != INVALID_FILE_ATTRIBUTES) {
|
||||
if (CopyFileW(localDll.c_str(), outDllPath.c_str(), FALSE)) {
|
||||
return true;
|
||||
}
|
||||
outDllPath = localDll;
|
||||
outLogPath = std::wstring(exePath) + L"\\slinky-native.log";
|
||||
DeleteFileW(outLogPath.c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleUI::SetStatusText("Could not extract bundled DLL.");
|
||||
ConsoleUI::PrintError("Reading bundled DLL", ERROR_FILE_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Injector::InjectDll(DWORD pid, const std::wstring& dllPath) {
|
||||
if (ProcessUtil::IsModuleLoaded(pid, L"SlinkyNative.dll")) {
|
||||
ConsoleUI::SetStatusText("DLL already loaded.");
|
||||
return true;
|
||||
}
|
||||
|
||||
ConsoleUI::SetStatusText("Opening game process");
|
||||
HANDLE hProcess = OpenProcess(
|
||||
PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION,
|
||||
FALSE, pid);
|
||||
|
||||
if (!hProcess) {
|
||||
ConsoleUI::PrintError("OpenProcess", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
ConsoleUI::SetStatusText("Allocating DLL path in game");
|
||||
SIZE_T pathSizeBytes = (dllPath.length() + 1) * sizeof(wchar_t);
|
||||
LPVOID pRemotePath = VirtualAllocEx(hProcess, nullptr, pathSizeBytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
|
||||
if (!pRemotePath) {
|
||||
ConsoleUI::PrintError("Allocating DLL path in game", GetLastError());
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
|
||||
ConsoleUI::SetStatusText("Copying DLL path into game");
|
||||
if (!WriteProcessMemory(hProcess, pRemotePath, dllPath.c_str(), pathSizeBytes, nullptr)) {
|
||||
ConsoleUI::PrintError("Copying DLL path into game", GetLastError());
|
||||
VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
|
||||
ConsoleUI::SetStatusText("Finding Windows DLL loader");
|
||||
HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll");
|
||||
LPTHREAD_START_ROUTINE pLoadLibraryW = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, "LoadLibraryW");
|
||||
|
||||
if (!pLoadLibraryW) {
|
||||
ConsoleUI::PrintError("Finding Windows DLL loader", GetLastError());
|
||||
VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
|
||||
ConsoleUI::SetStatusText("Starting DLL loader in game");
|
||||
HANDLE hThread = CreateRemoteThread(hProcess, nullptr, 0, pLoadLibraryW, pRemotePath, 0, nullptr);
|
||||
if (!hThread) {
|
||||
ConsoleUI::PrintError("Starting DLL loader in game", GetLastError());
|
||||
VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
|
||||
ConsoleUI::SetStatusText("Reading DLL loader result");
|
||||
DWORD waitRes = WaitForSingleObject(hThread, 30000);
|
||||
if (waitRes != WAIT_OBJECT_0) {
|
||||
ConsoleUI::SetStatusText("Game did not finish loading the DLL.");
|
||||
CloseHandle(hThread);
|
||||
VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD dwExitCode = 0;
|
||||
GetExitCodeThread(hThread, &dwExitCode);
|
||||
CloseHandle(hThread);
|
||||
VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
|
||||
CloseHandle(hProcess);
|
||||
|
||||
if (!ProcessUtil::WaitForModuleLoad(pid, L"SlinkyNative.dll", 5000)) {
|
||||
ConsoleUI::SetStatusText("DLL not in game after load attempt.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ConsoleUI::SetStatusText("Injected successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Injector::MonitorStartup(DWORD pid, const std::wstring& logPath) {
|
||||
HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, pid);
|
||||
|
||||
ConsoleUI::SetStatusText("Still loading...");
|
||||
DWORD startTime = GetTickCount();
|
||||
const DWORD timeoutMs = 30000;
|
||||
|
||||
while (GetTickCount() - startTime < timeoutMs) {
|
||||
if (hProcess && WaitForSingleObject(hProcess, 0) == WAIT_OBJECT_0) {
|
||||
ConsoleUI::SetStatusText("Game process closed before startup finished.");
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
|
||||
FILE* fp = nullptr;
|
||||
if (_wfopen_s(&fp, logPath.c_str(), L"r, ccs=UTF-8") == 0 && fp) {
|
||||
wchar_t line[1024];
|
||||
while (fgetws(line, 1024, fp)) {
|
||||
if (wcsstr(line, L"FINAL STATUS: RUNNING")) {
|
||||
ConsoleUI::SetStatusText("Press Right Shift in Minecraft.");
|
||||
fclose(fp);
|
||||
if (hProcess) CloseHandle(hProcess);
|
||||
return true;
|
||||
} else if (wcsstr(line, L"FINAL STATUS: RELOADED")) {
|
||||
ConsoleUI::SetStatusText("Slinky reloaded. Press Right Shift.");
|
||||
fclose(fp);
|
||||
if (hProcess) CloseHandle(hProcess);
|
||||
return true;
|
||||
} else if (wcsstr(line, L"FINAL STATUS: ALREADY_RUNNING")) {
|
||||
ConsoleUI::SetStatusText("Already running. Unload from menu first.");
|
||||
fclose(fp);
|
||||
if (hProcess) CloseHandle(hProcess);
|
||||
return false;
|
||||
} else if (wcsstr(line, L"FINAL STATUS: FAILED")) {
|
||||
ConsoleUI::SetStatusText("Slinky could not start.");
|
||||
fclose(fp);
|
||||
if (hProcess) CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
Sleep(150);
|
||||
}
|
||||
|
||||
if (hProcess) CloseHandle(hProcess);
|
||||
ConsoleUI::SetStatusText("DLL loaded but Slinky never confirmed startup.");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Slinky
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <windows.h>
|
||||
#include "ConsoleUI.hpp"
|
||||
#include "Injector.hpp"
|
||||
#include "ProcessUtil.hpp"
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
using namespace Slinky;
|
||||
|
||||
int main() {
|
||||
ConsoleUI::Initialize();
|
||||
ConsoleUI::StartAnimation();
|
||||
|
||||
ConsoleUI::SetStatusText("Finding bundled DLL");
|
||||
std::wstring dllPath;
|
||||
std::wstring logPath;
|
||||
|
||||
if (!Injector::ExtractBundledDll(dllPath, logPath)) {
|
||||
Sleep(2500);
|
||||
ConsoleUI::StopAnimation();
|
||||
ConsoleUI::Shutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
ConsoleUI::SetStatusText("SCANNING");
|
||||
|
||||
DWORD startWait = GetTickCount();
|
||||
const DWORD timeoutWait = 180000;
|
||||
std::vector<TargetWindow> windows;
|
||||
int dotCount = 0;
|
||||
|
||||
while (GetTickCount() - startWait < timeoutWait) {
|
||||
windows = ProcessUtil::FindMinecraftWindows();
|
||||
if (!windows.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const char* dots[] = { "", ".", "..", "..." };
|
||||
char waitMsg[64];
|
||||
sprintf_s(waitMsg, "Waiting for Minecraft%s", dots[dotCount % 4]);
|
||||
ConsoleUI::SetStatusText(waitMsg);
|
||||
dotCount++;
|
||||
|
||||
Sleep(500);
|
||||
}
|
||||
|
||||
if (windows.empty()) {
|
||||
ConsoleUI::SetStatusText("Minecraft not found after 3 minutes.");
|
||||
Sleep(3000);
|
||||
ConsoleUI::StopAnimation();
|
||||
ConsoleUI::Shutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
TargetWindow target = windows[0];
|
||||
|
||||
char foundMsg[256];
|
||||
char titleUtf8[256] = { 0 };
|
||||
WideCharToMultiByte(CP_UTF8, 0, target.title.c_str(), -1, titleUtf8, 256, nullptr, nullptr);
|
||||
sprintf_s(foundMsg, "Found Minecraft %s", titleUtf8);
|
||||
ConsoleUI::SetStatusText(foundMsg);
|
||||
|
||||
Sleep(500);
|
||||
|
||||
ConsoleUI::SetStatusText("INJECTING ...");
|
||||
if (!Injector::InjectDll(target.pid, dllPath)) {
|
||||
Sleep(3000);
|
||||
ConsoleUI::StopAnimation();
|
||||
ConsoleUI::Shutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool success = Injector::MonitorStartup(target.pid, logPath);
|
||||
|
||||
Sleep(3000);
|
||||
ConsoleUI::StopAnimation();
|
||||
ConsoleUI::Shutdown();
|
||||
|
||||
return success ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
#include "ProcessUtil.hpp"
|
||||
#include <tlhelp32.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace Slinky {
|
||||
|
||||
std::vector<DWORD> ProcessUtil::GetJavaProcessIds() {
|
||||
std::vector<DWORD> pids;
|
||||
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hSnap == INVALID_HANDLE_VALUE) return pids;
|
||||
|
||||
PROCESSENTRY32W pe;
|
||||
pe.dwSize = sizeof(pe);
|
||||
|
||||
if (Process32FirstW(hSnap, &pe)) {
|
||||
do {
|
||||
if (_wcsicmp(pe.szExeFile, L"javaw.exe") == 0 ||
|
||||
_wcsicmp(pe.szExeFile, L"java.exe") == 0)
|
||||
{
|
||||
pids.push_back(pe.th32ProcessID);
|
||||
}
|
||||
} while (Process32NextW(hSnap, &pe));
|
||||
}
|
||||
|
||||
CloseHandle(hSnap);
|
||||
return pids;
|
||||
}
|
||||
|
||||
struct EnumData {
|
||||
std::vector<DWORD> javaPids;
|
||||
std::vector<TargetWindow> windows;
|
||||
};
|
||||
|
||||
static BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam) {
|
||||
if (!IsWindowVisible(hWnd)) return TRUE;
|
||||
if (GetWindowTextLengthW(hWnd) == 0) return TRUE;
|
||||
|
||||
DWORD pid = 0;
|
||||
GetWindowThreadProcessId(hWnd, &pid);
|
||||
|
||||
EnumData* data = (EnumData*)lParam;
|
||||
if (std::find(data->javaPids.begin(), data->javaPids.end(), pid) == data->javaPids.end()) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
wchar_t titleBuf[512] = { 0 };
|
||||
if (GetWindowTextW(hWnd, titleBuf, 512) > 0) {
|
||||
TargetWindow tw;
|
||||
tw.hWnd = hWnd;
|
||||
tw.pid = pid;
|
||||
tw.title = titleBuf;
|
||||
data->windows.push_back(tw);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
std::vector<TargetWindow> ProcessUtil::FindMinecraftWindows() {
|
||||
EnumData data;
|
||||
data.javaPids = GetJavaProcessIds();
|
||||
if (data.javaPids.empty()) return {};
|
||||
|
||||
EnumWindows(EnumWindowsProc, (LPARAM)&data);
|
||||
return data.windows;
|
||||
}
|
||||
|
||||
bool ProcessUtil::IsModuleLoaded(DWORD pid, const wchar_t* moduleName) {
|
||||
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
|
||||
if (hSnap == INVALID_HANDLE_VALUE) return false;
|
||||
|
||||
MODULEENTRY32W me;
|
||||
me.dwSize = sizeof(me);
|
||||
|
||||
bool found = false;
|
||||
if (Module32FirstW(hSnap, &me)) {
|
||||
do {
|
||||
if (_wcsicmp(me.szModule, moduleName) == 0 ||
|
||||
wcsstr(me.szExePath, moduleName) != nullptr)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
} while (Module32NextW(hSnap, &me));
|
||||
}
|
||||
|
||||
CloseHandle(hSnap);
|
||||
return found;
|
||||
}
|
||||
|
||||
bool ProcessUtil::WaitForModuleLoad(DWORD pid, const wchar_t* moduleName, DWORD timeoutMs) {
|
||||
DWORD elapsed = 0;
|
||||
while (elapsed < timeoutMs) {
|
||||
if (IsModuleLoaded(pid, moduleName)) return true;
|
||||
Sleep(50);
|
||||
elapsed += 50;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Slinky
|
||||
@@ -0,0 +1,14 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.java]
|
||||
indent_style = space
|
||||
indent_size = 3
|
||||
|
||||
[*.gradle]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
@@ -0,0 +1,9 @@
|
||||
* text=auto
|
||||
*.java text eol=lf
|
||||
*.gradle text eol=lf
|
||||
*.md text eol=lf
|
||||
gradlew text eol=lf
|
||||
gradlew.bat text eol=crlf
|
||||
*.jar binary
|
||||
*.ttf binary
|
||||
*.bin binary
|
||||
@@ -0,0 +1,14 @@
|
||||
.gradle/
|
||||
.unimined/
|
||||
build/
|
||||
out/
|
||||
run/
|
||||
.idea/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
bin/
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
.vscode/
|
||||
@@ -0,0 +1,31 @@
|
||||
# Slinky
|
||||
|
||||
Open this folder as a Gradle project. Set the project's SDK and **Gradle JVM to JDK 17**, and use the included Gradle wrapper. [Unimined](https://github.com/unimined/Unimined/tree/lts/1.4) resolves the Forge 1.8.9 SDK and MCP stable 22 mappings automatically; no local Minecraft jar or hand-written IDE classpath is needed.
|
||||
|
||||
Build and verify on Windows:
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat clean build
|
||||
```
|
||||
|
||||
On macOS/Linux, run `bash ./gradlew clean build`. Gradle's daemon and compiler are pinned to JDK 17; the build emits Java 8 compatible bytecode for Minecraft 1.8.9. `build` includes a headless smoke check for module registration, configuration JSON, and injected class references. Run it separately with `gradlew.bat smokeCheck`.
|
||||
|
||||
The named development jar and sources jar are written to `build/libs/`. This is an injection payload, whose entry point is `dev.slinky.inject.Bootstrap.start(namespace, payloadPath)`. The native injector is not included; the jar is not an automatically loaded Forge mod. Runtime mappings remain in `mappings/srg.txt` and `mappings/official.txt`.
|
||||
|
||||
Source packages follow their responsibilities:
|
||||
|
||||
- `module`: registry and lifecycle; implementations in `combat`, `movement`, `render`, `world`, and `utility`; catalog metadata in `catalog`. Implementation groups follow the catalog's GUI categories, with source categories for the two source-only modules.
|
||||
- `client.input`, `client.event`, `client.command`: input routing, event bridge, and commands.
|
||||
- `client.gui`: screens and widgets, with `settings` and `profile` subpackages.
|
||||
- `client.render`: shared drawing operations, with `canvas`, `font`, `hook`, `text`, and `world` subpackages.
|
||||
- `client.action`, `client.inventory`, `client.packet`, `client.placement`, `client.rotation`, `client.target`: gameplay services.
|
||||
- `config`: persistence and module serialization; the JSON engine is in `config.json`.
|
||||
- `inject`: bootstrap, reflection, runtime mappings, and bytecode transformation.
|
||||
- `setting`: setting types. `shaded.asm`: bundled bytecode library.
|
||||
|
||||
Recovered arithmetic, configuration keys, resource paths, and module IDs are preserved. `SOURCE-REPAIRS.txt` and `STRUCTURAL-COMPARISON.json` describe the original source recovery before this reorganization.
|
||||
|
||||
Srced by 1113716173461266492(brboy) & 1479899069655547956(xMqrO)
|
||||
<img width="400" height="358" alt="nothing-ever-happens-nothing" src="https://github.com/user-attachments/assets/7bdf5862-cd3a-48ab-871d-d80e3103d582" />
|
||||
|
||||
https://discord.gg/XzQHeUFcY9
|
||||
@@ -0,0 +1,69 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'idea'
|
||||
id 'eclipse'
|
||||
id 'xyz.wagyourtail.unimined' version '1.4.1'
|
||||
}
|
||||
|
||||
group = 'dev.slinky'
|
||||
version = '0.5.0-settings'
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(17)
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
unimined.minecraft {
|
||||
version '1.8.9'
|
||||
mappings {
|
||||
searge()
|
||||
mcp('stable', '22-1.8.9')
|
||||
}
|
||||
minecraftForge {
|
||||
loader '11.15.1.2318-1.8.9'
|
||||
}
|
||||
defaultRemapJar = false
|
||||
runs.off = true
|
||||
}
|
||||
|
||||
configurations.compileOnly {
|
||||
extendsFrom configurations.minecraft, configurations.minecraftLibraries
|
||||
transitive = false
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
options.release = 8
|
||||
}
|
||||
|
||||
idea.module {
|
||||
downloadSources = true
|
||||
}
|
||||
|
||||
eclipse {
|
||||
jdt.javaRuntimeName = 'JavaSE-17'
|
||||
classpath.downloadSources = true
|
||||
}
|
||||
|
||||
sourceSets.test {
|
||||
compileClasspath += sourceSets.main.compileClasspath
|
||||
runtimeClasspath += sourceSets.main.runtimeClasspath
|
||||
}
|
||||
|
||||
tasks.register('smokeCheck', JavaExec) {
|
||||
group = 'verification'
|
||||
description = 'Checks module registration, configuration JSON, and injected class references.'
|
||||
dependsOn testClasses
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
mainClass = 'dev.slinky.ProjectSmokeTest'
|
||||
javaLauncher = javaToolchains.launcherFor(java.toolchain)
|
||||
enableAssertions = true
|
||||
}
|
||||
|
||||
check.dependsOn smokeCheck
|
||||
@@ -0,0 +1 @@
|
||||
org.gradle.jvmargs=-Xmx2G -Dfile.encoding=UTF-8
|
||||
@@ -0,0 +1,2 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainVersion=17
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
|
||||
Vendored
+252
@@ -0,0 +1,252 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,10 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven { url = 'https://maven.wagyourtail.xyz/releases' }
|
||||
maven { url = 'https://maven.fabricmc.net' }
|
||||
maven { url = 'https://maven.minecraftforge.net' }
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = 'Slinky'
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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