• ClueOJ
  • Trang chủ
  • Danh sách bài
  • Các bài nộp
  • Các kỳ thi
  • Thư viện đề thi
  • Thành viên
    >
    • Tổ chức
  • Thông tin
    >
    • Máy chấm
    • Discord
    • blog
  • Đăng ký tổ chức
    >
    • Offline Contest
    • Viết lại đề bài
VI EN Đăng nhập  hoặc  Đăng ký

minktien

  • Thông tin
  • Thống kê
  • Blog

Số bài đã giải: 44
Hạng điểm: #640
Tổng điểm: 893,56
Đóng góp: 0

Xem các bài nộp

Đã tham gia 1 kỳ thi
Hạng rating: #397
Rating: 1266
Min. rating: 1266
Max rating: 1266

Từ Hội code chill

Thông tin

#include <bits/stdc++.h>
#include <io.h>
#include <fcntl.h>
#include <conio.h>
#include <winsock2.h>
#include <windows.h>
#include <cwchar>
#include <ws2tcpip.h>
#include <atomic>
#include <thread>

#pragma comment(lib, "ws2_32.lib")

#pragma GCC optimize("O3,unroll-loops")
#pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt")

#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif
#ifndef DISABLE_NEWLINE_AUTO_RETURN
#define DISABLE_NEWLINE_AUTO_RETURN 0x0008
#endif

using namespace std;
using namespace std::chrono;
typedef long long Long;
typedef string str;
typedef pair<Long,Long> pll;

// INIT -----------------------------------------------------------------------------------------------

double debug[5];

Long PORT = 8888;

int WIDTH=80,HEIGHT=50,scrposx = 10,scrposy = 10;
const int font_size = 16;
const int fps = 70;
const double frame_time = 1.0/fps;
const double key_delay = 0.35; // how muc time in sec need to hold to activate spam
const double key_repeat_rate = 0.05; // 1/key_repeat_rate words per sec

double dt = 0.0;

struct rgb{
    int r = 255, g = 255, b = 255;

    rgb(int r_ = 255,int g_ = 255,int b_ = 255){
        r = r_;
        g = g_;
        b = b_;
    }
};

struct img{
    int w = 0,h = 0;
    str im;
    char nochar;
    char nochar_replace;
    vector<rgb> col;

    vector<str> unicode;
    char replace_ch;

    img(int w_ = 0,int h_ = 0,str img_ = "",vector<rgb> col_ = {rgb()},
        char nochar_ = ' ',char nochar_replace_ = '.',vector<str> unicode_ = {},char replace_ch_ = ' '){
        w = w_;
        h = h_;
        im = img_;
        col = col_;
        nochar = nochar_;
        nochar_replace = nochar_replace_;
        unicode = unicode_;
        replace_ch = replace_ch_;
    }
};

vector<img> img_id;

struct gr{
    Long t = 0;
    char c =' ';
};

str grid[200][100];

// END INIT -----------------------------------------------------------------------------------------------

// MULTIPLAYER -----------------------------------------------------------------------------------------------

#pragma pack(push, 1)
enum class PacketType : uint8_t{
    POSITION_UPDATE = 1,
    HEARTBEAT = 2
};

struct GamePacket{
    PacketType type;
    int player_id;
    double x;
    double y;
};
#pragma pack(pop)

// END MULTIPLAYER -----------------------------------------------------------------------------------------------


// DEFINE --------------------------------------------------------------------------------------------


#define RESET   "\033[0m"

#define GLOW    "\033[5m"

string fg_rgb(rgb c){
    return "\033[38;2;" + to_string(c.r) + ";" + to_string(c.g) + ";" + to_string(c.b) + "m";
}

string bg_rgb(rgb c){
    return "\033[48;2;" + to_string(c.r) + ";" + to_string(c.g) + ";" + to_string(c.b) + "m";
}

rgb text_col[200][100];
rgb bg_col[200][100];

str prev_grid[200][100];
rgb prev_text_col[200][100];
rgb prev_bg_col[200][100];
bool prev_valid = 0;

// HOW TO TRANSFORM U+2665 -> u8"\u2665"

const str FUL_BLOCK = u8"\u2588";
const str HEART = u8"\u2665";
const str RIGHTARROW = u8"\u21E8";
const str LEFTARROW_THIN = u8"\u21FD";

const str TOPLEF_CORNER = u8"\u250C";
const str TOPRIG_CORNER = u8"\u2510";
const str BOTLEF_CORNER = u8"\u2514";
const str BOTRIG_CORNER = u8"\u2518";

const str LIGHT_HOZON = u8"\u2500";
const str LIGHT_VERTI = u8"\u2502";

const str TOPFUL = u8"\u2580";
const str BOTFUL = u8"\u2584";
const str LEFFUL = u8"\u258C";
const str RIGFUL = u8"\u2590";

const str VERTI_DUO = u8"\u2551";
const str HOZON_DUO = u8"\u2550";

const str TOPLEF_CORNER_DUO = u8"\u2554";
const str TOPRIG_CORNER_DUO = u8"\u2557";
const str BOTLEF_CORNER_DUO = u8"\u255A";
const str BOTRIG_CORNER_DUO = u8"\u255D";

const str MID_DOT = u8"\u00B7";

// END DEFINE --------------------------------------------------------------------------------------------



// BASIC FUNCT -------------------------------------------------------------------------------------------

mt19937_64 rnd(time(0));

Long random(Long st,Long ed){
    if(ed < st) swap(ed,st);
    return st + (Long)(rnd() % ((unsigned long long)ed-st+1));
}

void wait_clear(Long milisec){
    if(milisec > -1) this_thread::sleep_for(std::chrono::milliseconds(milisec));
    #ifdef _WIN32
        system("cls");
    #else
        system("clear");
    #endif
}

// chinh xac hon
void wait_ms_p(double seconds){
    if(seconds <= 0) return;

    auto start = steady_clock::now();
    auto target = start + duration<double>(seconds);

    while (steady_clock::now() < target - microseconds(100)) {
        std::this_thread::yield(); 
    }

    while (steady_clock::now() < target){
        #if defined(_M_X64) or defined(_M_IX86) or defined(__x86_64__)
        _mm_pause();
        #endif
    }
}

void wait_ms(Long milisec){
    this_thread::sleep_for(std::chrono::milliseconds(milisec));
    //wait_ms_p((double)milisec/1000.0);
}

void set_square_font(int fontSize = 16){
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_FONT_INFOEX cfi = {0};
    cfi.cbSize = sizeof(CONSOLE_FONT_INFOEX);

    GetCurrentConsoleFontEx(hOut, FALSE, &cfi);

    wcscpy(cfi.FaceName, L"Consolas"); 
    cfi.FontFamily = FF_DONTCARE;
    cfi.FontWeight = FW_NORMAL;

    cfi.dwFontSize.X = fontSize;
    cfi.dwFontSize.Y = fontSize;

    SetCurrentConsoleFontEx(hOut, FALSE, &cfi);
}

void set_console_buffer_size(){
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);

    SMALL_RECT tmpWin = {0, 0, 1, 1};
    SetConsoleWindowInfo(hOut, TRUE, &tmpWin);

    COORD bufferSize = { (SHORT)WIDTH, (SHORT)HEIGHT };
    SetConsoleScreenBufferSize(hOut, bufferSize);

    SMALL_RECT windowSize = { 0, 0, (SHORT)(WIDTH - 1), (SHORT)(HEIGHT - 1) };
    SetConsoleWindowInfo(hOut, TRUE, &windowSize);
}

void hide_cursor(){
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursorInfo;
    GetConsoleCursorInfo(hOut, &cursorInfo);
    cursorInfo.bVisible = FALSE; // Set visibility to false
    SetConsoleCursorInfo(hOut, &cursorInfo);
}

void setup(Long col,Long row,Long posx,Long posy){

    cout << "\033[?25l"; 
    cout << "\033[H"; 
    // resize terminal window (ANSI format is: ESC [ 8 ; rows ; cols t)
    cout << "\033[8;" << row << ";" << col << "t";
    cout << flush;

    // move the physical window on your monitor
    HWND consolewindow = GetConsoleWindow();
    if(consolewindow != NULL){
        SetWindowPos(consolewindow, 0, posx, posy, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
    }

    // disable text selection and mark mode
    HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
    DWORD mode = 0;
    GetConsoleMode(hInput, &mode);

    mode &= ~ENABLE_QUICK_EDIT_MODE;
    mode &= ~ENABLE_LINE_INPUT;
    mode &= ~ENABLE_ECHO_INPUT;

    // ENABLE_MOUSE_INPUT and ENABLE_EXTENDED_FLAGS
    HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
    DWORD m = 0;
    GetConsoleMode(hIn, &m);
    m &= ~ENABLE_QUICK_EDIT_MODE;
    m |=  ENABLE_EXTENDED_FLAGS;     // required to change quick-edit
    m |=  ENABLE_MOUSE_INPUT;
    SetConsoleMode(hIn, m);

    // (anti slow fallback)
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD out_mode;
    GetConsoleMode(hOut, &out_mode);
    SetConsoleMode(hOut, out_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN);

    hide_cursor();
}

void set_con_size(Long col,Long row){
    cout << "\033[8;" << row << ";" << col << "t";
    cout << flush;
}

void set_con_pos(Long posx,Long posy){
    HWND consolewindow = GetConsoleWindow();
    if(consolewindow != NULL){
        SetWindowPos(consolewindow, 0, posx, posy, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
    }
}

pll getmousepos_monitor(){
    POINT cursorPos;
    GetCursorPos(&cursorPos);

    return {cursorPos.x,cursorPos.y};
}

// -------- combined console guard (selection + right-click) --------
static std::atomic<bool> g_stop_guard{false};
static HHOOK           g_mouse_hook = nullptr;
static std::thread     g_guard_thread;
static DWORD           g_guard_tid = 0;
static volatile LONG g_rc_down   = 0;
static volatile LONG g_wheel_acc = 0;   // <-- new

static LRESULT CALLBACK mouse_proc(int nCode, WPARAM wParam, LPARAM lParam){
    if(nCode < 0) return CallNextHookEx(g_mouse_hook, nCode, wParam, lParam);

    HWND fg = GetForegroundWindow();
    HWND me = GetConsoleWindow();
    if(fg != me) return CallNextHookEx(g_mouse_hook, nCode, wParam, lParam);

    switch(wParam){
        case WM_RBUTTONDOWN:
        case WM_NCRBUTTONDOWN:
            InterlockedExchange(&g_rc_down, 1);
            return 1;
        case WM_RBUTTONUP:
        case WM_NCRBUTTONUP:
            InterlockedExchange(&g_rc_down, 0);
            return 1;
        case WM_RBUTTONDBLCLK:
            return 1;

        case WM_MOUSEWHEEL: {
            MSLLHOOKSTRUCT* ms = (MSLLHOOKSTRUCT*)lParam;
            short delta = (short)HIWORD(ms->mouseData);
            if(delta > 0)      InterlockedIncrement(&g_wheel_acc);
            else if(delta < 0) InterlockedDecrement(&g_wheel_acc);
            // don't swallow — let it pass to any other hook in the chain
            return CallNextHookEx(g_mouse_hook, nCode, wParam, lParam);
        }
    }
    return CallNextHookEx(g_mouse_hook, nCode, wParam, lParam);
}

// -------- keyboard shortcut suppressor --------
static HHOOK g_kb_hook = nullptr;

static LRESULT CALLBACK kb_proc(int nCode, WPARAM wParam, LPARAM lParam){
    if(nCode < 0) return CallNextHookEx(g_kb_hook, nCode, wParam, lParam);

    HWND fg = GetForegroundWindow();
    HWND me = GetConsoleWindow();
    if(fg != me) return CallNextHookEx(g_kb_hook, nCode, wParam, lParam);

    if(wParam != WM_KEYDOWN and wParam != WM_SYSKEYDOWN)
        return CallNextHookEx(g_kb_hook, nCode, wParam, lParam);

    KBDLLHOOKSTRUCT * kb = (KBDLLHOOKSTRUCT*)lParam;
    DWORD vk = kb->vkCode;

    bool ctrl  = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
    bool shift = (GetAsyncKeyState(VK_SHIFT)   & 0x8000) != 0;

    if(ctrl and (vk == 'A' or vk == 'M' or vk == 'F' or vk == 'S' or vk == 'Q')){
        return 1;
    }
    if(shift and (vk == VK_LEFT  or vk == VK_RIGHT or
                  vk == VK_UP    or vk == VK_DOWN  or
                  vk == VK_HOME  or vk == VK_END   or
                  vk == VK_PRIOR or vk == VK_NEXT)){
        return 1;
    }
    return CallNextHookEx(g_kb_hook, nCode, wParam, lParam);
}

#define GUARD_TIMER_ID 1

static void check_selection_once(){
    HWND hwnd = GetConsoleWindow();
    if(!hwnd) return;

    CONSOLE_SELECTION_INFO si{};
    if(GetConsoleSelectionInfo(&si) && (si.dwFlags & CONSOLE_SELECTION_IN_PROGRESS)){
        PostMessage(hwnd, WM_CANCELMODE, 0, 0);
        PostMessage(hwnd, WM_KEYDOWN, VK_ESCAPE, 0);
        PostMessage(hwnd, WM_KEYUP,   VK_ESCAPE, 0);
    }
}

static void guard_thread_func(){
    g_guard_tid = GetCurrentThreadId();

    g_mouse_hook = SetWindowsHookEx(WH_MOUSE_LL,    mouse_proc, GetModuleHandle(NULL), 0);
    g_kb_hook    = SetWindowsHookEx(WH_KEYBOARD_LL, kb_proc,    GetModuleHandle(NULL), 0);

    UINT_PTR timer = SetTimer(NULL, GUARD_TIMER_ID, 20, NULL);

    MSG msg;
    while(!g_stop_guard.load() && GetMessage(&msg, NULL, 0, 0)){
        if(msg.message == WM_TIMER && msg.wParam == GUARD_TIMER_ID){
            check_selection_once();
            continue;
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    if(timer)             KillTimer(NULL, timer);
    if(g_mouse_hook)    { UnhookWindowsHookEx(g_mouse_hook); g_mouse_hook = nullptr; }
    if(g_kb_hook)       { UnhookWindowsHookEx(g_kb_hook);    g_kb_hook    = nullptr; }
}

static void start_console_guard(){
    g_guard_thread = std::thread(guard_thread_func);
    g_guard_thread.detach();
}

static void stop_console_guard(){
    g_stop_guard.store(true);
    if(g_guard_tid) PostThreadMessage(g_guard_tid, WM_QUIT, 0, 0);
}

bool key[500];
bool key_pressed[500];
double key_time[500];

Long mousex = 0,mousey = 0;
bool mouseclick[2]; // 0 = left, 1 = right
int mousewheel = 0; // 1 = upwheel, 0 = normal, -1 = downwheel

void auto_keycheck_mousepos(){

    mouseclick[0] = 0;
    mouseclick[1] = 0;
    mousewheel = 0;

    // check if console in foreground (if background then turn off all)
    HWND hConsole = GetConsoleWindow();
    HWND hForeground = GetForegroundWindow();
    if(hConsole != hForeground) {
        for(int i = 0;i&lt;500;i++){
            key[i] = 0;
        }
        mouseclick[0] = 0;
        mouseclick[1] = 0;
        mousewheel = 0;   
        InterlockedExchange(&g_wheel_acc, 0);
        return ; 
    }

    POINT p;
    if(GetCursorPos(&p)){
        ScreenToClient(hConsole, &p);

        int font_w = font_size;
        int font_h = font_size;

        int cx = p.x / font_w;
        int cy = p.y / font_h;

        if(cx >= 0 and cx < WIDTH)  mousex = cx;
        if(cy >= 0 and cy < HEIGHT) mousey = cy;
    }

    HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
    DWORD eventnum = 0;
    GetNumberOfConsoleInputEvents(hInput,&eventnum);

    mouseclick[0] = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
    mouseclick[1] = (InterlockedCompareExchange(&g_rc_down, 0, 0) != 0);

    // wheel — consume the accumulator, signed
    LONG wd = InterlockedExchange(&g_wheel_acc, 0);
    if(wd > 0)      mousewheel =  1;
    else if(wd < 0) mousewheel = -1;
    else            mousewheel =  0;

    for(int i = 3; i < 500; i++){
        key[i] = (GetAsyncKeyState(i) & 0x8000) != 0;
    }

    for(int i = 3;i&lt;500;i++){
        if(key[i]){
            if(key_time[i] == 0 or key_time[i] + dt >= key_delay) key_pressed[i] = 1;
            else key_pressed[i] = 0;

            key_time[i] += dt;

            if(key_time[i] >= key_delay){
                key_time[i] -= key_repeat_rate;
            }
        }
        else{
            key_time[i] = 0;
            key_pressed[i] = 0;
        }
    }
}

void reset_keys(){
    for(int i = 0;i&lt;500;i++){
        key[i] = 0;
        key_time[i] = 0;
        key_pressed[i] = 0;
    }
}

inline void append_num(string& s, int n){
    if (n >= 100) s += (char)('0' + n / 100);
    if (n >= 10)  s += (char)('0' + (n / 10) % 10);
    s += (char)('0' + n % 10);
}

inline void append_fg_rgb(string& s, const rgb& c){
    s += "\033[38;2;";
    append_num(s, c.r); s += ';';
    append_num(s, c.g); s += ';';
    append_num(s, c.b); s += 'm';
}

inline void append_bg_rgb(string& s, const rgb& c){
    s += "\033[48;2;";
    append_num(s, c.r); s += ';';
    append_num(s, c.g); s += ';';
    append_num(s, c.b); s += 'm';
}

void lock_window_size(){
    HWND consoleWindow = GetConsoleWindow();
    if (consoleWindow != NULL) {
        LONG style = GetWindowLong(consoleWindow, GWL_STYLE);
        style &= ~WS_MAXIMIZEBOX;
        style &= ~WS_SIZEBOX;
        SetWindowLong(consoleWindow, GWL_STYLE, style);
    }
}

void kill_console_selection(){
    HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
    DWORD m = 0;
    if(!GetConsoleMode(hIn, &m)) return;

    m &= ~ENABLE_QUICK_EDIT_MODE;
    m &= ~ENABLE_INSERT_MODE;
    m |=  ENABLE_MOUSE_INPUT;
    m |=  ENABLE_EXTENDED_FLAGS;
    m |=  ENABLE_PROCESSED_INPUT;

    SetConsoleMode(hIn, m);
}

// END BASIC FUNCT -------------------------------------------------------------------------------------------



// DRAW FUNCT ----------------------------------------------------------------------------------------------------------------------------------

// change rgb brightness
inline rgb scale_rgb(const rgb& c, double factor){
    // > 1.0 -> brightness
    // 1.0 -> bth
    // < 1.0 -> darkness
    return rgb(
        (int)(c.r * factor),
        (int)(c.g * factor),
        (int)(c.b * factor)
    );
}

void set_pixel(int x,int y,str ch,rgb fg,rgb bg){
    if(x >= 0 and x < WIDTH and y >= 0 and y < HEIGHT){
        grid[y][x] = ch;
        if(fg.r != -1) text_col[y][x] = fg;
        if(bg.r != -1) bg_col[y][x] = bg;
    }
}

void draw_rect(int start_x,int start_y,int end_x,int end_y,
               int layer = 1,vector<str> corner = {"O"},vector<str> side_horzontal = {"-"},vector<str> side_vertical = {"|"},vector<str> inside = {"."},
               rgb corner_col = rgb(),vector<rgb> side_horz_col = {rgb()},vector<rgb> side_vert_col = {rgb()},vector<rgb> inside_col = {rgb()},
               rgb bg_corner_col = {rgb(0,0,0)},vector<rgb> bg_side_horz_col = {rgb(0,0,0)},vector<rgb> bg_side_vert_col = {rgb(0,0,0)},vector<rgb> bg_inside_col = {rgb(0,0,0)}){

    if(end_x < start_x) swap(end_x,start_x);
    if(end_y < start_y) swap(end_y,start_y);

    int tx = 0;
    int ty = 0;

    for(int y = start_y;y<=start_y + (end_y - start_y)/2;y++){
        for(int x = start_x;x<=start_x + (end_x - start_x)/2;x++){
            if((y < start_y + layer and x < start_x + layer)){
                tx = x;
                ty = y;
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[y][x] = corner[0];
                    text_col[y][x] = corner_col;
                    bg_col[y][x] = bg_corner_col;
                }

                tx = end_x - (x-start_x);
                ty = end_y - (y-start_y);
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[end_y - (y-start_y)][end_x - (x-start_x)] = (corner.size() >= 4 ? corner[3] : corner[0]);
                    text_col[end_y - (y-start_y)][end_x - (x-start_x)] = corner_col;
                    bg_col[end_y - (y-start_y)][end_x - (x-start_x)] = bg_corner_col;
                }

                tx = x;
                ty = end_y - (y-start_y);
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[end_y - (y-start_y)][x] = (corner.size() >= 3 ? corner[2] : corner[0]);
                    text_col[end_y - (y-start_y)][x] = corner_col;
                    bg_col[end_y - (y-start_y)][x] = bg_corner_col;
                }

                tx = end_x - (x-start_x);
                ty = y;
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[y][end_x - (x-start_x)] = (corner.size() >= 2 ? corner[1] : corner[0]);
                    text_col[y][end_x - (x-start_x)] = corner_col;
                    bg_col[y][end_x - (x-start_x)] = bg_corner_col;
                }
            }
            else if(y < start_y + layer){
                tx = x;
                ty = y;
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[y][x] = side_horzontal[(x - (start_x + layer))%(side_horzontal.size())];
                    text_col[y][x] = side_horz_col[(x - (start_x + layer))%(side_horz_col.size())];
                    bg_col[y][x] = bg_side_horz_col[(x - (start_x + layer))%(side_horz_col.size())];
                }

                tx = end_x - (x-start_x);
                ty = y;
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[y][end_x - (x-start_x)] = side_horzontal[(x - (start_x + layer))%(side_horzontal.size())];
                    text_col[y][end_x - (x-start_x)] = side_horz_col[(x - (start_x + layer))%(side_horz_col.size())];
                    bg_col[y][end_x - (x-start_x)] = bg_side_horz_col[(x - (start_x + layer))%(side_horz_col.size())];
                }

                tx = x;
                ty = end_y - (y-start_y);
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[end_y - (y-start_y)][x] = side_horzontal[(x - (start_x + layer))%(side_horzontal.size())];
                    text_col[end_y - (y-start_y)][x] = side_horz_col[(x - (start_x + layer))%(side_horz_col.size())];
                    bg_col[end_y - (y-start_y)][x] = bg_side_horz_col[(x - (start_x + layer))%(side_horz_col.size())];
                }

                tx = end_x - (x-start_x);
                ty = end_y - (y-start_y);
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[end_y - (y-start_y)][end_x - (x-start_x)] = side_horzontal[(x - (start_x + layer))%(side_horzontal.size())];
                    text_col[end_y - (y-start_y)][end_x - (x-start_x)] = side_horz_col[(x - (start_x + layer))%(side_horz_col.size())];
                    bg_col[end_y - (y-start_y)][end_x - (x-start_x)] = bg_side_horz_col[(x - (start_x + layer))%(side_horz_col.size())];
                }
            }
            else if(x < start_x + layer){
                tx = x;
                ty = y;
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[y][x] = side_vertical[(y - (start_y + layer))%(side_vertical.size())];
                    text_col[y][x] = side_vert_col[(y - (start_y + layer))%(side_vert_col.size())];
                    bg_col[y][x] = bg_side_vert_col[(y - (start_y + layer))%(side_vert_col.size())];
                }

                tx = end_x - (x-start_x);
                ty = y;
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[y][end_x - (x-start_x)] = side_vertical[(y - (start_y + layer))%(side_vertical.size())];
                    text_col[y][end_x - (x-start_x)] = side_vert_col[(y - (start_y + layer))%(side_vert_col.size())];
                    bg_col[y][end_x - (x-start_x)] = bg_side_vert_col[(y - (start_y + layer))%(side_vert_col.size())];
                }

                tx = x;
                ty = end_y - (y-start_y);
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[end_y - (y-start_y)][x] = side_vertical[(y - (start_y + layer))%(side_vertical.size())];
                    text_col[end_y - (y-start_y)][x] = side_vert_col[(y - (start_y + layer))%(side_vert_col.size())];
                    bg_col[end_y - (y-start_y)][x] = bg_side_vert_col[(y - (start_y + layer))%(side_vert_col.size())];
                }

                tx = end_x - (x-start_x);
                ty = end_y - (y-start_y);
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[end_y - (y-start_y)][end_x - (x-start_x)] = side_vertical[(y - (start_y + layer))%(side_vertical.size())];
                    text_col[end_y - (y-start_y)][end_x - (x-start_x)] = side_vert_col[(y - (start_y + layer))%(side_vert_col.size())];
                    bg_col[end_y - (y-start_y)][end_x - (x-start_x)] = bg_side_vert_col[(y - (start_y + layer))%(side_vert_col.size())];
                }
            }
            else{
                tx = x;
                ty = y;
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[y][x] = inside[(y - (start_y + layer) + x - (start_x + layer))%(inside.size())];
                    text_col[y][x] = inside_col[(y - (start_y + layer) + x - (start_x + layer))%(inside_col.size())];
                    bg_col[y][x] = bg_inside_col[(y - (start_y + layer) + x - (start_x + layer))%(inside_col.size())];
                }

                tx = end_x - (x-start_x);
                ty = y;
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[y][end_x - (x-start_x)] = inside[(y - (start_y + layer) + x - (start_x + layer))%(inside.size())];
                    text_col[y][end_x - (x-start_x)] = inside_col[(y - (start_y + layer) + x - (start_x + layer))%(inside_col.size())];
                    bg_col[y][end_x - (x-start_x)] = bg_inside_col[(y - (start_y + layer) + x - (start_x + layer))%(inside_col.size())];
                }

                tx = x;
                ty = end_y - (y-start_y);
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){ 
                    grid[end_y - (y-start_y)][x] = inside[(y - (start_y + layer) + x - (start_x + layer))%(inside.size())];
                    text_col[end_y - (y-start_y)][x] = inside_col[(y - (start_y + layer) + x - (start_x + layer))%(inside_col.size())];
                    bg_col[end_y - (y-start_y)][x] = bg_inside_col[(y - (start_y + layer) + x - (start_x + layer))%(inside_col.size())];
                }

                tx = end_x - (x-start_x);
                ty = end_y - (y-start_y);
                if(!(0 > tx or tx >= WIDTH or 0 > ty or ty >= HEIGHT)){
                    grid[end_y - (y-start_y)][end_x - (x-start_x)] = inside[(y - (start_y + layer) + x - (start_x + layer))%(inside.size())];
                    text_col[end_y - (y-start_y)][end_x - (x-start_x)] = inside_col[(y - (start_y + layer) + x - (start_x + layer))%(inside_col.size())];
                    bg_col[end_y - (y-start_y)][end_x - (x-start_x)] = bg_inside_col[(y - (start_y + layer) + x - (start_x + layer))%(inside_col.size())];
                }
            }
        }
    }
}


void fill(char ch,rgb fg = rgb(0,0,0),rgb bg = rgb(0,0,0),int start_x = 0,int start_y = 0,int end_x = WIDTH - 1,int end_y = HEIGHT - 1){
    for(int y=start_y;y<=end_y;y++){
        for(int x=start_x;x<=end_x;x++){
            grid[y][x] = ch;
            text_col[y][x] = fg;
            bg_col[y][x] = bg;
        }
    }
}

void draw_text(int start_x,int start_y,
               str text,rgb text_col_ = rgb(),rgb bg_text_col = rgb(0,0,0),
               char space_char = ' ',rgb space_col = rgb(0,0,0),rgb bg_space_col = rgb(0,0,0),
               vector<str> unicode = {},char replace_char = '$'){
    if(start_x < 0 or start_x+text.size()-1 >= WIDTH or start_y < 0 or start_y >= HEIGHT) return;
    if (text.empty()) return;

    int pl =0;

    for(int i = start_x;i<=start_x + text.size() - 1;i++){

        if(text[i-(start_x)] == replace_char){
            grid[start_y][i] = unicode[pl % unicode.size()];
            if(text_col_.r != -1) text_col[start_y][i] = text_col_;
            if(bg_text_col.r != -1) bg_col[start_y][i] = bg_text_col;
            pl++;
        }
        else{
            grid[start_y][i] = text[i-(start_x)];
            if(text_col_.r != -1) text_col[start_y][i] = text_col_;
            if(bg_text_col.r != -1) bg_col[start_y][i] = bg_text_col;
        }
        if(text[i-(start_x)] == ' '){
            grid[start_y][i] = space_char;
            if(space_col.r != -1) text_col[start_y][i] = space_col;
            if(bg_space_col.r != -1) bg_col[start_y][i] = bg_space_col;
        }
    }
}


void draw_img(int start_x,int start_y,img image){

    if(start_x < 0 or start_x + image.w - 1 >= WIDTH or start_y < 0 or start_y + image.h - 1 >= HEIGHT or image.h == 0 or image.w == 0) return;

    int idx = 0;

    Long pl = 0;

    for(int i = start_y;i < start_y + image.h;i++){
        for(int j = start_x;j < start_x + image.w;j++){
            if(image.im[idx] == image.replace_ch){
                grid[i][j] = image.unicode[pl];
                text_col[i][j] = image.col[idx];
                pl++;
            }
            else if(image.im[idx] != image.nochar){
                grid[i][j] = image.im[idx];
                text_col[i][j] = image.col[idx];
            }
            else{
                grid[i][j] = image.nochar_replace;
                text_col[i][j] = RGB(0,0,0);
            }
            idx++;
        }
    }
}

static string output;
void draw_flush(){
    output.clear();
    output.reserve(WIDTH * HEIGHT * 16);

    rgb last_fg = {-1, -1, -1};
    rgb last_bg = {-1, -1, -1};
    int cur_x = -1, cur_y = -1;

    for(int y = 0; y < HEIGHT; y++){
        for(int x = 0; x < WIDTH; x++){
            if(grid[y][x].empty()){
                grid[y][x] = " ";
            }

            bool changed = !prev_valid
                or grid[y][x]      != prev_grid[y][x]
                or text_col[y][x].r != prev_text_col[y][x].r
                or text_col[y][x].g != prev_text_col[y][x].g
                or text_col[y][x].b != prev_text_col[y][x].b
                or bg_col[y][x].r   != prev_bg_col[y][x].r
                or bg_col[y][x].g   != prev_bg_col[y][x].g
                or bg_col[y][x].b   != prev_bg_col[y][x].b;

            if(!changed) continue;

            if(cur_y != y or cur_x != x){
                output += "\033[";
                append_num(output, y + 1);
                output += ';';
                append_num(output, x + 1);
                output += 'H';
                cur_y = y;
                cur_x = x;
            }

            if(text_col[y][x].r != last_fg.r or text_col[y][x].g != last_fg.g or text_col[y][x].b != last_fg.b){
                append_fg_rgb(output, text_col[y][x]);
                last_fg = text_col[y][x];
            }
            if(bg_col[y][x].r != last_bg.r or bg_col[y][x].g != last_bg.g or bg_col[y][x].b != last_bg.b){
                append_bg_rgb(output, bg_col[y][x]);
                last_bg = bg_col[y][x];
            }

            output += grid[y][x];
            cur_x++;

            prev_grid[y][x] = grid[y][x];
            prev_text_col[y][x] = text_col[y][x];
            prev_bg_col[y][x] = bg_col[y][x];
        }
    }

    if(output.empty()) return;

    output += RESET;

    prev_valid = true;

    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD written;
    WriteFile(hOut, output.c_str(), (DWORD)output.length(), &written, NULL);
}


// END DRAW FUNCT ----------------------------------------------------------------------------------------------------------------------------------



// MAIN FUNCTION1 -----------------------------------------------------------------------------------------------------------------------------------


void threadFunc(){



    return ;
}


// END MAIN FUNCTION1 -----------------------------------------------------------------------------------------------------------------------------------

bool init_socket_check = 0;

int prscr = 1;
int scr = 5;
int transcr = 1;

double scr_fade = 1.0;
int fade_state = 0;
void apply_scr_fade(double alpha){
    alpha = max(0.0, min(1.0, alpha));

    // > 1.0 -> brightness
    // 1.0 -> bth
    // < 1.0 -> darkness

    for(int y = 0; y < HEIGHT; y++){
        for(int x = 0; x < WIDTH; x++){
            text_col[y][x] = scale_rgb(text_col[y][x], alpha);
            bg_col[y][x]   = scale_rgb(bg_col[y][x], alpha);
        }
    }
} 

struct background{
    int x,y;
    str uni;
    rgb col;
    double fade;
    double fade_minus;

    background(int x_,int y_,str uni_,rgb col_,double fade_,double fade_minus_){
        x = x_;
        y = y_;
        uni = uni_;
        col = col_;
        fade = fade_;
        fade_minus = fade_minus_;
    }
};
vector<background> deco;

double timer[50];
int tempx,tempy;

struct item_data{

    // id = 0 is null

    bool isblock = 0; // 0 -> item, 1-> block
    int id = 0;
    str name = "NULL"; //max at 64 character

    int speed_plus = 0;
    double hp_plus = 0;

    double phy_dmg_plus = 0;
    double mag_dmg_plus = 0;

    int range_plus = 0; // tiles

    double phy_armor_plus = 0;
    double mag_armor_plus = 0;

    double counter = 0;
    double counter_percent = 0;

    double misc[7] = {0,0,0,0,0,0,0};

    /*
    0 = amount
    1 = mining efficiency on (bit count)
    - 0 = nature block (wood and other living things)
    - 1 = rock/stone/ore/...
    - 2 = dirt/sand/snow/...
    2 = tool level
    */

    int max_stack = 32;

    item_data() = default;

};

struct block_data{

    // id = 0 is null

    int id = 0;
    int type = 1; // 0 -> under ground, 1 -> water, 2 -> ground, 3 -> wall (not block hovering), 4 -> higher wall (not block air)

    str name = "NULL";

    int x = 0;
    int y = 0;

    double hp = 10;

    double phy_armor = 0;
    double mag_armor = 0;

    double counter = 0;
    double counter_percent = 0;

    item_data drops[5];
    int drop_amount[5];
    int drop_chance[5]; // 0 -> 100%

    bool is_simple_drop = 1;

    int level = 0;

    int states = 0;
    short open_gui_id = 0;

    block_data() = default;
};

struct player_data{

    str name = "Temp"; // min lengh = 1, max lengh = 16

    int type = 2; // 0 = underground, 1 = water, 2 = ground, 3 = hovering, 4 = air
    int active = 0;

    double x = 0;
    double y = 0;

    int speed = 10; // tile / s
    double hp = 10.0;
    double dir = 0; // 0 = up, 180 = down

    double range = 4.6;
    int tool = 0;

    double mining_speed = 1;
    double phy_dmg = 0.75;
    double mag_dmg = 0;

    double phy_armor = 0;
    double mag_armor = 0;

    item_data armor[2]; // 0 is more priority than 1
    item_data inventory[20];

    int selected_hotbar = 0;

    short gui = 0;

    player_data() = default;

    /*

    Player data:

    Speed: 10 tile/s
    Sprint: 1.5x speed

    direction: 0

    Hp: 10.0

    D1 = Physical dmg: 0.75
    D2 = Magical dmg: 0.0

    A1 = Armor points: 0
    A2 = Magical armor points: 0

    C = counter power
    C% = counter percentage (%)

    (balance at dmg = 2)

    Physical dmg armor reduction fomula:
    Normal:     D1 * Hp / (Hp + A1)
    Anti burst: D1 / (1 + 0.025 * A1 * D1^2)
    Anti spam:  max(0.124*D1,D1 - 0.1*A1)

    Magical dmg armor reduction fomula:
    normal:     D2 * min(0.95,A2 / 20)

    * magic dmg bypass all physical armor formula *

    Counter dmg (count as magic dmg) (C% it will happening):
    Physical    D1 * (1-c/100)
    Magical     D1 * (1-c/100) * 0.5


    */
}; 

struct entity_data{

    // id = 0 is null

    int type = 2; // 0 = underground, 1 = water, 2 = ground, 3 = hovering, 4 = air
    int id = 0;

    str name = "Temp"; // min lengh = 1, max lengh = 16

    double x = 0;
    double y = 0;

    int siz = 2;

    int speed = 10;
    double hp = 10.0;
    double dir = 0; // 0 = up, 180 = down

    int range = 3;

    double mining_speed = 1;
    double phy_dmg = 0.75;
    double mag_dmg = 0;

    double phy_armor = 0;
    double mag_armor = 0;

    double counter = 0;
    double counter_percent = 0;

    double misc[10] = {0,0,0,0,0,0,0,0,0,0};

    item_data drops[5];
    int drop_amount[5];
    int drop_chance[5]; // 0 -> 100%

    entity_data() = default;
};

enum BlockID : short{
    ID_AIR = 0,

    ID_DIRT,
    ID_SANDSTONE,
    ID_STONE,
    ID_GRANITE,
    ID_MAGMA,
    ID_COPPER_ORE,
    ID_IRON_ORE,
    ID_GOLD_ORE,
    ID_COAL_ORE,
    ID_DIAMOND,
    ID_RUBY,
    ID_EMERALD,
    ID_PACKED_ICE,
    ID_GAS_PACKET,
    ID_OBSIDIAN,

    ID_WATER,
    ID_LAVA,
    ID_OIL,

    ID_GRASS,
    ID_MYCELIUM,
    ID_SAND,
    ID_SNOW,
    ID_ICE,
    ID_WOOD,
    ID_LEAVES,
    ID_MUSHROOM,

    // man-made
    IT_PLANKS,


    // functional
    IT_WORKBENCH,

    ID_COUNT,
};

enum ItemID : short{
    IT_AIR = ID_COUNT + 1,

    IT_STICK,

    IT_STONE_HAMMER,
    IT_STONE_AXE,

};

struct item_entity{
    item_data it;

    double x = 0,y = 0;
    int layer = 2;

    double hp = 5.0;
    double spawn_time = 0;
};

const Long chunk_size = 32;
const Long render_rad = 7; // (render_rad*2*chunk_size)^2 cell
const Long max_c = 3600/chunk_size+1;

struct chunk{
    vector<block_data> blocks;
    vector<entity_data> entities;
    vector<item_entity> items; 
};

pll get_chunk(Long x,Long y){
    return {x/chunk_size,y/chunk_size};
}

struct world_data{

    bool iscreated = 0;
    bool gen = 0;
    string name = ""; // min lengh = 1, max lengh = 16

    chunk chunk_grid[max_c][max_c];

    // block display
    short grid[3600][3600][5]; // how many block types

    player_data players[8]; // 0 is admin

    bool isjoin = 0;

    world_data() = default;
}; 

int worldid = 0;
world_data worlds[6] = {world_data(),world_data(),world_data(),world_data(),world_data(),world_data()};

int type_mask[10];
str type_input[100];

// ==================== Item / BLOCK DROPS INFO ========================================================

bool checkitem(const item_data & x,const item_data & y){
    return (x.id == y.id and x.isblock == y.isblock and x.name == y.name and x.speed_plus == y.speed_plus and x.hp_plus == y.hp_plus and 
            x.phy_dmg_plus == y.phy_dmg_plus and x.mag_dmg_plus == y.mag_dmg_plus and x.range_plus == y.range_plus and x.phy_armor_plus == y.phy_armor_plus and
            x.mag_armor_plus == y.mag_armor_plus and x.counter == y.counter and x.counter_percent == y.counter_percent and x.max_stack == y.max_stack and
            x.misc[1] == y.misc[1] and x.misc[2] == y.misc[2] and x.misc[3] == y.misc[3] and x.misc[4] == y.misc[4] and x.misc[5] == y.misc[5] and x.misc[6] == y.misc[6]);
}

static str get_name(short id){
    switch(id){
        case ID_DIRT:        return "Dirt";
        case ID_GRASS:       return "Grass";
        case ID_MYCELIUM:    return "Mycelium";
        case ID_SAND:        return "Sand";
        case ID_SANDSTONE:   return "Sandstone";
        case ID_STONE:       return "Stone";
        case ID_GRANITE:     return "Granite";
        case ID_MAGMA:       return "Magma";
        case ID_OBSIDIAN:    return "Obsidian";
        case ID_COPPER_ORE:  return "Copper Ore";
        case ID_IRON_ORE:    return "Iron Ore";
        case ID_GOLD_ORE:    return "Gold Ore";
        case ID_COAL_ORE:    return "Coal Ore";
        case ID_DIAMOND:     return "Diamond";
        case ID_RUBY:        return "Ruby";
        case ID_EMERALD:     return "Emerald";
        case ID_PACKED_ICE:  return "Packed Ice";
        case ID_ICE:         return "Ice";
        case ID_SNOW:        return "Snow";
        case ID_WOOD:        return "Wood";
        case ID_LEAVES:      return "Leaves";
        case ID_MUSHROOM:    return "Mushroom";

        case IT_PLANKS:       return "Wood Planks";

        case IT_WORKBENCH:    return "Workbench";


        case IT_STICK:        return "Stick";
        case IT_STONE_HAMMER: return "Stone Hammer";
        case IT_STONE_AXE:    return "Stone Axe";

        default: return "NULL";
    }
}

static item_data get_item_data(Long id){
    item_data bi = item_data();
    bi.misc[0] = 0;
    switch(id){
        case ID_DIRT:             bi.id=ID_DIRT;             bi.isblock=1; bi.max_stack = 128;    break;
        case ID_GRASS:            bi.id=ID_GRASS;            bi.isblock=1; bi.max_stack = 128;    break;
        case ID_MYCELIUM:         bi.id=ID_MYCELIUM;         bi.isblock=1; bi.max_stack = 128;    break;
        case ID_SAND:             bi.id=ID_SAND;             bi.isblock=1; bi.max_stack = 32;     break;
        case ID_SANDSTONE:        bi.id=ID_SANDSTONE;        bi.isblock=1; bi.max_stack = 64;     break;
        case ID_STONE:            bi.id=ID_STONE;            bi.isblock=1; bi.max_stack = 64;     break;
        case ID_GRANITE:          bi.id=ID_GRANITE;          bi.isblock=1; bi.max_stack = 64;     break;
        case ID_MAGMA:            bi.id=ID_MAGMA;            bi.isblock=1; bi.max_stack = 32;     break;
        case ID_OBSIDIAN:         bi.id=ID_OBSIDIAN;         bi.isblock=1; bi.max_stack = 32;     break;
        case ID_COPPER_ORE:       bi.id=ID_COPPER_ORE;       bi.isblock=1; bi.max_stack = 64;     break;
        case ID_IRON_ORE:         bi.id=ID_IRON_ORE;         bi.isblock=1; bi.max_stack = 64;     break;
        case ID_GOLD_ORE:         bi.id=ID_GOLD_ORE;         bi.isblock=1; bi.max_stack = 64;     break;
        case ID_COAL_ORE:         bi.id=ID_COAL_ORE;         bi.isblock=1; bi.max_stack = 64;     break;
        case ID_DIAMOND:          bi.id=ID_DIAMOND;          bi.isblock=1; bi.max_stack = 32;     break;
        case ID_RUBY:             bi.id=ID_RUBY;             bi.isblock=1; bi.max_stack = 32;     break;
        case ID_EMERALD:          bi.id=ID_EMERALD;          bi.isblock=1; bi.max_stack = 32;     break;
        case ID_PACKED_ICE:       bi.id=ID_PACKED_ICE;       bi.isblock=1; bi.max_stack = 64;     break;
        case ID_ICE:              bi.id=ID_ICE;              bi.isblock=1; bi.max_stack = 128;    break;
        case ID_SNOW:             bi.id=ID_SNOW;             bi.isblock=1; bi.max_stack = 32;     break;
        case ID_WOOD:             bi.id=ID_WOOD;             bi.isblock=1; bi.max_stack = 64;     break;
        case ID_LEAVES:           bi.id=ID_LEAVES;           bi.isblock=1; bi.max_stack = 128;    break;
        case ID_MUSHROOM:         bi.id=ID_MUSHROOM;         bi.isblock=1; bi.max_stack = 128;    break;

        case IT_PLANKS:           bi.id=IT_PLANKS;           bi.isblock=1; bi.max_stack=64;       break;

        case IT_WORKBENCH:        bi.id=IT_WORKBENCH;        bi.isblock=1; bi.max_stack=32;       break;

        case IT_STICK:            bi.id=IT_STICK;            bi.isblock=0; bi.max_stack=128;      break;

        case IT_STONE_HAMMER:     bi.id=IT_STONE_HAMMER;     bi.isblock=0; bi.max_stack=1; bi.phy_dmg_plus=4;       break;
        case IT_STONE_AXE:        bi.id=IT_STONE_AXE;        bi.isblock=0; bi.max_stack=1; bi.phy_dmg_plus=3;       break;

        default: break;
    }


    return bi;
}

static block_data get_block_data(Long id){
    block_data bi = block_data();

    bi.hp = 1.0;

    for(int i = 0;i&lt;4;i++){
        bi.drops[i].id = ID_AIR;
    }

    switch(id){
        case ID_DIRT:       bi.hp=1.2;   bi.drops[0].id=ID_DIRT;        bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=0;  break;
        case ID_GRASS:      bi.hp=1.2;   bi.drops[0].id=ID_DIRT;        bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=0;  break;
        case ID_MYCELIUM:   bi.hp=1.2;   bi.drops[0].id=ID_MYCELIUM;    bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=0;  break;
        case ID_SAND:       bi.hp=1;     bi.drops[0].id=ID_SAND;        bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=0;  break;
        case ID_SANDSTONE:  bi.hp=10;    bi.drops[0].id=ID_SANDSTONE;   bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=1;  break;
        case ID_STONE:      bi.hp=20;    bi.drops[0].id=ID_STONE;       bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=1;  break;
        case ID_GRANITE:    bi.hp=18.5;  bi.drops[0].id=ID_GRANITE;     bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=1;  break;
        case ID_MAGMA:      bi.hp=22;    bi.drops[0].id=ID_MAGMA;       bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=80;  bi.level=2;  break;
        case ID_OBSIDIAN:   bi.hp=16;    bi.drops[0].id=ID_OBSIDIAN;    bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=4;  break;
        case ID_COPPER_ORE: bi.hp=30;    bi.drops[0].id=ID_COPPER_ORE;  bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=2;  break;
        case ID_IRON_ORE:   bi.hp=35;    bi.drops[0].id=ID_IRON_ORE;    bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=2;  break;
        case ID_GOLD_ORE:   bi.hp=24;    bi.drops[0].id=ID_GOLD_ORE;    bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=3;  break;
        case ID_COAL_ORE:   bi.hp=29;    bi.drops[0].id=ID_COAL_ORE;    bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=2;  break;
        case ID_DIAMOND:    bi.hp=45;    bi.drops[0].id=ID_DIAMOND;     bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=4;  break;
        case ID_RUBY:       bi.hp=29;    bi.drops[0].id=ID_RUBY;        bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=4;  break;
        case ID_EMERALD:    bi.hp=28.7;  bi.drops[0].id=ID_EMERALD;     bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=3;  break;
        case ID_PACKED_ICE: bi.hp=5.5;   bi.drops[0].id=ID_PACKED_ICE;  bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=1;  break;
        case ID_ICE:        bi.hp=2.5;   bi.drops[0].id=ID_ICE;         bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=1;  break;
        case ID_SNOW:       bi.hp=1.0;   bi.drops[0].id=ID_SNOW;        bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=40;  bi.level=0;  break;
        case ID_WOOD:       bi.hp=5;     bi.drops[0].id=ID_WOOD;        bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=0;  break;
        case ID_LEAVES:     bi.hp=0.50;  bi.drops[0].id=ID_LEAVES;      bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=50;  bi.level=0;  break;
        case ID_MUSHROOM:   bi.hp=0.17;  bi.drops[0].id=ID_MUSHROOM;    bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=0;  break;

        case IT_PLANKS:     bi.hp=4;     bi.drops[0].id=IT_PLANKS;      bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=0;  break;

        case IT_WORKBENCH:  bi.hp=4;     bi.drops[0].id=IT_WORKBENCH;   bi.drops[0].isblock=1;  bi.drop_amount[0]=1; bi.drop_chance[0]=100; bi.level=0;  break;

        default: break;
    }
    return bi;
}

// ==================== END ITEM/BLOCK DROPS INFO ========================================================

// ==================== WORLD GEN ====================================================================

static inline void wg_place(world_data & w, int x, int y, int type, short id){
    if((unsigned)x >= 3600u) return;
    if((unsigned)y >= 3600u) return;
    if(type < 0 or type > 4) return;
    w.grid[y][x][type] = id;
}

static inline uint64_t wg_hash(uint64_t x, uint64_t y, uint64_t seed){
    uint64_t h = x * 0x9E3779B97F4A7C15ULL;
    h ^= y * 0xBF58476D1CE4E5B9ULL;
    h ^= seed + 0x94D049BB133111EBULL;
    h ^= h >> 30; h *= 0xBF58476D1CE4E5B9ULL;
    h ^= h >> 27; h *= 0x94D049BB133111EBULL;
    h ^= h >> 31;
    return h;
}
static inline double wg_rand01(uint64_t x, uint64_t y, uint64_t seed){
    return (wg_hash(x, y, seed) & 0xFFFFFF) / (double)0x1000000;
}
static inline int wg_randi(int lo, int hi, uint64_t x, uint64_t y, uint64_t seed){
    if(hi < lo){ int t = lo; lo = hi; hi = t; }
    uint64_t rng = (uint64_t)(hi - lo + 1);
    return lo + (int)(wg_hash(x, y, seed) % rng);
}

// 2D value noise
static double wg_noise2(double x, double y, double freq, uint64_t seed){
    double fx = x * freq, fy = y * freq;
    int64_t ix = (int64_t)floor(fx), iy = (int64_t)floor(fy);
    double tx = fx - (double)ix, ty = fy - (double)iy;
    tx = tx * tx * (3.0 - 2.0 * tx);
    ty = ty * ty * (3.0 - 2.0 * ty);
    double a = wg_rand01((uint64_t)ix,     (uint64_t)iy,     seed);
    double b = wg_rand01((uint64_t)(ix+1), (uint64_t)iy,     seed);
    double c = wg_rand01((uint64_t)ix,     (uint64_t)(iy+1), seed);
    double d = wg_rand01((uint64_t)(ix+1), (uint64_t)(iy+1), seed);
    double ab = a + (b - a) * tx;
    double cd = c + (d - c) * tx;
    return ab + (cd - ab) * ty;
}

enum Biome {
    BIOME_PLAINS, BIOME_FOREST, BIOME_RIVER, BIOME_DESERT,
    BIOME_JUNGLE, BIOME_SNOWY_PLAINS, BIOME_FROZEN_RIVER,
    BIOME_MUSHROOM_PLAINS, BIOME_STONY_MOUNTAIN, BIOME_MOUNTAIN_PEAK,
    BIOME_VOLCANO, BIOME_COUNT
};

static Biome wg_get_biome(int x, int y, uint64_t seed){
    double v = wg_noise2((double)x, (double)y, 1.0/400.0, seed ^ 0xABCD);
    int bi = (int)(v * BIOME_COUNT);
    if(bi >= BIOME_COUNT) bi = BIOME_COUNT - 1;
    if(bi < 0) bi = 0;
    Biome base = (Biome)bi;

    // The base noise occasionally lands on the peak slot.
    // Fold it into stony mountain so peaks ONLY appear as a sub-biome.
    if(base == BIOME_MOUNTAIN_PEAK) base = BIOME_STONY_MOUNTAIN;

    // Secondary noise carves peaks out of stony mountain.
    //  > 0.68  ? about 32% of stony mountain becomes peak
    //  1/150   ? peaks are ~150-tile blobs
    if(base == BIOME_STONY_MOUNTAIN){
        double p = wg_noise2((double)x, (double)y, 1.0/150.0, seed ^ 0x5EED);
        if(p > 0.68) return BIOME_MOUNTAIN_PEAK;
    }
    return base;
}

static Long wg_floor_id(Biome b){
    switch(b){
        case BIOME_PLAINS:          return ID_GRASS;
        case BIOME_FOREST:          return ID_GRASS;
        case BIOME_RIVER:           return ID_SAND;
        case BIOME_DESERT:          return ID_SAND;
        case BIOME_JUNGLE:          return ID_LEAVES;
        case BIOME_SNOWY_PLAINS:    return ID_SNOW;
        case BIOME_FROZEN_RIVER:    return ID_ICE;
        case BIOME_MUSHROOM_PLAINS: return ID_MYCELIUM;
        case BIOME_STONY_MOUNTAIN:  return ID_STONE;
        case BIOME_MOUNTAIN_PEAK:   return ID_PACKED_ICE;
        case BIOME_VOLCANO:         return ID_MAGMA;
        default: return ID_GRASS;
    }
}
static Long wg_underground_id(Biome b){
    switch(b){
        case BIOME_RIVER:           return ID_GRANITE;
        case BIOME_FROZEN_RIVER:    return ID_GRANITE;
        case BIOME_DESERT:          return ID_SANDSTONE;
        case BIOME_STONY_MOUNTAIN:  return ID_STONE;
        case BIOME_MOUNTAIN_PEAK:   return ID_STONE;
        case BIOME_VOLCANO:         return ID_OBSIDIAN;
        default: return ID_DIRT;
    }
}

static void wg_tree(world_data & w, int cx, int cy, uint64_t seed,
                    int trunk_h, int leaf_r, int wall_type)
{
    // trunk is a short vertical strip (top-down: wood is a "wall" a couple tiles tall)
    for(int i = 0; i < trunk_h; i++){
        for(int j = 0; j < trunk_h; j++){
            wg_place(w, cx + i, cy + j, wall_type, ID_WOOD);
            wg_place(w, cx + i, cy + j, 3, ID_WOOD);
        }
    }
    // leaves: a circle blob around the top of the trunk
    for(int dy = -leaf_r - trunk_h; dy <= leaf_r + trunk_h; dy++){
        for(int dx = -leaf_r - trunk_h; dx <= leaf_r + trunk_h; dx++){
            if(dx*dx + dy*dy > (leaf_r+trunk_h)*(leaf_r+trunk_h) + random(0,2) - 1) continue;
            int x = cx + dx + trunk_h/2, y = cy + dy + trunk_h/2;
            if((unsigned)x >= 3600u or (unsigned)y >= 3600u) continue;
            // don't overwrite trunk
            if(cx <= x and x <= cx + trunk_h-1 and cy <= y and y <= cy + trunk_h-1) continue;
            wg_place(w, x, y, wall_type, ID_LEAVES);
            wg_place(w, x, y, 3, ID_LEAVES);
        }
    }
}

void gen_world(world_data & world){
    world.iscreated = 1;

    world.players[0] = player_data();

    uint64_t seed = (uint64_t)random(1, 1000000000LL);
    const int WW = 3600;

    // ---------- PASS 1: floor + underground for every tile ----------
    for(int y = 0; y < WW; y++){
        for(int x = 0; x < WW; x++){
            Biome b = wg_get_biome(x, y, seed);

            Long floor_id = wg_floor_id(b);
            Long ug_id    = wg_underground_id(b);

            // River override (a "ridge" of water snaking through the map)
            double rn = wg_noise2((double)x, (double)y, 1.0/500.0, seed ^ 0x9999);
            if(fabs(rn - 0.5) <= 0.012){
                world.grid[y][x][2] = ID_AIR;
                world.grid[y][x][0] = ID_GRANITE;
                if(b == BIOME_SNOWY_PLAINS) world.grid[y][x][2] = ID_ICE;
                else if(b != BIOME_VOLCANO) world.grid[y][x][1] = ID_WATER;
                else world.grid[y][x][1] = ID_LAVA;



                continue;
            }

            world.grid[y][x][2] = floor_id;
            world.grid[y][x][0] = ug_id;
        }
    }

    // ---------- PASS 2: underground ore patches (type 0, invisible until mining) ----------
    for(int i = 0; i < 10000; i++){
        int ox = wg_randi(0, WW - 1, (uint64_t)i, 1, seed);
        int oy = wg_randi(0, WW - 1, (uint64_t)i, 2, seed);
        int rad = random(1,4);
        double r = wg_rand01((uint64_t)i, 4, seed);

        Biome b = wg_get_biome(ox, oy, seed);
        Long ore;
        if(b == BIOME_STONY_MOUNTAIN){
            if(r <= 0.4){
                rad = wg_randi(4, 9,(uint64_t)i, 3, seed);
                ore = ID_COAL_ORE;
            }
            else if(r <= 0.8){
                rad = wg_randi(2, 5,(uint64_t)i, 3, seed);
                ore = ID_IRON_ORE;
            }
            else if(r <= 0.9){
                rad = wg_randi(1, 3,(uint64_t)i, 3, seed);
                ore = ID_GOLD_ORE;
            }
            else if(r <= 0.95){
                rad = wg_randi(1, 2,(uint64_t)i, 3, seed);
                ore = ID_EMERALD;
            }
            else{
                rad = wg_randi(1, 2,(uint64_t)i, 3, seed);
                ore = ID_RUBY;
            }
        } 
        else if(b == BIOME_MOUNTAIN_PEAK){
            if(r <= 0.2){
                rad = wg_randi(4, 9,(uint64_t)i, 3, seed);
                ore = ID_COAL_ORE;
            }
            else if(r <= 0.5){
                rad = wg_randi(3, 6,(uint64_t)i, 3, seed);
                ore = ID_IRON_ORE;
            }
            else if(r <= 0.75){
                rad = wg_randi(2, 4,(uint64_t)i, 3, seed);
                ore = ID_GOLD_ORE;
            }
            else if(r <= 0.845){
                rad = wg_randi(1, 3,(uint64_t)i, 3, seed);
                ore = ID_EMERALD;
            }
            else if(r <= 0.94){
                rad = wg_randi(1, 3,(uint64_t)i, 3, seed);
                ore = ID_RUBY;
            }
            else{
                rad = wg_randi(1, 2,(uint64_t)i, 3, seed);
                ore = ID_DIAMOND;
            }
        } 
        else if(b == BIOME_FROZEN_RIVER or b == BIOME_RIVER){
            if(r <= 0.4){
                rad = wg_randi(5, 11,(uint64_t)i, 3, seed);
                ore = ID_COAL_ORE;
            }
            else if(r <= 0.75){
                rad = wg_randi(2, 4,(uint64_t)i, 3, seed);
                ore = ID_COPPER_ORE;
            }
            else if(r <= 0.9){
                rad = wg_randi(1, 4,(uint64_t)i, 3, seed);
                ore = ID_IRON_ORE;
            }
            else if(r <= 0.96){
                rad = wg_randi(1, 2,(uint64_t)i, 3, seed);
                ore = ID_RUBY;
            }
            else{
                rad = wg_randi(1, 2,(uint64_t)i, 3, seed);
                ore = ID_DIAMOND;
            }
        } 
        else if(b == BIOME_VOLCANO){
            if(r <= 0.3){
                rad = wg_randi(6, 13,(uint64_t)i, 3, seed);
                ore = ID_COAL_ORE;
            }
            else if(r <= 0.7){
                rad = wg_randi(2, 6,(uint64_t)i, 3, seed);
                ore = ID_GOLD_ORE;
            }
            else if(r <= 0.9){
                rad = wg_randi(1, 2,(uint64_t)i, 3, seed);
                ore = ID_DIAMOND;
            }
            else{
                rad = wg_randi(1, 2,(uint64_t)i, 3, seed);
                ore = ID_MAGMA;
            }
        } 
        else if(b == BIOME_DESERT){
            if(r <= 0.2){
                rad = wg_randi(5, 11,(uint64_t)i, 3, seed);
                ore = ID_COAL_ORE;
            }
            else if(r <= 0.4){
                rad = wg_randi(3, 5,(uint64_t)i, 3, seed);
                ore = ID_OIL;
            }
            else if(r <= 0.7){
                rad = wg_randi(2, 7,(uint64_t)i, 3, seed);
                ore = ID_GOLD_ORE;
            }
            else if(r <= 0.8){
                rad = wg_randi(1, 4,(uint64_t)i, 3, seed);
                ore = ID_IRON_ORE;
            }
            else if(r <= 0.9){
                rad = wg_randi(2, 5,(uint64_t)i, 3, seed);
                ore = ID_COPPER_ORE;
            }
            else if(r <= 0.96){
                rad = wg_randi(1, 2,(uint64_t)i, 3, seed);
                ore = ID_WATER;
            }
            else{
                rad = 1;
                ore = ID_DIAMOND;
            }
        } 
        else if(b == BIOME_MUSHROOM_PLAINS){
            if(r <= 0.4){
                rad = wg_randi(3, 7,(uint64_t)i, 3, seed);
                ore = ID_COPPER_ORE;
            }
            else if(r <= 0.7){
                rad = wg_randi(2, 6,(uint64_t)i, 3, seed);
                ore = ID_IRON_ORE;
            }
            else if(r <= 0.8){
                rad = wg_randi(1, 4,(uint64_t)i, 3, seed);
                ore = ID_GOLD_ORE;
            }
            else if(r <= 0.89){
                rad = wg_randi(1, 2,(uint64_t)i, 3, seed);
                ore = ID_RUBY;
            }
            else{
                rad = 2;
                ore = ID_MUSHROOM;
            }
        } 
        else{
            if(r <= 0.45){
                rad = wg_randi(2, 7,(uint64_t)i, 3, seed);
                ore = ID_COAL_ORE;
            }
            else if(r <= 0.7){
                rad = wg_randi(1, 5,(uint64_t)i, 3, seed);
                ore = ID_COPPER_ORE;
            }
            else if(r <= 0.88){
                rad = wg_randi(1, 4,(uint64_t)i, 3, seed);
                ore = ID_IRON_ORE;
            }
            else{
                rad = wg_randi(1, 2,(uint64_t)i, 3, seed);
                ore = ID_GOLD_ORE;
            }
        }

        for(int dy = -rad-4; dy <= rad+4; dy++){
            for(int dx = -rad-4; dx <= rad+4; dx++){
                Long rd = random(-1,1);
                if(dx*dx + dy*dy > (rad+rd)*(rad+rd)) continue;
                int x = ox + dx, y = oy + dy;
                if((unsigned)x >= 3600u or (unsigned)y >= 3600u) continue;
                Long cur = world.grid[y][x][0];
                if(cur == ID_DIRT or cur == ID_STONE or cur == ID_GRANITE or cur == ID_SANDSTONE){
                    world.grid[y][x][0] = ore;
                }
            }
        }
    }

    // ---------- PASS 3: biome features (trees, mushrooms, walls, snowdrifts) ----------
    for(int y = 2; y < WW - 2; y++){
        for(int x = 2; x < WW - 2; x++){
            // skip tiles that are water
            if(world.grid[y][x][1] != ID_AIR or world.grid[y][x][2] == ID_ICE) continue;
            // skip tiles that already have a wall
            if(world.grid[y][x][3] != ID_AIR) continue;
            if(world.grid[y][x][4] != ID_AIR) continue;

            Biome b = wg_get_biome(x, y, seed);
            double r = wg_rand01((uint64_t)x, (uint64_t)y, seed ^ 0xCAFE);

            switch(b){
                case BIOME_PLAINS:
                    if(r < 0.005) wg_tree(world, x, y, seed, 1, 2, 3);
                    break;
                case BIOME_FOREST:
                    if(r < 0.150) wg_tree(world, x, y, seed, 2, 2, 3);
                    break;
                case BIOME_JUNGLE:
                    if(r < 0.220) wg_tree(world, x, y, seed, 3, 3, 4);
                    break;
                case BIOME_SNOWY_PLAINS:
                    if(r < 0.005) wg_tree(world, x, y, seed, 1, 1, 3);
                    break;
                case BIOME_DESERT:
                    if(r < 0.004) world.grid[y][x][3] = ID_WOOD; // dead tree
                    break;
                case BIOME_MUSHROOM_PLAINS:
                    if(r < 0.130) world.grid[y][x][2] = ID_MUSHROOM;
                    break;
                case BIOME_STONY_MOUNTAIN:
                    world.grid[y][x][3] = ID_STONE;
                    world.grid[y][x][2] = ID_STONE;
                    break;
                case BIOME_MOUNTAIN_PEAK:
                    world.grid[y][x][3] = ID_STONE;
                    world.grid[y][x][2] = ID_STONE;
                    if(r < 0.750) world.grid[y][x][4] = ID_PACKED_ICE;
                    else if(r < 0.950) world.grid[y][x][4] = ID_ICE;
                    else world.grid[y][x][4] = ID_SNOW;
                    break;
                case BIOME_VOLCANO:
                    if(r < 0.650) world.grid[y][x][2] = ID_STONE;
                    else if(r&lt;0.690) world.grid[y][x][2] = ID_COAL_ORE;
                    else if(r&lt;0.691) world.grid[y][x][2] = ID_DIAMOND;
                    break;
                default: break;
            }
        }
    }
    // ---------- PASS 3.5: wall paths (grass in plains, snow in snowy plains) ----------
    for(int y = 2; y < WW - 2; y++){
        for(int x = 2; x < WW - 2; x++){
            if(world.grid[y][x][1] != ID_AIR) continue;   // don't cut through liquid
            //if(world.grid[y][x][3] != ID_AIR) continue;   // don't overwrite trees / walls

            Biome b = wg_get_biome(x, y, seed);
            if(b != BIOME_PLAINS and b != BIOME_SNOWY_PLAINS and b != BIOME_DESERT and b != BIOME_FOREST and b != BIOME_MUSHROOM_PLAINS) continue;

            // Ridge noise — a thin band near 0.5 forms a snaking line
            double v = wg_noise2((double)x, (double)y, 1.0/220.0, seed ^ 0xBEEF);

            // Vary thickness a little so it doesn't look like a uniform stripe
            double thick = 0.006 + wg_rand01((uint64_t)x, (uint64_t)y, seed ^ 0xBEEF2) * 0.010;

            short uid = ID_GRASS;
            short did = ID_DIRT;
            if(b == BIOME_SNOWY_PLAINS) uid = ID_SNOW;
            else if(b == BIOME_DESERT){
                uid = ID_SAND;
                did = ID_SANDSTONE;
            }
            else if(b == BIOME_MUSHROOM_PLAINS){
                uid = ID_MYCELIUM;
            }

            if(fabs(v - 0.5) < thick){
                world.grid[y][x][3] = uid;
                world.grid[y][x][2] = did;
            }
        }
    }

    // ---------- PASS 4: surface ore mounds (wall1 shell + ore inside) ----------
    for(int i = 0; i < 800; i++){
        int mx = wg_randi(20, WW - 20, (uint64_t)i, 20, seed);
        int my = wg_randi(20, WW - 20, (uint64_t)i, 21, seed);
        Biome b = wg_get_biome(mx, my, seed);

        Long outer = (b == BIOME_DESERT) ? ID_SANDSTONE : (random(1,100) <= 70 ? ID_STONE : ID_GRANITE);
        Long inner;
        double r = wg_rand01((uint64_t)i, 22, seed);
        if(b == BIOME_DESERT){
            inner = (r < 0.5) ? ID_GOLD_ORE : (r < 0.8 ? ID_IRON_ORE : ID_COAL_ORE);
        } 
        else if(b == BIOME_MUSHROOM_PLAINS){
            inner = (r < 0.7) ? ID_COPPER_ORE : ID_COAL_ORE;
        } 
        else {
            inner = (r < 0.6) ? ID_COAL_ORE : ID_IRON_ORE;
        }

        double radius = wg_randi(8, 14, (uint64_t)i, 23, seed);
        for(int dy = -radius-5; dy <= radius+5; dy++){
            for(int dx = -radius-5; dx <= radius+5; dx++){
                double d = sqrt((double)(dx*dx + dy*dy));
                if(d > radius + random(0,3)) continue;
                int x = mx + dx, y = my + dy;
                if((unsigned)x >= 3600u or (unsigned)y >= 3600u) continue;

                if(d <= radius*0.4 + random(0,2)){
                    if(random(0,100) <= 25){
                        wg_place(world, mx + dx, my + dy, 3, inner);
                    }
                    else{
                        wg_place(world, mx + dx, my + dy, 3, outer);
                    }
                }
                else{
                    wg_place(world, mx + dx, my + dy, 3, outer);
                }

            }
        }
    }

    // ---------- PASS 4.5: stony mountain / mountain peak surface ores ----------
    for(int i = 0; i < 1500; i++){
        int mx = wg_randi(20, WW - 20, (uint64_t)i, 70, seed);
        int my = wg_randi(20, WW - 20, (uint64_t)i, 71, seed);
        Biome b = wg_get_biome(mx, my, seed);
        if(b != BIOME_STONY_MOUNTAIN) continue;

        int radius = wg_randi(3, 7, (uint64_t)i, 72, seed);
        double r = wg_rand01((uint64_t)i, 73, seed);

        Long ore = ID_GRANITE;

        for(int dy = -radius; dy <= radius; dy++){
            for(int dx = -radius; dx <= radius; dx++){
                double d = sqrt((double)(dx*dx + dy*dy));
                double jitter = wg_rand01((uint64_t)i, (uint64_t)(dx*4099 + dy*131), seed ^ 0xACE);
                if(d > radius + jitter * 1.5) continue;

                int x = mx + dx, y = my + dy;
                if((unsigned)x >= 3600u || (unsigned)y >= 3600u) continue;
                if(world.grid[y][x][1] != ID_AIR) continue;

                if(b == BIOME_MOUNTAIN_PEAK){

                }
                else{
                    if(world.grid[y][x][4] != ID_AIR) continue;
                    if(d < radius * 0.55){
                        world.grid[y][x][3] = ore;
                        if(random(1,100) < 45) world.grid[y][x][2] = ore;
                    } 
                    else if(wg_rand01((uint64_t)x, (uint64_t)y, seed ^ 0xACE3) < 0.45){
                        world.grid[y][x][3] = ore;
                        if(random(1,100) < 35) world.grid[y][x][2] = ore;
                    }
                }
            }
        }
    }

    for(int i = 0; i < 2000; i++){
        int mx = wg_randi(20, WW - 20, (uint64_t)i, 60, seed);
        int my = wg_randi(20, WW - 20, (uint64_t)i, 61, seed);
        Biome b = wg_get_biome(mx, my, seed);
        if(b != BIOME_STONY_MOUNTAIN && b != BIOME_MOUNTAIN_PEAK) continue;

        int radius = wg_randi(3, 7, (uint64_t)i, 62, seed);
        double r = wg_rand01((uint64_t)i, 63, seed);

        Long ore;
        if(b == BIOME_MOUNTAIN_PEAK){
            // Surface emerald, with a bit of diamond / ruby as a bonus

            if(r < 0.70) ore = ID_IRON_ORE;
            else{
                radius = wg_randi(1, 3, (uint64_t)i, 62, seed);
                if(r < 0.90)      ore = ID_EMERALD;
                else if(r < 0.95) ore = ID_DIAMOND;
                else              ore = ID_RUBY;
            }
        }
        else {
            // Stony mountain: much more coal & iron
            if(r < 0.55)      ore = ID_COAL_ORE;
            else if(r < 0.92) ore = ID_IRON_ORE;
            else{
                ore = ID_EMERALD;
                radius = wg_randi(1, 3, (uint64_t)i, 62, seed);
            }
        }

        for(int dy = -radius; dy <= radius; dy++){
            for(int dx = -radius; dx <= radius; dx++){
                double d = sqrt((double)(dx*dx + dy*dy));
                double jitter = wg_rand01((uint64_t)i, (uint64_t)(dx*4099 + dy*131), seed ^ 0xACE);
                if(d > radius + jitter * 1.5) continue;

                int x = mx + dx, y = my + dy;
                if((unsigned)x >= 3600u || (unsigned)y >= 3600u) continue;
                if(world.grid[y][x][1] != ID_AIR) continue;   // don't touch liquid

                if(b == BIOME_MOUNTAIN_PEAK){
                    // Peaks render on layer 4/3 (packed ice / stone wall2),
                    // so put the ore there to make it visible.
                    if(d < radius * 0.55){
                        world.grid[y][x][4] = ore;                      // solid core
                        if(random(1,100) < 30) world.grid[y][x][3] = ore;
                        if(random(1,100) < 8) world.grid[y][x][2] = ore;
                    } 
                    else if(wg_rand01((uint64_t)x, (uint64_t)y, seed ^ 0xACE2) < 0.45){
                        world.grid[y][x][4] = ore;                      // mottled rim
                        if(random(1,100) < 20) world.grid[y][x][3] = ore;
                        if(random(1,100) < 6) world.grid[y][x][2] = ore;
                    }
                }
                else{
                    if(world.grid[y][x][4] != ID_AIR) continue;
                    if(d < radius * 0.55){
                        world.grid[y][x][3] = ore;                      // solid core
                        if(random(1,100) < 45) world.grid[y][x][2] = ore;
                    } 
                    else if(wg_rand01((uint64_t)x, (uint64_t)y, seed ^ 0xACE3) < 0.45){
                        world.grid[y][x][3] = ore;                      // mottled rim
                        if(random(1,100) < 35) world.grid[y][x][2] = ore;
                    }
                }
            }
        }

        mx = wg_randi(20, WW - 20, (uint64_t)i, 60, seed);
        my = wg_randi(20, WW - 20, (uint64_t)i, 61, seed);
        radius = wg_randi(3, 7, (uint64_t)i, 62, seed);

        for(int dy = -radius; dy <= radius; dy++){
            for(int dx = -radius; dx <= radius; dx++){
                double d = sqrt((double)(dx*dx + dy*dy));
                double jitter = wg_rand01((uint64_t)i, (uint64_t)(dx*4099 + dy*131), seed ^ 0xACE);
                if(d > radius + jitter * 1.5) continue;

                int x = mx + dx, y = my + dy;
                if((unsigned)x >= 3600u || (unsigned)y >= 3600u) continue;
                if(world.grid[y][x][1] != ID_AIR) continue;

                if(b == BIOME_MOUNTAIN_PEAK){
                    if(d < radius * 0.55){
                        world.grid[y][x][2] = ore;
                    } 
                    else if(wg_rand01((uint64_t)x, (uint64_t)y, seed ^ 0xACE2) < 0.45){
                        world.grid[y][x][2] = ore;
                    }
                }
                else{
                    if(world.grid[y][x][4] != ID_AIR) continue;
                    if(d < radius * 0.55){
                        world.grid[y][x][2] = ore; 
                    } 
                    else if(wg_rand01((uint64_t)x, (uint64_t)y, seed ^ 0xACE3) < 0.45){
                        world.grid[y][x][2] = ore; 
                    }
                }
            }
        }
    }

    // ---------- PASS 5: biome-specific ponds / lakes ----------
    for(int i = 0; i < 400; i++){
        int px = wg_randi(10, WW - 10, (uint64_t)i, 30, seed);
        int py = wg_randi(10, WW - 10, (uint64_t)i, 31, seed);
        Biome b = wg_get_biome(px, py, seed);

        Long liquid = ID_AIR;
        double chance = 0.0;
        switch(b){
            case BIOME_PLAINS:          chance = 0.04; liquid = ID_WATER; break;
            case BIOME_FOREST:          chance = 0.05; liquid = ID_WATER; break;
            case BIOME_JUNGLE:          chance = 0.06; liquid = ID_WATER; break;
            case BIOME_SNOWY_PLAINS:    chance = 0.05; liquid = ID_ICE; break;
            case BIOME_DESERT:          chance = 0.10; liquid = ID_OIL;   break;
            case BIOME_VOLCANO:         chance = 0.25; liquid = ID_LAVA;  break;
            default: chance = 0.0; break;
        }
        double r = wg_rand01((uint64_t)i, 32, seed);
        if(r > chance) continue;

        int rx = wg_randi(4, 14, (uint64_t)i, 33, seed);
        int ry = wg_randi(4, 14, (uint64_t)i, 34, seed);

        for(int dy = -ry; dy <= ry; dy++){
            for(int dx = -rx; dx <= rx; dx++){
                double d = (double)(dx*dx)/(rx*rx) + (double)(dy*dy)/(ry*ry);
                if(d > 1.0) continue;
                int x = px + dx, y = py + dy;
                if((unsigned)x >= 3600u or (unsigned)y >= 3600u) continue;

                world.grid[y][x][3] = ID_AIR;   // clear any wall
                world.grid[y][x][4] = ID_AIR;
                world.grid[y][x][1] = liquid;
                world.grid[y][x][2] = ID_AIR;   // water replaces floor
                if(liquid == ID_LAVA) world.grid[y][x][0] = ID_OBSIDIAN;
            }
        }
    }

    // ---------- PASS 6: volcano cones ----------
    for(int i = 0; i < 90; i++){
        int vx = wg_randi(80, WW - 80, (uint64_t)i, 40, seed);
        int vy = wg_randi(80, WW - 80, (uint64_t)i, 41, seed);

        // only place inside a volcano biome
        if(wg_get_biome(vx, vy, seed) != BIOME_VOLCANO) continue;

        double radius = wg_randi(18, 36, (uint64_t)i, 42, seed);

        for(int dy = -radius - 2; dy <= radius + 2; dy++){
            for(int dx = -radius - 2; dx <= radius + 2; dx++){
                double d = sqrt((double)(dx*dx + dy*dy));

                // ragged edge
                double jitter = wg_rand01((uint64_t)i, (uint64_t)(dx*4099 + dy*131), seed ^ 0xFEED);
                if(d > radius + jitter * 2.0) continue;

                int x = vx + dx, y = vy + dy;
                if((unsigned)x >= 3600u or (unsigned)y >= 3600u) continue;

                if(d < radius * 0.30 + random(0,1)){
                    // core: lava pool (the only place layer 1 is used)
                    world.grid[y][x][0] = ID_OBSIDIAN;
                    world.grid[y][x][1] = ID_LAVA;
                    world.grid[y][x][2] = ID_AIR;
                    world.grid[y][x][3] = ID_AIR;
                    world.grid[y][x][4] = ID_AIR;
                }
                else if(d < radius * 0.55 + random(0,1)){
                    // inner cone: magma floor, magma wall1
                    world.grid[y][x][0] = ID_MAGMA;
                    world.grid[y][x][1] = ID_AIR;
                    world.grid[y][x][2] = ID_MAGMA;
                    world.grid[y][x][3] = ID_MAGMA;
                    world.grid[y][x][4] = ID_AIR;
                }
                else if(d < radius * 0.70 + random(0,1)){
                    // middle ring: obsidian floor, magma wall1, magma wall2
                    world.grid[y][x][0] = ID_OBSIDIAN;
                    world.grid[y][x][1] = ID_AIR;
                    world.grid[y][x][2] = ID_OBSIDIAN;
                    world.grid[y][x][3] = (random(0,100) <= 4 ? ID_DIAMOND : ID_MAGMA);
                    world.grid[y][x][4] = ID_MAGMA;
                }
                else if(d < radius * 0.9 + random(0,1)){
                    // outer ring: obsidian floor only, blends into surrounding magma biome
                    world.grid[y][x][0] = ID_OBSIDIAN;
                    world.grid[y][x][1] = ID_AIR;
                    world.grid[y][x][2] = (random(0,100) <= 2 ? ID_DIAMOND : ID_OBSIDIAN);
                    world.grid[y][x][3] = ID_AIR;
                    world.grid[y][x][4] = ID_AIR;
                }
                else{
                    world.grid[y][x][1] = ID_AIR;
                    world.grid[y][x][2] = ID_MAGMA;
                    world.grid[y][x][3] = ID_AIR;
                    world.grid[y][x][4] = ID_AIR;
                }
            }
        }
    }

    // ---------- spawn ----------


    int spawn_x = random(1600,2000), spawn_y = random(1600,2000);
    world.grid[spawn_y][spawn_x][3] = world.grid[spawn_y][spawn_x][4] = ID_AIR;
    world.grid[spawn_y+1][spawn_x][3] = world.grid[spawn_y+1][spawn_x][4] = ID_AIR;
    world.grid[spawn_y][spawn_x+1][3] = world.grid[spawn_y][spawn_x+1][4] = ID_AIR;
    world.grid[spawn_y+1][spawn_x+1][3] = world.grid[spawn_y+1][spawn_x+1][4] = ID_AIR;

    world.grid[spawn_y][spawn_x][2] = ID_STONE;
    world.grid[spawn_y+1][spawn_x][2] = ID_STONE;
    world.grid[spawn_y][spawn_x+1][2] = ID_STONE;
    world.grid[spawn_y+1][spawn_x+1][2] = ID_STONE;

    world.players[0].x = (double)spawn_x;
    world.players[0].y = (double)spawn_y;

    world.gen = 1;
}


// ==================== END WORLD GEN ====================================================================

// ==================== WORLD UPDATE ================================================================================

int render_layer = 4;

static int minex = -1;
static int miney = -1;
static int mine_layer = -1;
static double mine_block_hp = 0;
static double mine_reset = 0;

static bool workbench_open = 0;
static item_data input_item[4];
static item_data output_item;
static int output_item_count = 0;
static int output_index = 0;
const int maxium_same_output = 64;
static int output_array[maxium_same_output];
static int match_recipes_co = 0;
static bool left_pt = 0;
static bool righ_pt = 0;

const double mine_reset_time = 7.0 / fps; // 7 frames at 70 fps

static void add_item_to_world(world_data & world, const item_entity & it){
    pll cpos = get_chunk(it.x,it.y);
    int cx = cpos.first;
    int cy = cpos.second;
    if(cx >= max_c or cy >= max_c) return;
    world.chunk_grid[cy][cx].items.push_back(it);
}

static bool add_item(player_data & p,item_data & it){
    if(it.id == 0 or it.misc[0] <= 0) return 0;

    // stack onto existing
    for(int i = 0;i&lt;20;i++){
        if(checkitem(p.inventory[i],it) and p.inventory[i].misc[0] < it.max_stack){

            Long pl = min(p.inventory[i].max_stack - p.inventory[i].misc[0],it.misc[0]);

            p.inventory[i].misc[0] += pl;
            it.misc[0] -= pl;
            if(it.misc[0] <= 0) return 1;
        }
    }

    // first empty slot
    for(int i = 0;i&lt;20;i++){
        if(p.inventory[i].id == 0){
            Long pl = min(it.max_stack,(int) it.misc[0]);

            p.inventory[i] = it;
            p.inventory[i].misc[0] = pl;

            it.misc[0] -= pl;
            if(it.misc[0] <= 0) return 1;
        }
    }



    return 0;
}

// ==================== CRAFTING ====================

struct recipe{
    item_data ingr[4];
    int ingr_count = 0;

    item_data out;
    int out_count = 1;

    bool need_workbench = 0;
};

static vector<recipe> recipes;

static void init_recipes(){
    if(!recipes.empty()) return;

    auto mk_ingr = [](int id,int count){
        item_data ri;

        ri = get_item_data(id);
        ri.name = get_name(id);
        ri.misc[0] = count;

        return ri;
    };

    auto add = [](vector<item_data> inp,item_data out, bool owb){
        recipe r;

        for(int i = 0;i&lt;inp.size() and i&lt;4; i++){
            r.ingr[i] = inp[i];
        }

        r.ingr_count = inp.size();
        r.out = out;
        r.out_count = out.misc[0];
        r.need_workbench = owb;

        recipes.push_back(r);
    };

    // 1 wood -> 4 planks
    add({ mk_ingr(ID_WOOD, 1) },
        mk_ingr(IT_PLANKS, 4), 0);

    // 1 planks -> 4 stick
    add({ mk_ingr(IT_PLANKS, 1) },
        mk_ingr(IT_STICK, 4), 0);

    // 2 planks + 1 leaf -> workbench
    add({ mk_ingr(IT_PLANKS, 2), mk_ingr(ID_LEAVES, 1) },
        mk_ingr(IT_WORKBENCH, 1), 0);

    // 1 stone + 2 stick + 3 leaf -> stone hammer
    add({ mk_ingr(ID_STONE, 1), mk_ingr(IT_STICK, 2), mk_ingr(ID_LEAVES, 3) },
        mk_ingr(IT_STONE_HAMMER, 1), 1);

    // 2 stone + 2 stick + 4 leaf -> stone axe
    add({ mk_ingr(ID_STONE, 2), mk_ingr(IT_STICK, 2), mk_ingr(ID_LEAVES, 4) },
        mk_ingr(IT_STONE_AXE, 1), 1);
}

// count how many ingredients have same data in crafting slot
static int count_in_slots(item_data * slots, int n, const item_data & target){

    int kq = 0;

    for(int i = 0;i&lt;n;i++){
        if(slots[i].id == 0) continue;

        if(checkitem(slots[i], target)) kq += (int)slots[i].misc[0];
    }
    return kq;
}

// check if a recipe matches the required ingredient
static bool recipe_can_match(const recipe & r, item_data * slots, int n_slots, bool is_wb){
    if(r.need_workbench and !is_wb) return 0;

    for(int i = 0;i&lt;n_slots;i++){
        if(slots[i].id == 0) continue;
        bool che = 0;

        for(int j = 0;j&lt;r.ingr_count; j++){
            if(checkitem(slots[i], r.ingr[j])){
                che =1; 
                break; 
            }
        }
        if(!che) return 0;
    }

    for(int j = 0;j&lt;r.ingr_count;j++){
        if(count_in_slots(slots, n_slots, r.ingr[j]) < r.ingr[j].misc[0]) return 0;
    }

    for(int i = 0; i < n_slots; i++) if(slots[i].id != 0) return 1;

    return 0;
}

// how many output it need to return
static int recipe_craft_count(const recipe & r, item_data * slots, int n_slots){
    int best = 1 << 30;

    for(int j = 0; j < r.ingr_count; j++){
        int have = count_in_slots(slots, n_slots, r.ingr[j]);
        int need = r.ingr[j].misc[0];

        if(need <= 0) continue;

        best = min(best, have / need);
    }

    return (best == (1 << 30)) ? 0 : best;
}

// update what output from the input
static void update_craft_matches(){
    init_recipes();

    match_recipes_co = 0;
    int n_slots = workbench_open ? 4 : 2;

    for(int i = 0;i&lt;recipes.size();i++){
        if(recipe_can_match(recipes[i], input_item, n_slots, workbench_open)){
            if(match_recipes_co < maxium_same_output) output_array[match_recipes_co++] = i;
        }
    }
    if(output_index >= match_recipes_co) output_index = 0;


    if(match_recipes_co > 0){
        const recipe & r = recipes[output_array[output_index]];
        int c = 1;
        // c = recipe_craft_count(r, input_item, n_slots);

        output_item = r.out;
        output_item_count = c * r.out_count;
    } 
    else{
        output_item = item_data();
        output_item_count = 0;
    }
}

// crafting update output thinggy 
static bool do_craft(player_data & cp, world_data & world){

    if(match_recipes_co == 0) return 0;
    int n_slots = workbench_open ? 4 : 2;

    const recipe & r = recipes[output_array[output_index]];
    int count = 1;
    //count = recipe_craft_count(r, input_item, n_slots);
    if(count <= 0) return 0;

    for(int j = 0;j&lt;r.ingr_count;j++){
        int need = r.ingr[j].misc[0] * count;

        for(int s = 0;s&lt;n_slots and need > 0;s++){
            if(input_item[s].id == 0) continue;

            if(checkitem(input_item[s], r.ingr[j])){
                int pl = min((int)input_item[s].misc[0], need);
                input_item[s].misc[0] -= pl;

                need -= pl;

                if(input_item[s].misc[0] <= 0) input_item[s] = item_data();
            }
        }
    }

    item_data out = r.out;
    out.misc[0] = count * r.out_count;
    if(!add_item(cp, out)){
        item_entity it;

        it.x = cp.x; it.y = cp.y;
        it.it = out;
        it.layer = 2; 
        it.hp = 5.0;

        add_item_to_world(world, it);
    }

    update_craft_matches();

    return 1;
}

// ==================== END CRAFTING ====================

static bool open_inv = 0;
static bool mouse_have_item = 0;
static bool mouse_click_pr = 0;
static item_data mouse_item;
static bool mouse_click_pr1 = 0;

int g_hl_x = -1, g_hl_y = -1;
static int pages = 0;

static int check_hover_slot(player_data & cp){
    if(open_inv){
        const int base_x = 27;
        const int base_y = HEIGHT - 16;
        for(int r = 0; r < 4; r++){
            for(int c = 0; c < 5; c++){
                int sx = base_x + c*5;
                int sy = base_y + r*4;
                if(mousex >= sx and mousex <= sx+4 and mousey >= sy and mousey <= sy+3){
                    return r*5 + c;
                }
            }
        }
    }
    else{
        const int base_x = 27;
        const int base_y = HEIGHT - 4;
        for(int i = 0; i < 5; i++){
            int sx = base_x + i*5;
            int sy = base_y;
            if(mousex >= sx and mousex <= sx+4 and mousey >= sy and mousey <= sy+3) return i;
        }
    }
    return -1;
}

static int check_hover_craft(int n_slots){
    const int base_x = 54;
    const int base_y = HEIGHT - 16;

    for(int i = 0; i < n_slots; i++){
        int sx = base_x + (i % 2) * 5;
        int sy = base_y + (i / 2) * 4;
        if(mousex >= sx and mousex <= sx+4 and mousey >= sy and mousey <= sy+3) return i;
    }

    int ox = base_x + 12;
    int oy = base_y + (n_slots > 2 ? 2 : 0);
    if(mousex >= ox and mousex <= ox+4 and mousey >= oy and mousey <= oy+3) return 4;


    int ax = base_x;
    int ay = base_y + (n_slots > 2 ? 9 : 5);
    if(mousex >= ax and mousex <= ax+2 and mousey >= ay and mousey <= ay+2) return 5;
    if(mousex >= ax+3 and mousex <= ax+5 and mousey >= ay and mousey <= ay+2) return 6;

    return -1;
}

static void inv_input(player_data & cp,world_data & world){
    if(key_pressed['E']){
        open_inv = !open_inv;
    }
    int hov = check_hover_slot(cp);


    for(int k = 0; k < 5; k++){
        if(key_pressed['1' + k]){
            if(hov >= 0 and hov != k and open_inv == 1){
                swap(cp.inventory[hov], cp.inventory[k]);
            } 
            else{
                cp.selected_hotbar = k;
            }
        }
    }
    if(mousewheel == 1){
        cp.selected_hotbar = (cp.selected_hotbar+1)%5;
    }
    else if(mousewheel == -1){
        cp.selected_hotbar = (cp.selected_hotbar-1)%5;
        if(cp.selected_hotbar < 0) cp.selected_hotbar = 4;
    }

    // right click
    bool clicked = mouseclick[1] and !mouse_click_pr1;
    mouse_click_pr1 = mouseclick[1];

    if(clicked){
        if(mouse_have_item){
            if(hov >= 0 and cp.inventory[hov].id == 0){
                cp.inventory[hov] = mouse_item;
                cp.inventory[hov].misc[0] = 1;
                mouse_item.misc[0] -= 1;
                if(mouse_item.misc[0] <= 0){
                    mouse_item = item_data();
                    mouse_have_item = 0;
                }
            }
            else if(hov >= 0 and checkitem(cp.inventory[hov],mouse_item)){
                int empt = cp.inventory[hov].max_stack - (int)cp.inventory[hov].misc[0];
                int pl = 1;

                if(empt > 0){
                    cp.inventory[hov].misc[0] += pl;
                    mouse_item.misc[0] -= pl;

                    if(mouse_item.misc[0] <= 0){
                        mouse_item = item_data();
                        mouse_have_item = 0;
                    }
                }
            }
            else if(hov >= 0){
                swap(cp.inventory[hov],mouse_item);
            }
            else if(mouse_item.id != 0){
                if(g_hl_x != -1){
                    item_entity it;
                    it.x = g_hl_x;
                    it.y = g_hl_y;
                    it.it = mouse_item;
                    it.it.misc[0] = 1;
                    int layer = -1;
                    for(int typ = 4; typ >= 0; typ--){
                        short bid = world.grid[g_hl_y][g_hl_x][typ];
                        if(bid != ID_AIR){ 
                            layer = typ; 
                            break; 
                        }
                    }
                    it.layer = layer;
                    it.hp = 5;

                    add_item_to_world(world,it);

                    mouse_item.misc[0]--;
                    if(mouse_item.misc[0] <= 0){
                        mouse_item = item_data();
                        mouse_have_item = 0;
                    }
                }
                else{
                    add_item(cp,mouse_item);
                }
            }
        }
        else{
            if(hov >= 0 and cp.inventory[hov].id != 0){
                mouse_item = cp.inventory[hov];
                int hm = (mouse_item.misc[0] + 1)/2;

                mouse_item.misc[0] = hm;
                cp.inventory[hov].misc[0] -= hm;

                if(cp.inventory[hov].misc[0] <= 0) cp.inventory[hov] = item_data();
                mouse_have_item = 1;
            }
        }
    }

    // left click
    clicked = mouseclick[0] and !mouse_click_pr;
    mouse_click_pr = mouseclick[0];
    if(!clicked) return;

    if(22 <= mousex and mousex <= 23 and mousey == HEIGHT-2 and (pages < recipes.size()/4)){
        pages += 1;
    }
    else if(3 <= mousex and mousex <= 4 and mousey == HEIGHT-2 and (pages > 0)){
        pages -= 1;
    }

    if(mouse_have_item){
        if(hov >= 0 and cp.inventory[hov].id == 0){
            cp.inventory[hov] = mouse_item;
            mouse_item = item_data();
            mouse_have_item = 0;
        }
        else if(hov >= 0 and checkitem(cp.inventory[hov],mouse_item)){
            int empt = cp.inventory[hov].max_stack - (int)cp.inventory[hov].misc[0];
            int amount = (int)mouse_item.misc[0];
            int pl = min(empt, amount);

            cp.inventory[hov].misc[0] += pl;
            mouse_item.misc[0] -= pl;

            if(mouse_item.misc[0] <= 0) mouse_have_item = 0;
        }
        else if(hov >= 0){
            swap(cp.inventory[hov],mouse_item);
        }
        else if(mouse_item.id != 0){
            if(g_hl_x != -1){
                item_entity it;
                it.x = g_hl_x;
                it.y = g_hl_y;
                it.it = mouse_item;
                int layer = -1;
                for(int typ = 4; typ >= 0; typ--){
                    short bid = world.grid[g_hl_y][g_hl_x][typ];
                    if(bid != ID_AIR){ 
                        layer = typ; 
                        break; 
                    }
                }
                it.layer = layer;
                it.hp = 5;
                add_item_to_world(world,it);
                mouse_item = item_data();
                mouse_have_item = 0;
            }
            else{
                add_item(cp,mouse_item);
            }
        }
    }
    else{
        if(hov >= 0 and cp.inventory[hov].id != 0){
            mouse_item = cp.inventory[hov];
            cp.inventory[hov] = item_data();
            mouse_have_item = 1;
        }
    }
}

// returns world tile the player is aiming at, max to cp.range.
static void get_mining_target(player_data & cp,int & tx,int & ty,double range = -1){
    tx = ty = -1;

    Long cam_start_x = (Long)cp.x - WIDTH/2  + 1;
    Long cam_start_y = (Long)cp.y - HEIGHT/2 + 1;

    double pcx = cp.x + 0.5;
    double pcy = cp.y + 0.5;

    double world_x = (double)(cam_start_x + mousex);
    double world_y = (double)(cam_start_y + mousey);

    double dx = world_x - pcx;
    double dy = world_y - pcy;
    double di = sqrt(dx*dx + dy*dy);

    if(range == -1){
        if(di > (double)cp.range){
            dx = dx / di * (double)cp.range;
            dy = dy / di * (double)cp.range;
            world_x = pcx + dx;
            world_y = pcy + dy;
        }
    }
    else{
        if(di > (double)range){
            dx = dx / di * range;
            dy = dy / di * range;
            world_x = pcx + dx;
            world_y = pcy + dy;
        }
    }

    int itx = (int)floor(world_x);
    int ity = (int)floor(world_y);

    if(itx >= 3600 or ity >= 3600) return;
    tx = itx; 
    ty = ity;
}

static bool check_mining_vision(player_data & cp,int tx,int ty,int target_layer,world_data & world){
    double pcx = cp.x + 0.5, pcy = cp.y + 0.5;
    double bcx = tx + 0.5,  bcy = ty + 0.5;

    double dx = bcx - pcx, dy = bcy - pcy;
    double di = sqrt(dx*dx + dy*dy);
    if(di < 0.01) return 1;

    int steps = (int)(di * 4.0);
    if(steps < 1) steps = 1;

    int px = (int)floor(cp.x), py = (int)floor(cp.y);
    auto is_player_tile = [&](int x,int y){
        return (x == px or x == px+1) and (y == py or y == py+1);
    };

    int player_h = cp.type;
    if(player_h < 0) player_h = 0;
    if(player_h > 4) player_h = 4;

    for(int s = 1; s < steps; s++){
        double t = (double)s / (double)steps;

        int itx = (int)floor(pcx + dx * t);
        int ity = (int)floor(pcy + dy * t);

        if((unsigned)itx >= 3600u or (unsigned)ity >= 3600u) continue;
        if(itx == tx and ity == ty) continue;
        if(is_player_tile(itx,ity)) continue;

        for(int lay = 4; lay > player_h; lay--){
            if(world.grid[ity][itx][lay] != ID_AIR){
                extern Long mousex, mousey;
                debug[1] = itx;
                debug[2] = ity;
                debug[3] = lay;
                return false;
            }
        }
    }
    return 1;
}

void handle_mining(player_data & cp, world_data & world){
    bool holding = (mouseclick[0] == 1);
    int tx = -1,ty = -1;

    for(double ran = cp.range;ran >= 0.0;ran-=0.5){
        get_mining_target(cp, tx, ty,ran);

        g_hl_x = g_hl_y = -1;
        if(tx >= 0 and ty >= 0){
            short top_block = ID_AIR;
            int layer = -1;
            for(int typ = cp.type+1;typ >= 0;typ--){
                short bid = world.grid[ty][tx][typ];
                if(bid != ID_AIR){ 
                    top_block = bid; 
                    layer = typ; 
                    break; 
                }
            }
            if(top_block != ID_AIR and check_mining_vision(cp, tx, ty, layer, world)){
                g_hl_x = tx;
                g_hl_y = ty;
                break;
            }
        }
    }

    if(check_hover_slot(cp) != -1) return;

    if(!holding){
        if(minex != -1){
            mine_reset += dt;
            if(mine_reset > mine_reset_time){
                minex = miney = mine_layer = -1;
                mine_block_hp = 0;
                mine_reset = 0;
            }
        }
        return;
    }

    if(tx < 0 or ty < 0) return;

    Long top_block = ID_AIR;
    int layer = -1;
    for(int typ = cp.type+1; typ >= 1; typ--){
        short bid = world.grid[ty][tx][typ];
        if(bid != ID_AIR){ 
            top_block = bid; 
            layer = typ; 
            break; 
        }
    }

    if(top_block == ID_AIR) return;
    if(!check_mining_vision(cp, tx, ty, layer, world)) return;

    block_data bi = get_block_data(top_block);
    if(bi.hp <= 0.0) return;

    if(tx != minex or ty != miney or layer != mine_layer){
        minex = tx;
        miney = ty;
        mine_layer = layer;
        mine_block_hp = bi.hp;
    }
    mine_reset = 0;

    mine_block_hp -= cp.mining_speed * dt;


    if(mine_block_hp <= 0){
        world.grid[ty][tx][layer] = ID_AIR;

        for(int i = 0; i < 4; i++){
            if(bi.drops[i].id == ID_AIR) continue;
            if((int)random(1,100) <= bi.drop_chance[i]){
                item_entity it;
                if(bi.is_simple_drop){
                    it.it = get_item_data(bi.drops[i].id);
                    it.it.name = get_name(bi.drops[i].id);
                }
                else it.it = bi.drops[i];

                it.it.misc[0]  = bi.drop_amount[i];
                it.x           = tx + 0.5;
                it.y           = ty + 0.5;
                it.layer       = layer;
                it.hp          = 5.0;
                it.spawn_time  = 0;

                add_item_to_world(world, it);
            }
        }

        minex = miney = mine_layer = -1;
        mine_block_hp = 0;
    }
}

const double item_pickup_rad = 2.5;
const double item_pickup_delay = 0.4;

void update_world(world_data & world,double dt){
    if(!world.iscreated) return;

    for(int cy = 0;cy&lt;max_c;cy++){
        for(int cx = 0;cx&lt;max_c;cx++){

            chunk & ch = world.chunk_grid[cy][cx];

            if(ch.items.empty() and ch.entities.empty() and ch.blocks.empty()) continue;
            bool che = 0;
            for(int pp = 0; pp < 8; pp++){
                player_data & pl = world.players[pp];
                if(pl.hp <= 0) continue;
                if(pp > 0 and pl.active == 0) continue;

                pll pos = get_chunk(pl.x,pl.y);

                if(pos.first - render_rad <= cx and cx <= pos.first + render_rad and pos.second - render_rad <= cy and cy <= pos.second + render_rad){
                    che = 1;
                    break;
                }
            }
            if(!che) continue;

            // ---------- items ----------
            for(int i = ch.items.size()-1;i>=0;i--){
                item_entity & it = ch.items[i];
                it.spawn_time += dt;

                if(it.hp <= 0){
                    ch.items.erase(ch.items.begin() + i);
                    continue;
                }
                if(it.spawn_time < item_pickup_delay) continue;

                int best_p  = -1;
                double best_d2 = item_pickup_rad * item_pickup_rad;

                for(int pp = 0; pp < 8; pp++){
                    player_data & pl = world.players[pp];
                    if(pl.hp <= 0) continue;
                    if(pp > 0 and pl.active == 0) continue;

                    double ddx = it.x - (pl.x + 0.5);
                    double ddy = it.y - (pl.y + 0.5);
                    double d2  = ddx*ddx + ddy*ddy;

                    if(d2 < best_d2){ 
                        best_d2 = d2; 
                        best_p = pp;
                    }
                }
                if(best_p < 0) continue;

                if(add_item(world.players[best_p], it.it)){
                    ch.items.erase(ch.items.begin() + i);
                }
            }

            // ---------- entities ----------
            for(int e = 0; e < ch.entities.size();e++){
                entity_data & en = ch.entities[e];
                // TODO: entity AI, hp decay, movement

            }
        }
    }
}

// ==================== END WORLD UPDATE ================================================================================


// ===================== RENDER =====================================================================================

void get_block_visuals(Long id, int layer, str & sym, rgb & fg, rgb & bg){
    sym = " ";
    fg = rgb(0, 0, 0);
    bg = rgb(0, 0, 0);

    switch (id){
        case ID_DIRT:       sym="."; fg=rgb(140,90,50);   bg=rgb(70,45,25);   break;
        case ID_SANDSTONE:  sym=":"; fg=rgb(215,190,130); bg=rgb(110,95,65);  break;
        case ID_STONE:      sym="#"; fg=rgb(150,150,150); bg=rgb(75,75,75);   break;
        case ID_GRANITE:    sym="#"; fg=rgb(190,140,140); bg=rgb(95,70,70);   break;
        case ID_MAGMA:      sym=u8"\u2591"; fg=rgb(255,90,10);   bg=rgb(90,25,0);    break;
        case ID_COPPER_ORE: sym="o"; fg=rgb(210,130,70);  bg=rgb(85,55,30);   break;
        case ID_IRON_ORE:   sym="o"; fg=rgb(210,190,170); bg=rgb(85,75,65);   break;
        case ID_GOLD_ORE:   sym="o"; fg=rgb(255,215,40);  bg=rgb(105,85,10);  break;
        case ID_COAL_ORE:   sym="o"; fg=rgb(45,45,45);    bg=rgb(20,20,20);   break;
        case ID_DIAMOND:    sym=u8"\u25CF"; fg=rgb(120,255,255); bg=rgb(25,90,90);   break;
        case ID_RUBY:       sym=u8"\u25CF"; fg=rgb(255,60,90);   bg=rgb(90,15,25);   break;
        case ID_EMERALD:    sym=u8"\u25CF"; fg=rgb(60,255,110);  bg=rgb(15,90,35);   break;
        case ID_PACKED_ICE: sym=u8"\u2593"; fg=rgb(60,120,170); bg=rgb(70,130,180); break;
        case ID_GAS_PACKET: sym="o"; fg=rgb(210,255,180); bg=rgb(65,85,45);   break;
        case ID_OBSIDIAN:   sym="#"; fg=rgb(90,45,130);   bg=rgb(35,15,55);   break;

        case ID_WATER:      sym="~"; fg=rgb(70,130,230);  bg=rgb(25,45,110);  break;
        case ID_LAVA:       sym="~"; fg=rgb(255,110,25);  bg=rgb(130,35,0);   break;
        case ID_OIL:        sym="~"; fg=rgb(65,55,45);    bg=rgb(25,20,15);   break;

        case ID_GRASS:      sym="\"";fg=rgb(70,210,70);   bg=rgb(25,85,25);   break;
        case ID_MYCELIUM:   sym="\'";fg=rgb(190,160,210); bg=rgb(85,65,105);  break;
        case ID_SAND:       sym=u8"\u2591"; fg=rgb(245,225,150); bg=rgb(125,115,65); break;
        case ID_SNOW:       sym="\"";fg=rgb(245,245,255); bg=rgb(125,125,145);break;
        case ID_ICE:        sym="."; fg=rgb(170,210,255); bg=rgb(65,105,155); break;
        case ID_WOOD:       sym="|"; fg=rgb(130,75,35);   bg=rgb(65,35,15);   break;
        case ID_LEAVES:     sym=u8"\u2592"; fg=rgb(45,160,45);   bg=rgb(15,65,15);   break;
        case ID_MUSHROOM:   sym=u8"\u2660"; fg=rgb(230,70,70);   bg=rgb(85,25,25);   break;

        case IT_PLANKS:       sym="="; fg=rgb(190,140,80);   break;

        case IT_WORKBENCH:    sym="W"; fg=rgb(230,190,110);  break;
    }

    double mul = 1.0;
    switch(layer){
        case 0: mul = 0.65; break;   // underground
        case 1: mul = 1.00; break;   // liquid
        case 2: mul = 1.00; break;   // floor
        case 3: mul = 1.20; break;   // wall 1
        case 4: mul = 1.45; break;   // wall 2
    }

    if(layer == 4){ fg.b = min(255, fg.b + 30); bg.b = min(255, bg.b + 30); }
    if(layer == 3){ fg.r = min(255, fg.r + 20); bg.r = min(255, bg.r + 20); }

    fg = scale_rgb(fg, mul);
    bg = scale_rgb(bg, mul);
}

static void get_item_visuals(int id, int isblock, str &sym, rgb & fg, rgb & bg){
    if(isblock){
        get_block_visuals(id, 2, sym, fg, bg);
        fg = scale_rgb(fg, 1.30);
        bg = rgb(0, 0, 0);
    } 
    else{
        bg = rgb(0, 0, 0);
        switch(id){

            case IT_STICK:          sym="/"; fg = rgb(170,120,70);   break;

            case IT_STONE_HAMMER:   sym=u8"\u2530"; fg=rgb(200,200,200);  break;
            case IT_STONE_AXE:      sym="T"; fg=rgb(180,180,180);  break;

            default:                sym="?"; fg=rgb(255,255,255);  break;
        }
    }
}

void render(Long x, Long y, world_data & world){
    Long cam_start_x = x - WIDTH/2 + 1;
    Long cam_start_y = y - HEIGHT/2 + 1;

    // block draw
    for(int scr_y = 0; scr_y < HEIGHT; scr_y++){
        for(int scr_x = 0; scr_x < WIDTH; scr_x++){
            Long wx = cam_start_x + scr_x;
            Long wy = cam_start_y + scr_y;

            if(wx >= 0 and wx < 3600 and wy >= 0 and wy < 3600){
                Long top_block = ID_AIR;
                int get_typ = 4;
                for(int typ = render_layer; typ >= 0; typ--){
                    Long bid = world.grid[wy][wx][typ];
                    if(bid != ID_AIR){ 
                        top_block = bid; 
                        get_typ = typ;
                        break;
                    }
                }

                str uni = " ";
                rgb fg = rgb(0,0,0), bg = rgb(0,0,0);
                if(top_block != ID_AIR) get_block_visuals(top_block, get_typ, uni, fg, bg);

                if(wy > 0){
                    bool above_is_wall1 = world.grid[wy-1][wx][3] != ID_AIR;
                    bool above_is_wall2 = world.grid[wy-1][wx][4] != ID_AIR;
                    if(above_is_wall2){
                        fg = scale_rgb(fg, 0.60);
                        bg = scale_rgb(bg, 0.60);
                    } 
                    else if(above_is_wall1){
                        fg = scale_rgb(fg, 0.80);
                        bg = scale_rgb(bg, 0.80);
                    }

                    above_is_wall1 = world.grid[wy][wx][3] != ID_AIR;
                    above_is_wall2 = world.grid[wy][wx][4] != ID_AIR;
                    if((above_is_wall1 and render_layer <= 2) or (above_is_wall2 and render_layer <= 3)){
                        fg = scale_rgb(fg, 0.70);
                        bg = scale_rgb(bg, 0.70);
                    } 
                }

                if(wx == g_hl_x and wy == g_hl_y){
                    fg = scale_rgb(fg, 1.55);
                    bg = scale_rgb(bg, 1.55);
                }

                set_pixel(scr_x, scr_y, uni, fg, bg);
            } 
            else{
                set_pixel(scr_x, scr_y, " ", rgb(0,0,0), rgb(0,0,0));
            }
        }
    }


    // item entity and entity draw
    Long mi_ch_x = max(0LL, cam_start_x / chunk_size);
    Long ma_ch_x = min(max_c - 1, (cam_start_x + WIDTH) / chunk_size);
    Long mi_ch_y = max(0LL, cam_start_y / chunk_size);
    Long ma_ch_y = min(max_c - 1, (cam_start_y + HEIGHT) / chunk_size);

    for(Long cy = mi_ch_y; cy <= ma_ch_y; cy++){
        for(Long cx = mi_ch_x; cx <= ma_ch_x; cx++){
            chunk & ch = world.chunk_grid[cy][cx];

            // ---- items ----
            for(const item_entity & it : ch.items){
                int wx = (int)floor(it.x);
                int wy = (int)floor(it.y);
                if((unsigned)wx >= 3600u or (unsigned)wy >= 3600u) continue;

                Long sx = wx - cam_start_x;
                Long sy = wy - cam_start_y;
                if(sx < 0 or sx >= WIDTH or sy < 0 or sy >= HEIGHT) continue;

                if(it.layer > render_layer) continue;
                int top_visible = 0;
                for(int d_lay = render_layer; d_lay >= 1; d_lay--){
                    if(world.grid[wy][wx][d_lay] != ID_AIR){
                        top_visible = d_lay; 
                        break;
                    }
                }
                if(top_visible != 0 and it.layer < top_visible) continue;

                str sym; 
                rgb fg,bg;
                get_item_visuals(it.it.id, it.it.isblock, sym, fg, bg);

                if(it.spawn_time < 0.35){
                    double k = 0.65 + 0.35 * sin(it.spawn_time * 40.0);
                    fg = scale_rgb(fg, k + 0.30);
                }
                set_pixel(sx, sy, sym, fg, bg);
            }

            // ---- entities ----
            for(const auto & ent : ch.entities){
                Long sx = (Long)ent.x - cam_start_x;
                Long sy = (Long)ent.y - cam_start_y;
                if(sx >= 0 and sx < WIDTH and sy >= 0 and sy < HEIGHT){
                    // TODO: draw entity
                }
            }
        }
    }


    // player draw
    for(int p_idx = 7; p_idx >= 0; p_idx--){
        const player_data & p = world.players[p_idx];

        if(p.hp <= 0 or (p.active == 0 and p_idx > 0)) continue;
        bool check_local = (p_idx == 0);

        str uni = check_local ? "@" : "P";
        rgb p_fg = check_local ? rgb(0, 255, 220) : rgb(255, 200, 50);
        rgb p_bg = check_local ? rgb(0, 60, 50)   : rgb(60, 40, 0);

        for(int dy = 0; dy <= 1; dy++){
            for(int dx = 0; dx <= 1; dx++){
                Long sx = (Long)(p.x + dx) - cam_start_x;
                Long sy = (Long)(p.y + dy) - cam_start_y;
                if(sx >= 0 and sx < WIDTH and sy >= 0 and sy < HEIGHT){

                    if(((p.type == 2 or p.type == 1) and p.type != 0 and (world.grid[(Long)(p.y + dy)][(Long)(p.x + dx)][3] == ID_AIR)+(render_layer < 3) and (world.grid[(Long)(p.y + dy)][(Long)(p.x + dx)][4] == ID_AIR)+(render_layer < 4)) or
                       (p.type == 3 and (world.grid[(Long)(p.y + dy)][(Long)(p.x + dx)][4] == ID_AIR)+(render_layer < 4)) or
                       (p.type == 4)){
                        set_pixel(sx, sy, uni, p_fg, p_bg);
                    }

                }
            }
        }
    }

}

bool can_pass(int type,int x,int y,world_data & world){
    if(type == 4) return 1;
    if(type == 3 and (world.grid[y][x][4] == ID_AIR or world.grid[y][x][4] == ID_LEAVES or world.grid[y][x][4] == ID_MUSHROOM)) return 1;
    if(type == 2 and (world.grid[y][x][3] == ID_AIR or world.grid[y][x][3] == ID_LEAVES or world.grid[y][x][3] == ID_MUSHROOM)) return 1;

    else return 0;

}

static void draw_slot(int sx,int sy,const item_data & it,bool highlight,bool select){
    rgb border_col = select ? rgb(255,220,100) : highlight ? rgb(210,210,140) : rgb(120,120,120);

    draw_rect(sx, sy, sx+4, sy+3, 1,
              {TOPLEF_CORNER,TOPRIG_CORNER,BOTLEF_CORNER,BOTRIG_CORNER},
              {LIGHT_HOZON},{LIGHT_VERTI},{" "},
              {border_col},{border_col},{border_col},{border_col});

    if(it.id != 0){
        str sym; rgb fg, bg;
        get_item_visuals(it.id, it.isblock, sym, fg, bg);
        set_pixel(sx+2, sy+1, sym, fg, bg);

        if(it.misc[0] >= 1){
            int sl = (int)it.misc[0];
            if(sl <= 9) draw_text(sx+2, sy+2, to_string(sl), rgb(255,255,200), rgb(-1));
            else draw_text(sx+1, sy+2, to_string(sl), rgb(255,255,200), rgb(-1));
        }
    }
}

static void draw_inv_hotbar(player_data & cp){
    const int base_x = 27;
    const int base_y = HEIGHT - 4;

    for(int i = 0; i < 5; i++){
        int sx = base_x + i*5;
        int sy = base_y;

        bool select = (i == cp.selected_hotbar);
        bool hover  = (mousex >= sx and mousex <= sx+4 and mousey >= sy and mousey <= sy+3);

        draw_slot(sx, sy, cp.inventory[i], hover, select);

        draw_text(sx+2, sy-1, to_string(i+1), rgb(180,180,180), rgb(0,0,0));
    }
}

static void draw_inv_expand(player_data & cp){
    const int base_x = 27;
    const int base_y = HEIGHT-16;

    draw_rect(base_x - 1, base_y - 1,
              base_x + 25, HEIGHT + 1,1,
              {TOPLEF_CORNER,TOPRIG_CORNER,BOTLEF_CORNER,BOTRIG_CORNER},
              {LIGHT_HOZON},{LIGHT_VERTI},{" "},
              {rgb(80,80,80)},{rgb(80,80,80)},{rgb(80,80,80)},{rgb(0,0,0)});

    for(int r = 0; r < 4; r++){
        for(int c = 0; c < 5; c++){
            int i = r*5 + c;
            int sx = base_x + c*5;
            int sy = base_y + r*4;

            bool select = (i == cp.selected_hotbar);
            bool hover  = (mousex >= sx and mousex <= sx+4 and mousey >= sy and mousey <= sy+3);

            if(r == 0) draw_text(sx+2, sy-1, to_string(i+1), rgb(180,180,180), rgb(0,0,0));
            draw_slot(sx, sy, cp.inventory[i], hover, select);
        }
    }
}

static void draw_recipe_box(player_data & cp){
    const int bx = 1, by = HEIGHT - 16;
    const int bw = 24, bh = 16;

    draw_rect(bx, by, bx + bw, by + bh, 1,
              {TOPLEF_CORNER,TOPRIG_CORNER,BOTLEF_CORNER,BOTRIG_CORNER},
              {LIGHT_HOZON},{LIGHT_VERTI},{" "},
              {rgb(90,90,90)},{rgb(90,90,90)},{rgb(90,90,90)},{rgb(0,0,0)});

    draw_text(bx + 1, by + 1, "Recipes: ", rgb(220,220,220));

    init_recipes();
    int row = by + 2;
    for(int i = pages*4; i < recipes.size() and i < (pages+1)*4 and row < by + bh - 1; i++){
        const recipe & r = recipes[i];
        bool wben = !r.need_workbench or workbench_open;

        rgb col = wben ? rgb(220,220,220) : rgb(120,120,120);

        // output icon
        str s; rgb f = rgb(0,0,0), b = rgb(0,0,0);
        get_item_visuals(r.out.id, r.out.isblock, s, f, b);
        set_pixel(bx + 1, row, s, f, b);

        // output name
        draw_text(bx + 3, row, r.out.name, col);

        // required item
        int x = bx + 3;
        for(int j = 0; j < r.ingr_count and x + 6 < bx + bw; j++){
            str is; 
            rgb ifg, ibg;
            get_item_visuals(r.ingr[j].id, r.ingr[j].isblock, is, ifg, ibg);

            set_pixel(x, row + 1, is, ifg, ibg);
            draw_text(x + 1, row + 1, "x" + to_string((int)r.ingr[j].misc[0]), col);

            x += 5;
        }

        if(r.need_workbench){
            draw_text(bw, row, "W", rgb(200, 160, 100));
        }

        row += 3;
    }

    rgb lc = (pages > 0) ? rgb(220,220,220) : rgb(90,90,90);
    rgb rc = (pages < recipes.size()/4) ? rgb(220,220,220) : rgb(90,90,90);

    draw_text(3,  HEIGHT-2, "<=", lc);
    draw_text(22, HEIGHT-2, "=>", rc);
}

static void draw_craft_panel(player_data & cp){
    int n_slots = workbench_open ? 4 : 2;
    const int base_x = 54;
    const int base_y = HEIGHT - 16;

    // input slot
    for(int i = 0; i < n_slots; i++){
        int sx = base_x + (i % 2) * 5;
        int sy = base_y + (i / 2) * 4;
        bool h = (mousex >= sx and mousex <= sx+4 and mousey >= sy and mousey <= sy+3);

        draw_slot(sx, sy, input_item[i], h, 0);
    }

    // arrow
    int mx = base_x + 10, my = base_y + (n_slots > 2 ? 3 : 0);
    draw_text(mx, my+1, "$$", rgb(200,200,200),rgb(0,0,0),' ',rgb(-1),rgb(-1),{LIGHT_HOZON});
    draw_text(mx, my+2, "$$", rgb(200,200,200),rgb(0,0,0),' ',rgb(-1),rgb(-1),{LIGHT_HOZON});

    // output slot
    int ox = base_x + 12;
    int oy = base_y + (n_slots > 2 ? 3 : 0);
    draw_slot(ox, oy, output_item, (mousex >= ox and mousex <= ox+4 and mousey >= oy and mousey <= oy+3), 0);

    // craft count text
    if(output_item_count > 0){
        draw_text(ox + 1, oy + 4, to_string(output_item_count) + "x", rgb(255,240,160));
    }

    // arrows (only if > 1 match)
    int ax = base_x + 12;
    int ay = base_y + (n_slots > 2 ? 8 : 4);

    rgb lc = (match_recipes_co > 1) ? rgb(220,220,220) : rgb(90,90,90);
    rgb rc = (match_recipes_co > 1) ? rgb(220,220,220) : rgb(90,90,90);

    draw_text(ax,     ay, "<=", lc);
    draw_text(ax + 3, ay, "=>", rc);
}

static void draw_mouse_item(){
    if(!mouse_have_item or mouse_item.id == 0) return;

    str sym; rgb fg, bg;
    get_item_visuals(mouse_item.id, mouse_item.isblock, sym, fg, bg);
    set_pixel(mousex, mousey, sym, fg, bg);

    if(mouse_item.misc[0] >= 1){
        int sl = (int)mouse_item.misc[0];
        if(sl <= 9) draw_text(mousex, mousey+1, to_string(sl), rgb(255,255,200), rgb(-1));
        else draw_text(mousex-1, mousey+1, to_string(sl), rgb(255,255,200), rgb(-1));
    }
}

// ===================== END RENDER =====================================================================================

int main(){

    // INIT ==================================================
    ios_base::sync_with_stdio(0); cin.tie(0);
    SetConsoleOutputCP(CP_UTF8);
    SetConsoleCP(CP_UTF8);

    set_square_font(font_size);
    setup(WIDTH, HEIGHT, scrposx, scrposy);
    set_console_buffer_size();
    lock_window_size(); 
    kill_console_selection();
    start_console_guard(); 

    fill(' ');
    srand(time(0));
    wait_clear(0);
    wait_ms(200);
    // END INIT ==================================================

    // MULTIPLAYER SECTION ==================================================

    WSADATA wsaData;
    if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0){
        init_socket_check = 1;
        /*
        cout << "FAH UR WINSOCK" << endl;
        return 0;
        */
    }
    SOCKET netSocket = INVALID_SOCKET;

    //END MULTIPLAYER SECTION ==================================================

    // MAIN FUNCTION1 ========================================================
    thread funcTest1(threadFunc); // main function2

    bool cu = 0;

    auto frame_start = std::chrono::steady_clock::now();
    Long frame_count = 0;
    double fps_timer = 0.0;
    Long display_fps = 0;

    auto last_send_time = std::chrono::steady_clock::now();

    while(1){
        // must start
        auto now = std::chrono::steady_clock::now();
        dt = duration<double>(now - frame_start).count(); // tinh bang giay
        frame_start = now;
        if(dt > 0.1) dt = 0.1;

        double elapsed_ms = duration&lt;double, milli>(now - last_send_time).count();

        //kill_console_selection();

        frame_count++;
        fps_timer += dt;

        if(fps_timer >= 0.5){
            display_fps = (Long)(frame_count / fps_timer);
            frame_count = 0;
            fps_timer = 0.0;
        }

        fill(' ', rgb(0,0,0), rgb(0,0,0));
        // must start

        auto_keycheck_mousepos();
        bool is_shift = (key[VK_SHIFT] or key[VK_LSHIFT] or key[VK_RSHIFT]);

        if(scr == 1){ // main menu ---------------------------------------------------------------------------------------------------------------------
            {
                if(!(28<= mousex and mousex <= 52 and 5<= mousey and mousey <=15)){
                    draw_rect(28,5,52,15,2,{FUL_BLOCK},{HOZON_DUO},{VERTI_DUO},{" "},{rgb(0,150,0)},{rgb(0,150,0)},{rgb(0,150,0)});
                    draw_text(34,10,"P   L   A   Y",rgb(50,175,50));
                }
                else{
                    draw_rect(28,5,52,15,2,{FUL_BLOCK},{HOZON_DUO},{VERTI_DUO},{" "},{rgb(190,150,0)},{rgb(190,150,0)},{rgb(190,150,0)});
                    draw_text(34,10,"P   L   A   Y",rgb(175,50,50));
                    if(mouseclick[0] == 1 and fade_state == 0){
                        scr = 2;
                    }
                }
                timer[0]-=dt;
                if(timer[0] <= 0){
                    if(!(28<= mousex and mousex <= 52 and 5<= mousey and mousey <=15)){
                        tempx = random(30,50);
                        tempy = random(7,13);
                        if(grid[tempy][tempx] == " ") deco.push_back(background(tempx,tempy,"+",rgb(random(200,255),random(200,255),random(200,255)),1,0.3));
                        timer[0] = 0.2;
                    }
                    else{
                        tempx = random(30,50);
                        tempy = random(7,13);
                        if(grid[tempy][tempx] == " ") deco.push_back(background(tempx,tempy,"?",rgb(random(200,255),random(0,10),random(0,10)),1,0.12));
                        timer[0] = 0.5;
                    }
                }


                if(!(8 <= mousex and mousex <= 32 and 20 <= mousey and mousey <=30)){
                    draw_rect(8,20,32,30,2,{FUL_BLOCK},{HOZON_DUO},{VERTI_DUO},{" "},{rgb(0,150,0)},{rgb(0,150,0)},{rgb(0,150,0)});
                    draw_text(13,25,"S E T T I N G S",rgb(50,175,50));
                }
                else{
                    draw_rect(8,20,32,30,2,{FUL_BLOCK},{HOZON_DUO},{VERTI_DUO},{" "},{rgb(190,150,0)},{rgb(190,150,0)},{rgb(190,150,0)});
                    draw_text(12,25,"S E T T I N G S ?",rgb(200,125,10));
                    if(mouseclick[0] == 1 and fade_state == 0){
                        scr = 3;
                    }
                }
                timer[1]-=dt;
                if(timer[1] <= 0){
                    if(!(8 <= mousex and mousex <= 32 and 20 <= mousey and mousey <=30)){
                        tempx = random(10,30);
                        tempy = random(22,28);
                        if(grid[tempy][tempx] == " ") deco.push_back(background(tempx,tempy,"+",rgb(random(200,255),random(200,255),random(200,255)),1,0.3));
                        timer[1] = 0.2;
                    }
                    else{
                        tempx = random(10,30);
                        tempy = random(22,28);
                        if(grid[tempy][tempx] == " ") deco.push_back(background(tempx,tempy,"!",rgb(random(200,255),random(100,200),random(0,10)),1,0.12));
                        timer[1] = 0.4;
                    }
                }

                if(!(48 <= mousex and mousex <= 72 and 20 <= mousey and mousey <=30)){
                    draw_rect(48,20,72,30,2,{FUL_BLOCK},{HOZON_DUO},{VERTI_DUO},{" "},{rgb(0,150,0)},{rgb(0,150,0)},{rgb(0,150,0)});
                    draw_text(53,25,"T U T O R I A L",rgb(50,175,50));
                }
                else{
                    draw_rect(48,20,72,30,2,{FUL_BLOCK},{HOZON_DUO},{VERTI_DUO},{" "},{rgb(190,150,0)},{rgb(190,150,0)},{rgb(190,150,0)});
                    draw_text(52,25,"T U T O R I A L ?",rgb(200,125,10));
                    if(mouseclick[0] == 1 and fade_state == 0){
                        scr = 4;
                    }
                }
                timer[2]-=dt;
                if(timer[2] <= 0){
                    if(!(48 <= mousex and mousex <= 72 and 20 <= mousey and mousey <=30)){
                        tempx = random(50,70);
                        tempy = random(22,28);
                        if(grid[tempy][tempx] == " ") deco.push_back(background(tempx,tempy,"+",rgb(random(200,255),random(200,255),random(200,255)),1,0.3));
                        timer[2] = 0.2;
                    }
                    else{
                        tempx = random(50,70);
                        tempy = random(22,28);
                        if(grid[tempy][tempx] == " ") deco.push_back(background(tempx,tempy,"+",rgb(random(100,200),random(200,255),random(0,10)),1,0.12));
                        timer[2] = 0.4;
                    }
                }

                timer[3]-=dt;
                if(timer[3] <= 0){
                    tempx = random(0,WIDTH-1);
                    tempy = random(0,HEIGHT-1);
                    if(grid[tempy][tempx] == " ") deco.push_back(background(tempx,tempy,"+",rgb(random(100,155),random(200,255),random(100,155)),1,0.42));

                    timer[3] = 0.07;
                }

                vector<background> ndeco;
                for(background & i : deco){
                    i.fade -= i.fade_minus*dt;
                    if(i.fade > 0.05){

                        draw_text(i.x,i.y,i.uni,scale_rgb(i.col,i.fade));
                        ndeco.push_back(background(i));
                    }
                }
                deco = move(ndeco);

                //draw_text(1,2,to_string(timer[0]));

                if(key[27] == 1 and fade_state == 0){
                    break;
                }
            }
        }
        else if(scr == 2){ // play ---------------------------------------------------------------------------------------------------------------------
            {
                if(!(28<= mousex and mousex <= 52 and 5<= mousey and mousey <=15)){
                    draw_rect(28,5,52,15,2,{FUL_BLOCK},{HOZON_DUO},{VERTI_DUO},{" "},{rgb(0,150,0)},{rgb(0,150,0)},{rgb(0,150,0)});
                    draw_text(33,10,"S U R V I V A L",rgb(50,175,50));
                }
                else{
                    draw_rect(28,5,52,15,2,{FUL_BLOCK},{HOZON_DUO},{VERTI_DUO},{" "},{rgb(200,100,0)},{rgb(200,100,0)},{rgb(200,100,0)});
                    draw_text(33,10,"S U R V I V A L",rgb(220,125,10));
                    if(mouseclick[0] == 1 and fade_state == 0){
                        scr = 5;
                    }
                }

                if(!(8 <= mousex and mousex <= 32 and 20 <= mousey and mousey <=30)){
                    draw_rect(8,20,32,30,2,{FUL_BLOCK},{HOZON_DUO},{VERTI_DUO},{" "},{rgb(0,150,0)},{rgb(0,150,0)},{rgb(0,150,0)});
                    draw_text(10,25,"F I G H T   A R E N A",rgb(50,175,50));
                }
                else{
                    draw_rect(8,20,32,30,2,{FUL_BLOCK},{HOZON_DUO},{VERTI_DUO},{" "},{rgb(200,100,0)},{rgb(200,100,0)},{rgb(200,100,0)});
                    draw_text(10,25,"F I G H T   A R E N A",rgb(220,125,10));
                    if(mouseclick[0] == 1 and fade_state == 0){
                        scr = 6;
                    }
                }

                if(!(48 <= mousex and mousex <= 72 and 20 <= mousey and mousey <=30)){
                    draw_rect(48,20,72,30,2,{FUL_BLOCK},{HOZON_DUO},{VERTI_DUO},{" "},{rgb(0,150,0)},{rgb(0,150,0)},{rgb(0,150,0)});
                    draw_text(55,24,"O N L I N E",rgb(50,175,50));
                    draw_text(56,26,"R O O M S",rgb(50,175,50));
                }
                else{
                    draw_rect(48,20,72,30,2,{FUL_BLOCK},{HOZON_DUO},{VERTI_DUO},{" "},{rgb(200,100,0)},{rgb(200,100,0)},{rgb(200,100,0)});
                    draw_text(55,24,"O N L I N E",rgb(220,125,10));
                    draw_text(56,26,"R O O M S",rgb(220,125,10));
                    if(mouseclick[0] == 1 and fade_state == 0){
                        scr = 7;
                    }
                }

                timer[3]-=dt;
                if(timer[3] <= 0){
                    tempx = random(0,WIDTH-1);
                    tempy = random(0,HEIGHT-1);
                    if(grid[tempy][tempx] == " ") deco.push_back(background(tempx,tempy,"?",rgb(random(200,155),random(50,100),15),1,0.5));

                    timer[3] = 0.05;
                }

                vector<background> ndeco;
                for(background & i : deco){
                    i.fade -= i.fade_minus*dt;
                    if(i.fade > 0.05){

                        draw_text(i.x,i.y,i.uni,scale_rgb(i.col,i.fade));
                        ndeco.push_back(background(i));
                    }
                }
                deco = move(ndeco);

                if(key[27] == 1 and fade_state == 0){
                    scr = 1;
                }
            }
        }
        else if(scr == 3){ // settings ---------------------------------------------------------------------------------------------------------------------
            draw_text(10,10,"setting WIP");

            if(key[27] == 1 and fade_state == 0){
                scr = 1;
            }
        }
        else if(scr == 4){ // tutorial ---------------------------------------------------------------------------------------------------------------------
            draw_text(10,10,"tuto WIP");

            if(key[27] == 1 and fade_state == 0){
                scr = 1;
            }
        }
        else if(scr == 5){ // survival ---------------------------------------------------------------------------------------------------------------------
            {
                Long stx = 7;
                Long sty = 10;

                for(Long id = 0;id < 6;id++){
                    if(id == 3){
                        sty += 15;
                        stx = 7;
                    }
                    if(!(stx <= mousex and mousex <= stx + 18 and sty <= mousey and mousey <=sty + 6)){
                        draw_rect(stx,sty,stx+18,sty+6,1,{TOPLEF_CORNER,TOPRIG_CORNER,BOTLEF_CORNER,BOTRIG_CORNER},{LIGHT_HOZON},{LIGHT_VERTI},{" "},{rgb(0,200,70)},{rgb(0,200,70)},{rgb(0,200,70)});
                        if(worlds[id].iscreated) draw_text(stx+1,sty+3,worlds[id].name,rgb(0,200,70));
                        else draw_text(stx+1,sty+3,"Empty world!",rgb(0,200,70));
                    }
                    else{
                        draw_rect(stx,sty,stx+18,sty+6,1,{TOPLEF_CORNER,TOPRIG_CORNER,BOTLEF_CORNER,BOTRIG_CORNER},{LIGHT_HOZON},{LIGHT_VERTI},{" "},{rgb(220,140,40)},{rgb(220,140,40)},{rgb(220,140,40)});
                        if(worlds[id].iscreated) draw_text(stx+1,sty+3,worlds[id].name,rgb(220,140,40));
                        else draw_text(stx+1,sty+3,"Empty world!",rgb(220,140,40));
                        if(mouseclick[0] == 1 and fade_state == 0){
                            scr = 8;
                            worldid = id;
                            if(!worlds[id].iscreated) type_mask[0] = 0;
                        }
                    }
                    stx += 18+6;
                }

                timer[3]-=dt;
                if(timer[3] <= 0){
                    tempx = random(0,WIDTH-1);
                    tempy = random(0,HEIGHT-1);
                    if(grid[tempy][tempx] == " ") deco.push_back(background(tempx,tempy,FUL_BLOCK,rgb(random(200,255),random(100,150),15),1,0.5));

                    timer[3] = 0.05;
                }

                vector<background> ndeco;
                for(background & i : deco){
                    i.fade -= i.fade_minus*dt;
                    if(i.fade > 0.05){

                        draw_text(i.x,i.y,"$",scale_rgb(i.col,i.fade),rgb(0,0,0),' ',rgb(0,0,0),rgb(0,0,0),{i.uni});
                        ndeco.push_back(background(i));
                    }
                }
                deco = move(ndeco);

                if(key[27] == 1 and fade_state == 0){
                    scr = 2;
                }
            }
        }
        else if(scr == 6){ // fight arena ---------------------------------------------------------------------------------------------------------------------
            draw_text(10,10,"fight WIP");

            if(key[27] == 1 and fade_state == 0){
                scr = 2;
            }
        }
        else if(scr == 7){ // online rooms ---------------------------------------------------------------------------------------------------------------------
            draw_text(10,10,"online WIP");

            if(key[27] == 1 and fade_state == 0){
                scr = 2;
            }
        }
        else if(scr == 8){ // WORLD ---------------------------------------------------------------------------------------------------------------------
            {
                if(worlds[worldid].iscreated == 0){
                    draw_rect(20,10,60,40,1,{TOPLEF_CORNER,TOPRIG_CORNER,BOTLEF_CORNER,BOTRIG_CORNER},{LIGHT_HOZON},{LIGHT_VERTI},{" "},{rgb(0,200,70)},{rgb(0,200,70)},{rgb(0,200,70)});
                    draw_text(22,13,"Enter World name: ",rgb(0,200,70));
                    draw_text(40,14,"$$$$$$$$$$$$$$$$$",rgb(0,200,70),rgb(0,0,0),' ',rgb(0,0,0),rgb(0,0,0),{TOPFUL});
                    if((40 <= mousex and mousex <= 56 and 13 <= mousey and mousey <= 13)){
                        if(mouseclick[0] == 1 and fade_state == 0){
                            type_mask[0] = (1 << 0);
                        }
                    }
                    else{
                        if(mouseclick[0] == 1 and fade_state == 0){
                            type_mask[0] = 0;
                        }
                    }

                    if((type_mask[0] >> 0) & 1){
                        if(type_input[0].size() < 16){

                            for(Long i = 65;i<=90;i++){
                                if(key_pressed[i] == 1){
                                    if(is_shift == 0) type_input[0] += char(i + ('a'-'A'));
                                    else type_input[0] += char(i);
                                }
                            }
                            for(Long i = 48;i<=57;i++){
                                if(key_pressed[i] == 1){
                                    type_input[0] += char(i);
                                }
                            }
                            if(key_pressed[32] == 1){
                                type_input[0] += ' ';
                            }
                        }

                        if(key_pressed[8] == 1){
                            if(!type_input[0].empty()) type_input[0].pop_back();
                        }
                        draw_text(40 + type_input[0].size(),13,"|",rgb());
                    }

                    draw_text(40,13,type_input[0],rgb());

                    if(key_pressed[13] == 1){

                        if(type_input[0].size() <= 0 or type_input[0].size() > 16){
                            if(prev_grid[9][23] != "O"){
                                deco.push_back(background(22,9,"WORLD NAME MUST HAVE 1 TO 16 CHARACTER",rgb(),1,0.4));
                            }
                        }
                        else{
                            fill(' ');
                            draw_text(30,25,"... L O A D I N G ...");
                            draw_flush();
                            worlds[worldid].name = type_input[0];
                            type_input[0] = "";
                            reset_keys();
                            gen_world(worlds[worldid]);
                        }
                    }

                    vector<background> ndeco;
                    for(background & i : deco){
                        i.fade -= i.fade_minus*dt;
                        if(i.fade > 0.05){

                            draw_text(i.x,i.y,i.uni,scale_rgb(i.col,i.fade),rgb(0,0,0),' ',rgb(0,0,0),rgb(0,0,0),{i.uni});
                            ndeco.push_back(background(i));
                        }
                    }
                    deco = move(ndeco);
                }
                else{
                    if(worlds[worldid].gen == 0 or worlds[worldid].isjoin == 0){
                        draw_text(30,25,"... L O A D I N G ...");
                        open_inv = 0;
                        mouse_have_item = 0;
                        mouse_click_pr = 0;
                        mouse_item = item_data();
                        mouse_click_pr1 = 0;

                        render_layer = 4;

                        minex = -1;
                        miney = -1;
                        mine_layer = -1;
                        mine_block_hp = 0;
                        mine_reset = 0;

                        workbench_open = 0;
                        input_item[0] = input_item[1] = input_item[2] = input_item[3] = item_data();
                        output_item = item_data();
                        output_item_count = 0;
                        output_index = 0;
                        for(int lol = 0;lol&lt;maxium_same_output;lol++) output_array[lol] = 0;
                        match_recipes_co = 0;
                        left_pt = 0;
                        righ_pt = 0;

                        g_hl_x = g_hl_y = -1;

                        pages = 0;

                        worlds[worldid].isjoin = 1;
                    }
                    else{

                        world_data & world = worlds[worldid];

                        player_data & cp = world.players[0];

                        double & x = cp.x;
                        double & y = cp.y;

                        if((key[37] or key[65])){
                            x -= cp.speed * dt;
                            if(!can_pass(cp.type,cp.x,cp.y,world) or 
                               !can_pass(cp.type,cp.x+1,cp.y,world) or
                               !can_pass(cp.type,cp.x,cp.y+1,world) or
                               !can_pass(cp.type,cp.x+1,cp.y+1,world)){

                               x += cp.speed * dt;  
                            }
                        }
                        if((key[39] or key[68])){
                            x += cp.speed * dt;
                            if(!can_pass(cp.type,cp.x,cp.y,world) or 
                               !can_pass(cp.type,cp.x+1,cp.y,world) or
                               !can_pass(cp.type,cp.x,cp.y+1,world) or
                               !can_pass(cp.type,cp.x+1,cp.y+1,world)){

                               x -= cp.speed * dt;  
                            }
                        }
                        if((key[38] or key[87])){
                            y -= cp.speed * dt;
                            if(!can_pass(cp.type,cp.x,cp.y,world) or 
                               !can_pass(cp.type,cp.x+1,cp.y,world) or
                               !can_pass(cp.type,cp.x,cp.y+1,world) or
                               !can_pass(cp.type,cp.x+1,cp.y+1,world)){

                               y += cp.speed * dt;  
                            }
                        }
                        if((key[40] or key[83])){
                            y += cp.speed * dt;
                            if(!can_pass(cp.type,cp.x,cp.y,world) or 
                               !can_pass(cp.type,cp.x+1,cp.y,world) or
                               !can_pass(cp.type,cp.x,cp.y+1,world) or
                               !can_pass(cp.type,cp.x+1,cp.y+1,world)){

                               y -= cp.speed * dt;  
                            }
                        }

                        inv_input(cp,world);

                        handle_mining(cp, world);
                        update_world(world,dt);


                        if(key_pressed['Q'] and timer[4] <= 0){
                            // 4 3 2
                            render_layer = render_layer - 1;
                            if(render_layer < 2) render_layer = 4;
                            timer[4] = 0.45;
                        }
                        if(timer[4] > 0) timer[4] -=dt;


                        render(cp.x, cp.y, world);

                        if(!open_inv) draw_inv_hotbar(cp);
                        else draw_inv_expand(cp);

                        if(open_inv){
                            draw_recipe_box(cp);
                            draw_craft_panel(cp);
                        }
                        draw_mouse_item();

                        draw_text(0, 2, "X: " + to_string(worlds[worldid].players[0].x),rgb(),rgb(-1),' ',rgb(),rgb(-1));
                        draw_text(0, 3, "Y: " + to_string(worlds[worldid].players[0].y),rgb(),rgb(-1),' ',rgb(),rgb(-1));
                        draw_text(0, 4, "Render layer: "+to_string(render_layer-1),rgb(),rgb(-1),' ',rgb(),rgb(-1));

                        if(minex != -1){
                            Long bid = world.grid[miney][minex][mine_layer];
                            block_data bi = get_block_data(bid);

                            double pct = 1.0 - mine_block_hp / bi.hp;

                            pct = max(0.0, min(1.0, pct));

                            int bar_w = 20;
                            int fill = (int)(pct * bar_w);

                            str bar = "[";
                            for(int i = 0; i < bar_w; i++) bar += (i < fill ? '#' : '-');
                            bar += "]";

                            draw_text(29 - get_name(bid).size() - 1, 0, get_name(bid) + " " + bar + " " + to_string((int)(pct * 100)) + "%", rgb(255, 220, 120));
                        }
                    }
                }

                if(key[27] == 1 and fade_state == 0 and !(worlds[worldid].iscreated == 1 and worlds[worldid].gen == 0)){
                    scr = 5;
                    type_input[0] = "";
                    worlds[worldid].isjoin = 0;
                    worldid = 0;
                }
            }
        }
        else{
            draw_text(10,10,"WIP");

            if(key[27] == 1 and fade_state == 0){
                scr = 1;
            }
        }

        if(prscr != scr and fade_state == 0){
            fade_state = 1;
            scr_fade = 1;
            transcr = scr;
            scr = prscr;
        }
        else if(fade_state == 1){
            scr_fade -= dt * 3;
            if(scr_fade <= 0){
                fade_state = 2;
                scr = transcr;
                prscr = scr;
                deco.clear();
                reset_keys();
            }
        }
        else if(fade_state == 2){
            scr_fade += dt * 3;
            if(scr_fade >= 1){
                scr_fade = 1;
                fade_state = 0;
            }
        }

        if(scr_fade != 1.0){
            apply_scr_fade(scr_fade);
        }



        Long pl = 14;
        for(Long i = 0; i < 500; i++){
            if(key_pressed[i] >= 1){
                draw_text(1, pl, to_string(i));
                pl++;
            }
        }

        if(key[112] == 1){
            break;
        }

        int hov = check_hover_slot(worlds[worldid].players[0]);

        draw_text(0, 5, to_string(mousewheel),rgb(),rgb(-1),' ',rgb(),rgb(-1));

        draw_text(0, 0, "FPS: " + to_string(display_fps),rgb(),rgb(-1),' ',rgb(),rgb(-1));
        if(scr != 8) draw_text(0, HEIGHT-1, "press ESC to quit",rgb(),rgb(-1),' ',rgb(),rgb(-1));
        else draw_text(0, HEIGHT-1, "Press ESC to go to options",rgb(),rgb(-1),' ',rgb(),rgb(-1));

        // must end

        draw_flush();

        auto frame_end = std::chrono::steady_clock::now();
        double elapsed = std::chrono::duration<double>(frame_end - frame_start).count();


        if(elapsed < frame_time){
            wait_ms_p(frame_time - elapsed);
        }


        // must end
    }

    // END MAIN FUNCTION1 ========================================================

    stop_console_guard();
    wait_clear(0);
    cout << "EXIT COMPLETED" << endl;

    /*
    ofstream ofs("cp.log");
    for(int y = 30; y <= 49; y++){
        string row = "";
        for(int x = 20; x <= 60; x++){
            row += grid[y][x].empty() ? " " : grid[y][x];
        }
        ofs << "y=" << y << " |" << row << "|\n";
    }
    ofs.close();
    */

    if(funcTest1.joinable()){
        funcTest1.join();
    }
    else{
        // -> detached
    }

    return 0;
}

Huy hiệu

Người dùng này không có huy hiệu nào.

«    »
CN
T2
T3
T4
T5
T6
T7
Ít
Nhiều

Lịch sử rating

, #

dựa trên nền tảng DMOJ | follow us on Github, Discord and Facebook