/*
 * tngp/mainmenu.c
 *
 * Copyright (C) 2022 bzt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * @brief Main menu
 *
 * In the player we don't have ui_form, so this code is a bloated mess... thankfully the main menu is
 * the only one with a real UI-like interface, the rest of the game uses HUD instead with fixed pop-ups.
 *
 * The string constants in the main menu (like "world.text ? world.text[x] : "something") should never
 * be displayed, just failsafes, translations should come from the game file. Maybe add them to lang[].
 */

#include <math.h>
#include "tngp.h"
#include "theora.h"

/* check if the "OK" button is enabled or not */
#define LOGIN_OK (mainmenu_user[0] && mainmenu_pass[0])
#define NEWGAME_OK (mainmenu_name[0] && !mainmenu_rem && (world.hdr.magic[0] == '#' || mainmenu_pass[0]))

typedef struct {
    char **opts;
    uint32_t pal[9*16];
    int curr, maxcurr, currpal, maxpal;
} charopt_t;
/* has world.numchar elements */
charopt_t *mainmenu_opts = NULL;

SDL_Texture *mainmenu_bg = NULL, *mainmenu_err = NULL, *mainmenu_title = NULL, *mainmenu_frm = NULL;
SDL_Texture *mainmenu_savs = NULL, *mainmenu_prev = NULL, *mainmenu_prx[WORLD_MAXPRX] = { 0 };
SDL_Rect mainmenu_bgsrc, mainmenu_bgdst, mainmenu_titledst, mainmenu_src, mainmenu_prxrect[WORLD_MAXPRX], mainmenu_savsrc;
SDL_Rect mainmenu_prevsrc, mainmenu_prevdst, mainmenu_dst;
SDL_Point *mainmenu_poly = NULL;
int mainmenu_t = 0, mainmenu_l, mainmenu_m, mainmenu_r, mainmenu_h, mainmenu_f, mainmenu_p, mainmenu_sel, mainmenu_last;
int mainmenu_o, mainmenu_v = 0, *mainmenu_vp = NULL, mainmenu_min, mainmenu_max, mainmenu_numsaves = 0, mainmenu_i, mainmenu_j;
int mainmenu_resi[] = { 0, 0, 640, 480, 800, 600, 1024, 576, 1024, 768, 1280, 720, 1440, 1080, 1920, 1080, 2048, 858, 2048, 1080 };
int mainmenu_resc = 0, mainmenu_np, mainmenu_fade, mainmenu_a, mainmenu_n = TAB_CREDITS, mainmenu_fst = 1, mainmenu_sav = 0;
int mainmenu_scr = 0, mainmenu_wh, mainmenu_sx[125], *mainmenu_attrs = NULL, *mainmenu_pris = NULL, mainmenu_wasres = 0;
int *mainmenu_curr = NULL, mainmenu_hd = 0, mainmenu_rem, mainmenu_is, mainmenu_prevt, mainmenu_prevf;
char *mainmenu_desc = NULL;
uint32_t mainmenu_prxtime[WORLD_MAXPRX], mainmenu_fadeb, *mainmenu_times = NULL;
char *mainmenu_res[32], **mainmenu_saves = NULL, mainmenu_user[256], mainmenu_pass[256], mainmenu_name[256];
void mainmenu_switch(int t);
void mainmenu_getresources();
void mainmenu_redraw();

/**
 * Free character generation options
 */
void mainmenu_optsfree()
{
    int i;

    if(mainmenu_opts) {
        for(i = 0; i < world.numchar; i++) {
            if(mainmenu_opts[i].opts)
                free(mainmenu_opts[i].opts);
        }
        free(mainmenu_opts);
        mainmenu_opts = NULL;
    }
    if(mainmenu_curr) {
        free(mainmenu_curr);
        mainmenu_curr = NULL;
    }
    if(mainmenu_attrs) {
        free(mainmenu_attrs);
        mainmenu_attrs = NULL;
    }
    if(mainmenu_pris) {
        free(mainmenu_pris);
        mainmenu_pris = NULL;
    }
    if(mainmenu_desc) {
        free(mainmenu_desc);
        mainmenu_desc = NULL;
    }
    if(mainmenu_poly) {
        free(mainmenu_poly);
        mainmenu_poly = NULL;
    }
    if(mainmenu_prev) { SDL_DestroyTexture(mainmenu_prev); mainmenu_prev = NULL; }
}

/**
 * Generate character generation options
 */
void mainmenu_optsrecalc()
{
    world_sprite_t *spr;
    world_charopt_t *opts;
    uint32_t *buf, *pal;
    char *old, *str = NULL;
    int i, j, k, l, p, ok, again, cnt = 0, dir;

    if(world.numchar < 1 || !world.chars) return;
    /* resolve dependencies recursively (recursion converted to a loop) */
    do {
        cnt++;
        for(j = 0; j < world.numchar; j++) {
            old = mainmenu_opts[j].opts ? mainmenu_opts[j].opts[mainmenu_opts[j].curr] : NULL;
            if(!mainmenu_opts[j].opts)
                mainmenu_opts[j].opts = (char**)main_alloc((world.chars[j].numopts + 1) * sizeof(char*));
            else
                memset(mainmenu_opts[j].opts, 0, (world.chars[j].numopts + 1) * sizeof(char*));
            mainmenu_opts[j].curr = 0;
            for(i = k = 0; i < world.chars[j].numopts; i++) {
                opts = &world.chars[j].opts[i];
                ok = 1;
                if(opts->na > 0 && mainmenu_attrs && world.attrs)
                    for(l = 0; ok && l < opts->na; l++) {
                        if(world.attrs[opts->atr[l].attr].type >= ATTR_GLOBVAR) { ok = 0; break; }
                        switch(opts->atr[l].type) {
                            case REL_EQ: ok &= (mainmenu_attrs[world.attrs[opts->atr[l].attr].idx] == opts->atr[l].value); break;
                            case REL_NE: ok &= (mainmenu_attrs[world.attrs[opts->atr[l].attr].idx] != opts->atr[l].value); break;
                            case REL_GE: ok &= (mainmenu_attrs[world.attrs[opts->atr[l].attr].idx] >= opts->atr[l].value); break;
                            case REL_GT: ok &= (mainmenu_attrs[world.attrs[opts->atr[l].attr].idx] > opts->atr[l].value); break;
                            case REL_LE: ok &= (mainmenu_attrs[world.attrs[opts->atr[l].attr].idx] <= opts->atr[l].value); break;
                            case REL_LT: ok &= (mainmenu_attrs[world.attrs[opts->atr[l].attr].idx] < opts->atr[l].value); break;
                        }
                    }
                if(!ok) continue;
                if(world.text && opts->name >= 0 && opts->name < world.numtext)
                    mainmenu_opts[j].opts[k] = world.text[opts->name];
                if(old == mainmenu_opts[j].opts[k])
                    mainmenu_opts[j].curr = k;
                k++;
            }
            mainmenu_opts[j].maxcurr = k;
        }
        for(j = again = 0; j < world.numchar; j++) {
            opts = &world.chars[j].opts[mainmenu_opts[j].curr];
            if(opts->na > 0 && mainmenu_attrs)
                for(i = 0; i < opts->na; i++)
                    if(opts->atr[i].type == REL_SET && mainmenu_attrs[opts->atr[i].attr] != opts->atr[i].value) {
                        mainmenu_attrs[opts->atr[i].attr] = opts->atr[i].value;
                        again = 1;
                    }
        }
    } while(again && cnt < 16);

    /* get current settings */
    if(mainmenu_desc) { free(mainmenu_desc); mainmenu_desc = NULL; }
    if(mainmenu_prev) { SDL_DestroyTexture(mainmenu_prev); mainmenu_prev = NULL; }
    memset(&mainmenu_prevsrc, 0, sizeof(mainmenu_prevsrc)); mainmenu_prevf = mainmenu_prevt = 0;
    for(j = l = 0, dir = DIR_SW; j < world.numchar; j++) {
        opts = &world.chars[j].opts[mainmenu_opts[j].curr];
        if(world.text && opts->desc >= 0 && opts->desc < world.numtext && world.text[opts->desc][0]) {
            k = strlen(world.text[opts->desc]);
            str = (char*)realloc(str, l + k + 2);
            if(str) {
                if(l) str[l++] = ' ';
                strcpy(str + l, world.text[opts->desc]);
                l += k;
            }
        }
        /* we have to check this, because spr_get() automatically returns the sprite for the nearest direction */
        if(opts->ns >= CSPR_WALK && (opts->spr[CSPR_WALK].idx < 0 ||
          opts->spr[CSPR_WALK].idx >= world.numspr[opts->spr[CSPR_WALK].cat] ||
          !world.spr[opts->spr[CSPR_WALK].cat] ||
          !world.spr[opts->spr[CSPR_WALK].cat][(opts->spr[CSPR_WALK].idx << 3) + DIR_SW].data)) dir = DIR_SO;
    }
    if(str) {
        mainmenu_desc = ui_fitstr(str, mainmenu_src.w - ui_wl - ui_wr - 2 * world.padtop[PAD_H], FNT_TXT, &l, NULL, NULL);
        free(str);
        /* only allow 3 lines tops. */
        if(mainmenu_desc && l > 3) {
            for(l = 0, str = mainmenu_desc; *str; str++)
                if(*str == '\n') {
                    l++; if(l == 3) { *str = 0; break; }
                }
        }
    }
    for(j = 0, mainmenu_prevf = 999; j < world.numchar; j++) {
        opts = &world.chars[j].opts[mainmenu_opts[j].curr];
        if(opts->ns >= CSPR_WALK && opts->spr[CSPR_WALK].idx >= 0) {
            spr = spr_get(opts->spr[CSPR_WALK].cat, opts->spr[CSPR_WALK].idx, dir);
            if(spr) {
                if(mainmenu_prevsrc.w < spr->w) mainmenu_prevsrc.w = spr->w;
                if(mainmenu_prevsrc.h < spr->h) mainmenu_prevsrc.h = spr->h;
                if(mainmenu_prevf > spr->f) mainmenu_prevf = spr->f;
                mainmenu_prevt = spr->t;
            }
        }
    }
    if(mainmenu_prevf < 1 || mainmenu_prevf == 999) mainmenu_prevf = 1;
    if(mainmenu_prevsrc.w > 0 && mainmenu_prevsrc.h > 0) {
        mainmenu_prev = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING,
            mainmenu_prevsrc.w * mainmenu_prevf, mainmenu_prevsrc.h);
        if(mainmenu_prev) {
            SDL_SetTextureBlendMode(mainmenu_prev, SDL_BLENDMODE_BLEND);
            SDL_LockTexture(mainmenu_prev, NULL, (void**)&buf, &p);
            memset(buf, 0, p * mainmenu_prevsrc.h);
            for(j = 0; j < world.numchar; j++) {
                opts = &world.chars[j].opts[mainmenu_opts[j].curr];
                if(opts->ns >= CSPR_WALK && opts->spr[CSPR_WALK].idx >= 0) {
                    spr = spr_get(opts->spr[CSPR_WALK].cat, opts->spr[CSPR_WALK].idx, dir);
                    if(spr) {
                        pal = mainmenu_opts[j].maxpal ? &mainmenu_opts[j].pal[mainmenu_opts[j].currpal * 9 + 1] : NULL;
                        for(i = 0; i < spr->f; i++)
                            spr_blit(buf, mainmenu_prevsrc.w * mainmenu_prevf, mainmenu_prevsrc.h, p,
                                i * mainmenu_prevsrc.w + (mainmenu_prevsrc.w - spr->w) / 2,
                                (mainmenu_prevsrc.h - spr->h) / 2, spr->w, spr->h, spr, i, pal);
                    }
                }
            }
            SDL_UnlockTexture(mainmenu_prev);
        }
    }
/*
    for(i = 0; i < world.numattrs; i++)
        if(world.attrs[i].type < ATTR_GLOBVAR)
            printf("%s %d\n", world.attrs[i].name, mainmenu_attrs[world.attrs[i].idx]);
    printf("\n");
*/
}

/**
 * Set resolution
 */
void mainmenu_setres(int idx)
{
#ifdef __EMSCRIPTEN__
    idx = 0;
#else
    int i;

    if(idx < 0 || idx >= (int)(sizeof(mainmenu_resi)/sizeof(mainmenu_resi[0])) / 2) idx = 0;
    if(idx == mainmenu_resc) return;
    if(verbose) printf("tngp: trying resolution %u x %u\r\n", mainmenu_resi[idx * 2], mainmenu_resi[idx * 2 + 1]);
    if(mainmenu_bg) { SDL_DestroyTexture(mainmenu_bg); mainmenu_bg = NULL; }
    if(mainmenu_title) { SDL_DestroyTexture(mainmenu_title); mainmenu_title = NULL; }
    if(mainmenu_frm) { SDL_DestroyTexture(mainmenu_frm); mainmenu_frm = NULL; }
    if(screen) { SDL_DestroyTexture(screen); screen = NULL; }
    for(i = 0; i < WORLD_MAXPRX; i++)
        if(mainmenu_prx[i]) { SDL_DestroyTexture(mainmenu_prx[i]); mainmenu_prx[i] = NULL; }
    spr_free();
    main_win(mainmenu_resi[idx * 2], mainmenu_resi[idx * 2 + 1]);
    if(!window || !renderer) { idx = 0; main_win(mainmenu_resi[idx * 2], mainmenu_resi[idx * 2 + 1]); }
    if(!window || !renderer) { SDL_Quit(); main_error(ERR_DISPLAY); return; }
    spr_init();
    mainmenu_getresources();
    mainmenu_sel = GAME_CONFIG;
    mainmenu_t = -1;
    mainmenu_switch(GAME_CONFIG);
    mainmenu_sel = 1;
#endif
    mainmenu_resc = idx;
    mainmenu_wasres = 1;
}

/**
 * Draw graph
 */
float sinf(float x);
float cosf(float x);
void mainmenu_graph(uint32_t *dst, int dw, int dh, int dp, int x, int y)
{
    int i, r = mainmenu_is / 2, xs = x + r, ys = y + r, xe, ye;
    float a, t;
    uint32_t color = (world.colors[COLOR_GRFG] & 0xffffff) | ((world.colors[COLOR_GRFG] >> 3) & 0xff000000);

    if(world.numpris < 2 || world.freeattrs < 1) return;
    for(i = 0; i < world.numpris; i++) {
        a = (float)i * 2 * 3.14159265358979323846 / (float)world.numpris;
        xe = xs + (int)(cosf(a - 1.57079632679489661923) * (float)r);
        ye = ys + (int)(sinf(a - 1.57079632679489661923) * (float)r);
        ui_line(dst, dw, dh, dp, xs, ys, xe, ye, world.colors[COLOR_GRBG]);
        t = (float)mainmenu_attrs[mainmenu_pris[i]] / (float)world.freeattrs;
        if(mainmenu_poly) {
            mainmenu_poly[i].x = (1.0 - t) * (float)xs + t * (float)xe;
            mainmenu_poly[i].y = (1.0 - t) * (float)ys + t * (float)ye;
        }
    }
    if(mainmenu_poly) {
        for(i = 1; i < world.numpris; i++) {
            ui_tri(dst, dw, dh, dp, xs, ys, mainmenu_poly[i - 1].x, mainmenu_poly[i - 1].y, mainmenu_poly[i].x, mainmenu_poly[i].y, color);
            ui_line(dst, dw, dh, dp, mainmenu_poly[i - 1].x, mainmenu_poly[i - 1].y, mainmenu_poly[i].x, mainmenu_poly[i].y, world.colors[COLOR_GRFG]);
        }
        if(world.numpris > 2) {
            ui_tri(dst, dw, dh, dp, xs, ys, mainmenu_poly[i - 1].x, mainmenu_poly[i - 1].y, mainmenu_poly[0].x, mainmenu_poly[0].y, color);
            ui_line(dst, dw, dh, dp, mainmenu_poly[i - 1].x, mainmenu_poly[i - 1].y, mainmenu_poly[0].x, mainmenu_poly[0].y, world.colors[COLOR_GRFG]);
        }
    }
}

/**
 * Redraw the screen
 */
void mainmenu_redraw()
{
    world_sprite_t *spr, *spr2;
    ssfn_buf_t buf = { 0 };
    ssfn_t *font;
    int i, j, k, l, m, p, q, y = 0, w, h, x, hb;
    uint32_t *dst = NULL, *src, c;
    char *str, tmp[256];

    SDL_LockTexture(logo, NULL, (void**)&src, &q);
    if(mainmenu_frm) {
        SDL_LockTexture(mainmenu_frm, NULL, (void**)&dst, &p);
        memset(dst, 0, p * mainmenu_src.h);
    }
    switch(mainmenu_t) {
        case GAME_LOGIN:
            if(dst) {
                mainmenu_i = 0;
                font = font_get(mainmenu_sel == 0 ? FNT_SEL : FNT_TXT);
                i = mainmenu_src.y + world.padtop[PAD_V];
                str = world.text && world.text[GAME_USERNAME][0] ? world.text[GAME_USERNAME] : "Username";
                if(str && ssfn_bbox(font, str, &mainmenu_i, &h, &x, &hb) == SSFN_OK) {
                    font->style &= ~SSFN_STYLE_A;
                    ui_text(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + world.padtop[PAD_H],
                        i + (ui_btnh - world.fonts[mainmenu_sel == 0 ? FNT_SEL : FNT_TXT].size) / 2 + hb, font,
                        world.colors[mainmenu_sel == 0 ? COLOR_SEL : COLOR_FG], str);
                }
                font = font_get(mainmenu_sel == 1 ? FNT_SEL : FNT_TXT);
                str = world.text && world.text[GAME_PASSWORD][0] ? world.text[GAME_PASSWORD] : "Password";
                if(str && ssfn_bbox(font, str, &w, &h, &x, &hb) == SSFN_OK) {
                    if(mainmenu_i < w) mainmenu_i = w;
                    font->style &= ~SSFN_STYLE_A;
                    ui_text(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + world.padtop[PAD_H],
                        i + world.padtop[PAD_V] + ui_btnh + (ui_btnh - world.fonts[mainmenu_sel == 1 ? FNT_SEL : FNT_TXT].size) / 2 + hb,
                        font, world.colors[mainmenu_sel == 1 ? COLOR_SEL : COLOR_FG], str);
                }
                mainmenu_i += mainmenu_src.x + 2 * world.padtop[PAD_H];
                mainmenu_j = mainmenu_src.w - mainmenu_src.x - mainmenu_i - world.padtop[PAD_H];
                ui_textinp(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_i, i, mainmenu_j,
                    (mainmenu_sel == 0 ? 1 : 0), mainmenu_user);
                i += world.padtop[PAD_V] + ui_btnh;
                ui_textinp(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_i, i, mainmenu_j,
                    (mainmenu_sel == 1 ? 1 : 0) | 2, mainmenu_pass);
                i += world.padtop[PAD_V] + ui_btnh;
                j = (mainmenu_src.w - 3 * world.padtop[PAD_H] - ui_wl - ui_wr) / 2;
                j = mainmenu_src.w / 3; m = mainmenu_src.w - ui_wl - ui_wr;
                ui_button(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + (m / 2 - j) / 2, i, j,
                    mainmenu_sel == 2 ? (mainmenu_p ? 2 : 1) : 0,
                    world.text && world.text[GAME_BACK][0] ? world.text[GAME_BACK] : "Back");
                ui_button(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + m / 2 + (m / 2 - j) / 2,
                    i, j, mainmenu_sel == 3 ? (mainmenu_p ? 2 : 1) : (LOGIN_OK ? 0 : 3),
                    world.text && world.text[GAME_LOGIN][0] ? world.text[GAME_LOGIN] : "Login");
            }
        break;
        case GAME_LOADGAME:
            if(dst) {
                if(mainmenu_sav >= mainmenu_numsaves) mainmenu_sav = 0;
                i = mainmenu_src.y + world.padtop[PAD_V];
                j = mainmenu_sel == 0 && mainmenu_p == 1 ? E_OPTPREVPRESS : E_OPTPREV;
                spr = spr_get(world.uispr[j].cat, world.uispr[j].idx, 0);
                if(spr) spr_blit(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + world.padtop[PAD_H] + ui_ol - spr->w,
                    i + (256 - spr->h) / 2, spr->w, spr->h, spr, 0, NULL);
                j = mainmenu_sel == 0 && mainmenu_p == 2 ? E_OPTNEXTPRESS : E_OPTNEXT;
                spr = spr_get(world.uispr[j].cat, world.uispr[j].idx, 0);
                if(spr) spr_blit(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + world.padtop[PAD_H] + ui_ol + 512,
                    i + (256 - spr->h) / 2, spr->w, spr->h, spr, 0, NULL);
                i += 256;
                if(!mainmenu_scr) {
                    font = font_get(mainmenu_sel == 0 ? FNT_SEL : FNT_TXT);
                    str = mainmenu_saves[mainmenu_sav];
                    if(str && ssfn_bbox(font, str, &w, &h, &x, &hb) == SSFN_OK) {
                        font->style &= ~SSFN_STYLE_A;
                        ui_text(dst, mainmenu_src.w, mainmenu_src.h, p, (mainmenu_src.w - w) / 2, i + hb, font,
                            world.colors[mainmenu_sel == 0 ? COLOR_SEL : COLOR_FG], str);
                    }
                    if(mainmenu_times && mainmenu_times[mainmenu_sav] < 10*365*24*3600) {
                        if(mainmenu_times[mainmenu_sav] < 24*3600)
                            sprintf(tmp, "%u:%02u:%02u", mainmenu_times[mainmenu_sav] / 3600,
                                (mainmenu_times[mainmenu_sav] / 60) % 60, mainmenu_times[mainmenu_sav] % 60);
                        else
                            sprintf(tmp, "%ud %u:%02u:%02u", mainmenu_times[mainmenu_sav] / 24 / 3600,
                                (mainmenu_times[mainmenu_sav] / 3600) % 23,
                                (mainmenu_times[mainmenu_sav] / 60) % 60, mainmenu_times[mainmenu_sav] % 60);
                    } else
                        sprintf(tmp, "?:??:??");
                    ui_small(dst, mainmenu_src.w, mainmenu_src.h, p, (mainmenu_src.w - strlen(tmp)*4) / 2, i + font->size,
                        world.colors[mainmenu_sel == 0 ? COLOR_SEL : COLOR_FG], tmp);
                }
                i += 6 + ui_lineh;
                j = (mainmenu_src.w - 3 * world.padtop[PAD_H] - ui_wl - ui_wr) / 2;
                j = mainmenu_src.w / 3; m = mainmenu_src.w - ui_wl - ui_wr;
                ui_button(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + (m / 2 - j) / 2, i, j,
                    mainmenu_sel == 1 ? (mainmenu_p ? 2 : 1) : 0,
                    world.text && world.text[GAME_BACK][0] ? world.text[GAME_BACK] : "Back");
                ui_button(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + m / 2 + (m / 2 - j) / 2,
                    i, j, mainmenu_sel == 2 ? (mainmenu_p ? 2 : 1) : 0,
                    world.text && world.text[GAME_LOAD][0] ? world.text[GAME_LOAD] : "Load");
            }
        break;
        case GAME_NEWGAME:
            if(dst) {
                mainmenu_i = 0;
                font = font_get(mainmenu_sel == 0 ? FNT_SEL : FNT_TXT);
                i = mainmenu_src.y + world.padtop[PAD_V];
                str = world.text && world.text[GAME_USERNAME][0] ? world.text[GAME_USERNAME] : "Username";
                if(str && ssfn_bbox(font, str, &mainmenu_i, &h, &x, &hb) == SSFN_OK) {
                    font->style &= ~SSFN_STYLE_A;
                    ui_text(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + world.padtop[PAD_H],
                        i + (ui_btnh - world.fonts[mainmenu_sel == 0 ? FNT_SEL : FNT_TXT].size) / 2 + hb, font,
                        world.colors[mainmenu_sel == 0 ? COLOR_SEL : COLOR_FG], str);
                }
                if(world.hdr.magic[0] != '#') {
                    font = font_get(mainmenu_sel == 1 ? FNT_SEL : FNT_TXT);
                    str = world.text && world.text[GAME_PASSWORD][0] ? world.text[GAME_PASSWORD] : "Password";
                    if(str && ssfn_bbox(font, str, &w, &h, &x, &hb) == SSFN_OK) {
                        if(mainmenu_i < w) mainmenu_i = w;
                        font->style &= ~SSFN_STYLE_A;
                        ui_text(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + world.padtop[PAD_H],
                            i + world.padtop[PAD_V] + ui_btnh + (ui_btnh - world.fonts[mainmenu_sel == 1 ? FNT_SEL : FNT_TXT].size)
                            / 2 + hb, font, world.colors[mainmenu_sel == 1 ? COLOR_SEL : COLOR_FG], str);
                    }
                }
                mainmenu_i += mainmenu_src.x + 2 * world.padtop[PAD_H];
                mainmenu_j = mainmenu_src.w - mainmenu_src.x - mainmenu_i - world.padtop[PAD_H];
                ui_textinp(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_i, i, mainmenu_j,
                    (mainmenu_sel == 0 ? 1 : 0), mainmenu_name);
                i += world.padtop[PAD_V] + ui_btnh;
                if(world.hdr.magic[0] != '#') {
                    ui_textinp(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_i, i, mainmenu_j,
                        (mainmenu_sel == 1 ? 1 : 0) | 2, mainmenu_pass);
                    i += world.padtop[PAD_V] + ui_btnh;
                }
                ui_fit(mainmenu_is, mainmenu_is, mainmenu_prevsrc.w, mainmenu_prevsrc.h, &mainmenu_prevdst.w, &mainmenu_prevdst.h);
                mainmenu_prevdst.x = mainmenu_src.w / 2 + (mainmenu_src.w / 2 - 2 * world.padtop[PAD_H] - mainmenu_prevdst.w) / 2;
                mainmenu_prevdst.y = i + (mainmenu_is - mainmenu_prevdst.h) / 2;
                if(world.numpris > 1)
                    mainmenu_graph(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.w / 2 +
                        (mainmenu_src.w / 2 - 2 * world.padtop[PAD_H] - mainmenu_is) / 2, i + world.padtop[PAD_V] + mainmenu_is);
                m = (mainmenu_src.w / 2 - 2 * world.padtop[PAD_H] - ui_ol) / 16;
                if(m > ui_lineh) m = ui_lineh - 2;
                x = mainmenu_src.x + 2 * world.padtop[PAD_H] + ui_ol;
                for(j = 0; j < world.numchar; j++) {
                    ui_option(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + 2 * world.padtop[PAD_H], i,
                        mainmenu_src.w / 2 - 2 * world.padtop[PAD_H],
                        mainmenu_sel == (j + 1) * 2 ? (mainmenu_p << 1) | 1 : 0,
                        mainmenu_opts[j].opts, mainmenu_opts[j].curr);
                    i += ui_btnh;
                    for(l = 0; l < mainmenu_opts[j].maxpal; l++) {
                        c = world.colors[mainmenu_sel == (j + 1) * 2 + 1 && l == mainmenu_opts[j].currpal ? COLOR_SEL : COLOR_FG];
                        ui_box(dst, mainmenu_src.w, mainmenu_src.h, p, x + l * m, i, m - 4, m - 4, c, mainmenu_opts[j].pal[l * 9], c);
                    }
                    i += ui_lineh;
                }
                i += world.padtop[PAD_V];
                if(mainmenu_attrs && mainmenu_pris)
                    for(j = 0; j < world.numpris; j++) {
                        l = world.freetotal > 0 && mainmenu_sel == 2 + world.numchar * 2 + j;
                        m = mainmenu_pris[j];
                        font = font_get(l ? FNT_SEL : FNT_TXT);
                        str = world.text && world.attrs[m].trname != -1 && world.attrs[m].trname < world.numtext &&
                            world.text[world.attrs[m].trname][0] ? world.text[world.attrs[m].trname] : world.attrs[m].name;
                        if(str && ssfn_bbox(font, str, &w, &h, &x, &hb) == SSFN_OK) {
                            font->style &= ~SSFN_STYLE_A;
                            ui_text(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + 2 * world.padtop[PAD_H],
                                i + (ui_lineh - world.fonts[l ? FNT_SEL : FNT_TXT].size)
                                / 2 + hb, font, world.colors[l ? COLOR_SEL : COLOR_FG], str);
                        }
                        if(world.freetotal > 0) {
                            ui_number(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + mainmenu_src.w / 2 - 100, i, 100,
                                l ? (mainmenu_p << 1) | 1 : 0, mainmenu_attrs[m]);
                        } else {
                            sprintf(tmp, "%d", mainmenu_attrs[m]);
                            font = font_get(FNT_TXT); str = tmp;
                            if(ssfn_bbox(font, str, &w, &h, &x, &hb) == SSFN_OK) {
                                font->style &= ~SSFN_STYLE_A;
                                ui_text(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + mainmenu_src.w / 2 - w,
                                    i + (ui_lineh - world.fonts[FNT_TXT].size) / 2 + hb, font, world.colors[COLOR_FG], str);
                            }
                        }
                        i += ui_btnh;
                    }
                if(world.freetotal > 0 && world.numpris > 0) {
                    sprintf(tmp, "%s: %u", world.text && world.text[GAME_REMAIN][0] ? world.text[GAME_REMAIN] : "Remaining",
                        mainmenu_rem);
                    font = font_get(FNT_TXT); str = tmp;
                    if(ssfn_bbox(font, str, &w, &h, &x, &hb) == SSFN_OK) {
                        font->style &= ~SSFN_STYLE_A;
                        ui_text(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + (mainmenu_src.w / 2 - w) / 2,
                            i + (ui_lineh - world.fonts[FNT_TXT].size) / 2 + hb, font, world.colors[COLOR_FG], str);
                    }
                    i += ui_lineh;
                }
                i = mainmenu_wh + mainmenu_src.y - world.padtop[PAD_V] - ui_btnh;
                if(mainmenu_hd && mainmenu_desc) {
                    font = font_get(FNT_TXT);
                    font->style &= ~SSFN_STYLE_A;
                    ui_text(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + world.padtop[PAD_H],
                        i - 3 * world.fonts[FNT_TXT].size, font, world.colors[COLOR_FG], mainmenu_desc);
                }
                j = mainmenu_src.w / 3; m = mainmenu_src.w - ui_wl - ui_wr;
                ui_button(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + (m / 2 - j) / 2, i, j,
                    mainmenu_sel == mainmenu_max - 1 ? (mainmenu_p ? 2 : 1) : 0,
                    world.text && world.text[GAME_BACK][0] ? world.text[GAME_BACK] : "Back");
                ui_button(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + m / 2 + (m / 2 - j) / 2, i, j,
                    mainmenu_sel == mainmenu_max ? (mainmenu_p ? 2 : 1) : (NEWGAME_OK ? 0 : 3),
                    world.text && world.text[GAME_CREATE][0] ? world.text[GAME_CREATE] : "Create");
            }
        break;
        case GAME_CONFIG:
            if(dst) {
                i = mainmenu_src.y + world.padtop[PAD_V];
                ui_blit(dst, 16, 16, p, mainmenu_src.x + world.padtop[PAD_H], i + (ui_lineh - 16) / 2, src, 288, 176, 16, 16, q,
                    mainmenu_sel == 0 ? 2 : 3);
                ui_option(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + 2 * world.padtop[PAD_H] + 16, i, 255,
                    mainmenu_sel == 0 ? (mainmenu_p << 1) | 1 : 0,
                    world.langname, world.langidx);
                i += world.padtop[PAD_V] + ui_lineh;
                ui_blit(dst, 16, 16, p, mainmenu_src.x + world.padtop[PAD_H], i + (ui_lineh - 16) / 2, src, 304, 176, 16, 16, q,
                    mainmenu_sel == 1 ? 2 : 3);
                ui_option(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + 2 * world.padtop[PAD_H] + 16, i, 255,
                    mainmenu_sel == 1 ? (mainmenu_p << 1) | 1 : 0,
                    mainmenu_res, mainmenu_resc);
                i += world.padtop[PAD_V] + ui_lineh;
                ui_blit(dst, 16, 16, p, mainmenu_src.x + world.padtop[PAD_H], i + (ui_lineh - 16) / 2, src, 320, 176, 16, 16, q,
                    mainmenu_sel == 2 ? 2 : 3);
                ui_slider(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + 2 * world.padtop[PAD_H] + 16, i, 255,
                    main_musvol, MIX_MAX_VOLUME);
                i += world.padtop[PAD_V] + ui_lineh;
                ui_blit(dst, 16, 16, p, mainmenu_src.x + world.padtop[PAD_H], i + (ui_lineh - 16) / 2, src, 336, 176, 16, 16, q,
                    mainmenu_sel == 3 ? 2 : 3);
                ui_slider(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + 2 * world.padtop[PAD_H] + 16, i, 255,
                    main_sfxvol, MIX_MAX_VOLUME);
                i += world.padtop[PAD_V] + ui_lineh;
                ui_blit(dst, 16, 16, p, mainmenu_src.x + world.padtop[PAD_H], i + (ui_lineh - 16) / 2, src, 352, 176, 16, 16, q,
                    mainmenu_sel == 4 ? 2 : 3);
                ui_slider(dst, mainmenu_src.w, mainmenu_src.h, p, mainmenu_src.x + 2 * world.padtop[PAD_H] + 16, i, 255,
                    main_spcvol, MIX_MAX_VOLUME);
                i += world.padtop[PAD_V] + ui_lineh;
                ui_button(dst, mainmenu_src.w, mainmenu_src.h, p, (mainmenu_src.w - mainmenu_o) / 2, i, mainmenu_o,
                    mainmenu_sel == 5 ? (mainmenu_p ? 2 : 1) : 0,
                    world.text && world.text[GAME_OKAY][0] ? world.text[GAME_OKAY] : "Okay");
            }
        break;
        default:
            if(screen) {
                font = font_get(FNT_MENU);
                SDL_LockTexture(screen, NULL, (void**)&dst, &p);
                memset(dst, 0, p * scr_h);
                buf.w = scr_w; buf.h = scr_h; buf.p = p; buf.ptr = (uint8_t*)dst;
                for(i = GAME_LOADGAME; i <= GAME_EXIT; i++, y += mainmenu_h + 10) {
                    j = MENU_N;
                    if(mainmenu_f != GAME_LOADGAME && i == GAME_LOADGAME) j = MENU_I;
                    if(i == mainmenu_sel) j = mainmenu_p ? MENU_P : MENU_S;
                    spr = spr_get(world.menubtn[j].l.cat, world.menubtn[j].l.idx, 0);
                    if(spr) spr_blit(dst, scr_w, scr_h, p, mainmenu_l - spr->w, y + (mainmenu_h - spr->h) / 2, spr->w, spr->h, spr, 0, NULL);
                    spr2 = spr_get(world.menubtn[j].m.cat, world.menubtn[j].m.idx, 0);
                    if(spr2) spr_blit(dst, scr_w, scr_h, p, mainmenu_l, y + (mainmenu_h - spr2->h) / 2, mainmenu_m, spr2->h, spr2, 0, NULL);
                    spr = spr_get(world.menubtn[j].r.cat, world.menubtn[j].r.idx, 0);
                    if(spr) spr_blit(dst, scr_w, scr_h, p, mainmenu_l + mainmenu_m, y + (mainmenu_h - spr->h) / 2, spr->w, spr->h, spr, 0, NULL);
                    ui_blit(dst, 16, 16, p, mainmenu_l, y + (mainmenu_h - 16) / 2, src, 288 + (i - 1) * 16, 160, 16, 16, q, j == MENU_I);
                    if(world.text) {
                        str = world.text[i];
                        if(str && ssfn_bbox(font, str, &w, &h, &x, &hb) == SSFN_OK) {
                            buf.fg = world.colors[COLOR_MENUN + j];
                            buf.x = (scr_w - w) / 2;
                            buf.y = y + ((mainmenu_h - world.fonts[FNT_MENU].size) / 2) + hb + world.padtop[PAD_MENU] +
                                (j == MENU_P ? 1 : 0);
                            while(*str && ((k = ssfn_render(font, &buf, str)) > 0 || k == SSFN_ERR_NOGLYPH)) str += (k < 1 ? 1 : k);
                        }
                    }
                }
                SDL_UnlockTexture(screen);
            }
        break;
    }
    if(mainmenu_frm)
        SDL_UnlockTexture(mainmenu_frm);
    SDL_UnlockTexture(logo);
    y = mainmenu_bgdst.y;
    spr = spr_get(world.titimg.cat, world.titimg.idx, 0);
    y += spr ? spr->h : 32;
    if(mainmenu_title) y += mainmenu_titledst.h;
    mainmenu_dst.w = mainmenu_src.w; mainmenu_dst.h = mainmenu_src.h;
    mainmenu_dst.x = (win_w - mainmenu_src.w) / 2;
    mainmenu_dst.y = (win_h - mainmenu_src.h) / 2;
    if(!mainmenu_t) mainmenu_dst.y = y + (win_h - mainmenu_src.h - y) / 2;
    if(mainmenu_dst.y < y) mainmenu_dst.y = y;
    if(mainmenu_dst.y + mainmenu_dst.h > mainmenu_bgdst.y + mainmenu_bgdst.h - 5)
        mainmenu_dst.y = mainmenu_bgdst.y + mainmenu_bgdst.h - mainmenu_dst.h - 5;
}

/**
 * Change selected menu
 */
void mainmenu_select(int idx)
{
    if(idx > mainmenu_max) idx = mainmenu_min;
    if(idx < mainmenu_min) idx = mainmenu_max;
    if(mainmenu_sel != idx) {
        if(mainmenu_err) { SDL_DestroyTexture(mainmenu_err); mainmenu_err = NULL; }
        media_playsel();
        mainmenu_sel = idx;
    }
}

/**
 * Get game's title
 */
void mainmenu_gettitle()
{
    ssfn_t *font;
    ssfn_buf_t buf = { 0 };
    char *str;
    int p, x, y;

    if(mainmenu_title) { SDL_DestroyTexture(mainmenu_title); mainmenu_title = NULL; }
    if((world.hastitle & 1) && world.text && world.text[GAME_TITLE][0] && (font = font_get(FNT_TITLE)) &&
      ssfn_bbox(font, world.text[GAME_TITLE], &buf.w, &buf.h, &x, &y) == SSFN_OK &&
      (mainmenu_title = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, buf.w + 4, buf.h + 4))) {
        SDL_SetTextureBlendMode(mainmenu_title, SDL_BLENDMODE_BLEND);
        SDL_LockTexture(mainmenu_title, NULL, (void**)&buf.ptr, &p);
        mainmenu_titledst.w = buf.w + 4;
        mainmenu_titledst.h = buf.h + 4;
        buf.p = p; buf.x = x + 4; buf.y = y + 4; buf.fg = world.colors[COLOR_TITSH];
        str = world.text[GAME_TITLE];
        while(*str && ((p = ssfn_render(font, &buf, str)) > 0 || p == SSFN_ERR_NOGLYPH)) str += (p < 1 ? 1 : p);
        buf.x = x; buf.y = y; buf.fg = world.colors[COLOR_TITLE];
        str = world.text[GAME_TITLE];
        while(*str && ((p = ssfn_render(font, &buf, str)) > 0 || p == SSFN_ERR_NOGLYPH)) str += (p < 1 ? 1 : p);
        SDL_UnlockTexture(mainmenu_title);
    }
}

/**
 * Set language
 */
void mainmenu_setlanguage(int idx)
{
    int i, j, p, x, y, m = 0;
    ssfn_t *font;

    if(idx < 0 || idx >= world.numlang) idx = 0;
    if(idx != world.langidx) {
        media_freespeech();
        world_loadtext(idx);
        mainmenu_gettitle();
        mainmenu_sel = GAME_CONFIG;
        mainmenu_t = -1;
        mainmenu_switch(GAME_CONFIG);
        mainmenu_sel = 0;
    }
    mainmenu_o = 0;
    font = font_get(FNT_BTN);
    if(font && world.text) {
        ssfn_bbox(font, world.text[GAME_OKAY], &mainmenu_o, &j, &x, &y);
        mainmenu_o += 2 * world.padtop[PAD_H];
    }
    mainmenu_o += 32;
    if(mainmenu_o < 64) mainmenu_o = 64;
    font = font_get(FNT_MENU);
    if(font && world.text) {
        ssfn_bbox(font, world.text[GAME_LOGIN], &m, &j, &x, &y);
        for(i = GAME_LOADGAME; i <= GAME_EXIT; i++)
            if(ssfn_bbox(font, world.text[i], &p, &j, &x, &y) == SSFN_OK) {
                if(p > m) m = p;
            }
    }
    mainmenu_m = m + 48;
}

/**
 * Get preview from saved game
 */
void mainmenu_getpreview(int idx, int slot)
{
    int i, j, p;
    char *fn, tmp[64] = { 0 };
    uint16_t *pix, *orig;
    uint32_t *pix2;
    FILE *f;

    if(mainmenu_savs && slot >= 0 && slot < 5) {
        SDL_LockTexture(mainmenu_savs, NULL, (void**)&pix2, &p);
        pix2 += slot * (256 + 32);
        /* clear slot */
        for(j = 0; j < 256; j++)
            memset(pix2 + j * p / 4, 0, 1024);
    } else
        return;
    if(world.hdr.magic[0] == '#' && mainmenu_saves && idx >= 0 && idx < mainmenu_numsaves) {
        /* set a red slot bg indicating error. If everything goes well, will be overridden by image */
        for(j = 0; j < 256; j++)
            for(i = 0; i < 256; i++)
                pix2[j * p / 4 + i] = 0xFF000020;
        ssfn_dst.w = ssfn_dst.h = 0; ssfn_dst.p = p; ssfn_dst.x = 124; ssfn_dst.y = 120; ssfn_dst.fg = 0xFF000040;
        ssfn_dst.ptr = (uint8_t*)pix2; ssfn_putc('?');
        fn = (char*)main_alloc(strlen(files_saves) + 256 + 8);
        memcpy(tmp, world.hdr.id, 16);
        sprintf(fn, "%s" SEP "%s" SEP "%s.sav", files_saves, tmp, mainmenu_saves[idx]);
        f = files_open(fn, "rb");
        free(fn);
        if(f) {
            if(fread(tmp, 1, 64, f) == 64 && !memcmp(tmp, WORLD_STATEMAGIC, 16) && !memcmp(tmp + 32, "PRVW", 4)) {
                memcpy(&i, tmp + 36, 4); i = SDL_SwapLE32(i);
                if(i > 32 && /* dim == 256 */ tmp[63] == 1 && !tmp[62]) {
                    fn = (char*)main_alloc(i);
                    if(fread(fn, 1, i - 32, f) == (size_t)i - 32) {
                        j = 0;
                        pix = orig = (uint16_t*)stbi_zlib_decode_malloc_guesssize_headerflag(fn, i - 32, 256*256*2, &j, 1);
                        if(pix) {
                            if(j == 256 * 256 * 2)
                                for(j = 0; j < 256; j++, pix2 += p / 4, pix += 256)
                                    for(i = 0; i < 256; i++)
                                        pix2[i] = 0xFF000000 | (((pix[i] >> 10) & 0x1F) << 19) |
                                            (((pix[i] >> 5) & 0x1F) << 11) | ((pix[i] & 0x1F) << 3);
                            free(orig);
                        }
                    }
                    free(fn);
                }
            }
            fclose(f);
        }
    }
    SDL_UnlockTexture(mainmenu_savs);
}

/**
 * Scroll the save games
 */
void mainmenu_savscr()
{
    int i, p;
    uint8_t *data;

    if(!mainmenu_savs || !mainmenu_scr) return;
    SDL_LockTexture(mainmenu_savs, NULL, (void**)&data, &p);
    if(mainmenu_scr < 0)
        for(i = 0; i < 256; i++, data += p)
            memmove(data + (256 + 32) * 4, data, p - (256 + 32) * 4);
    else
        for(i = 0; i < 256; i++, data += p)
            memmove(data, data + (256 + 32) * 4, p - (256 + 32) * 4);
    SDL_UnlockTexture(mainmenu_savs);
    if(mainmenu_scr < 0)
        mainmenu_getpreview(mainmenu_sav - 2, 0);
    else
        mainmenu_getpreview(mainmenu_sav + 2, 4);
    mainmenu_scr = 0;
    mainmenu_savsrc.x = 448;
}

/**
 * Get resources
 */
void mainmenu_getresources()
{
    int i, p, x, y;
    uint8_t *dst;

    mainmenu_titledst.w = mainmenu_titledst.h = 0;
    memset(&mainmenu_prxrect, 0, sizeof(mainmenu_prxrect));
    mainmenu_gettitle();
    mainmenu_bgsrc.x = mainmenu_bgsrc.y = mainmenu_bgdst.x = mainmenu_bgdst.y = 0;
    mainmenu_bgdst.w = win_w; mainmenu_bgdst.h = win_h; mainmenu_np = 0;
    if(theora_ctx.hasVideo) {
        mainmenu_bg = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, theora_ctx.w, theora_ctx.h);
        if(!(world.hastitle & 2)) {
            ui_fit(theora_ctx.w, theora_ctx.h, win_w, win_h, &mainmenu_bgsrc.w, &mainmenu_bgsrc.h);
            mainmenu_bgsrc.x = (theora_ctx.w - mainmenu_bgsrc.w) / 2; mainmenu_bgsrc.y = (theora_ctx.h - mainmenu_bgsrc.h) / 2;
        } else {
            ui_fit(win_w, win_h, theora_ctx.w, theora_ctx.h, &mainmenu_bgdst.w, &mainmenu_bgdst.h);
            mainmenu_bgsrc.w = theora_ctx.w; mainmenu_bgsrc.h = theora_ctx.h;
        }
        /* planar YUV is green by default, let's make it black */
        SDL_LockTexture(mainmenu_bg, NULL, (void**)&dst, &p);
        memset(dst + (theora_ctx.w * theora_ctx.h), 0x80, (theora_ctx.w / 2) * theora_ctx.h);
        SDL_UnlockTexture(mainmenu_bg);
    } else {
        mainmenu_bg = spr_getbg(world.bgimage.idx, &x, &y);
        if(!(world.hastitle & 2)) {
            ui_fit(x, y, win_w, win_h, &mainmenu_bgsrc.w, &mainmenu_bgsrc.h);
            mainmenu_bgsrc.x = (x - mainmenu_bgsrc.w) / 2; mainmenu_bgsrc.y = (y - mainmenu_bgsrc.h) / 2;
        } else {
            ui_fit(win_w, win_h, x, y, &mainmenu_bgdst.w, &mainmenu_bgdst.h);
            mainmenu_bgsrc.w = x; mainmenu_bgsrc.h = y;
        }
    }
    /* failsafe */
    if(!mainmenu_bgdst.w || !mainmenu_bgdst.h) { mainmenu_bgdst.w = win_w; mainmenu_bgdst.h = win_h; }
    mainmenu_bgdst.x = (win_w - mainmenu_bgdst.w) / 2; mainmenu_bgdst.y = (win_h - mainmenu_bgdst.h) / 2;
    if(!theora_ctx.hasVideo) {
        for(i = 0; i < WORLD_MAXPRX; i++) {
            mainmenu_prx[i] = spr_getbg(world.menuprx[i].spr.idx, &mainmenu_prxrect[i].w, &mainmenu_prxrect[i].h);
            if(mainmenu_prx[i]) {
                mainmenu_np++;
                mainmenu_prxrect[i].x = world.menuprx[i].dx < 0 ? mainmenu_bgdst.w :
                    (world.menuprx[i].dx > 0 ? -mainmenu_prxrect[i].w : (mainmenu_bgdst.w - mainmenu_prxrect[i].w) / 2);
                mainmenu_prxrect[i].y = world.menuprx[i].dy < 0 ? mainmenu_bgdst.h :
                    (world.menuprx[i].dy > 0 ? -mainmenu_prxrect[i].h : (mainmenu_bgdst.h - mainmenu_prxrect[i].h) / 2);
            }
        }
    }
    SDL_RenderSetClipRect(renderer, &mainmenu_bgdst);
}

/**
 * Initialize main menu
 */
void mainmenu_init()
{
    world_sprite_t *spr;
    int i, j, k, R, G, B, h = 0, l = 0, r = 0, s = 0;
#ifndef __EMSCRIPTEN__
    char *fn, *n, *c, tmp[48] = { 0 };
    FILE *f;

    if(world.hdr.magic[0] == '#') {
        fn = (char*)main_alloc(strlen(files_saves) + 256 + 8);
        memcpy(tmp, world.hdr.id, 16);
        sprintf(fn, "%s" SEP "%s", files_saves, tmp);
        n = fn + strlen(fn);
        mainmenu_saves = files_getdir(fn, "sav", &mainmenu_numsaves);
        if(mainmenu_numsaves > 0) mainmenu_times = (uint32_t*)main_alloc(mainmenu_numsaves * sizeof(uint32_t));
        for(i = 0; i < mainmenu_numsaves && mainmenu_saves[i]; i++) {
            memset(tmp, 0, sizeof(tmp));
            *n = SEP[0]; strcpy(n + 1, mainmenu_saves[i]);
            f = files_open(fn, "rb"); if(f) { if(!fread(tmp, 1, 44, f)) { tmp[0] = 0; } fclose(f); }
            memcpy(&l, tmp + 36, 4);
            l = SDL_SwapLE32(l);
            if(memcmp(tmp, WORLD_STATEMAGIC, 16) || memcmp(tmp + 16, world.hdr.id, 16) || memcmp(tmp + 32, "PRVW", 4) || l < 32) {
                free(mainmenu_saves[i]);
                memcpy(&mainmenu_saves[i], &mainmenu_saves[i + 1], (mainmenu_numsaves - i) * sizeof(char *));
                i--; mainmenu_numsaves--;
            } else {
                memcpy(&j, tmp + 40, 4); mainmenu_times[i] = SDL_SwapLE32(j);
                c = strrchr(mainmenu_saves[i], '.'); if(c) *c = 0;
            }
        }
        free(fn);
        mainmenu_f = mainmenu_numsaves ? GAME_LOADGAME : GAME_NEWGAME;
    } else
        mainmenu_f = GAME_LOADGAME;
#else
    mainmenu_f = GAME_NEWGAME;
#endif
    mainmenu_sel = mainmenu_last = mainmenu_f;
    mainmenu_pris = (int*)main_alloc(world.numattrs * sizeof(int));
    mainmenu_attrs = (int*)main_alloc(world.numattrs * sizeof(int));
    mainmenu_poly = (SDL_Point*)main_alloc(world.numattrs * sizeof(SDL_Point));
    for(i = j = 0; world.attrs && i < world.numattrs; i++) {
        if(world.attrs[i].type == ATTR_PRI)
            mainmenu_pris[j++] = world.attrs[i].idx;
        if(world.attrs[i].type < ATTR_GLOBVAR)
            mainmenu_attrs[world.attrs[i].idx] = world.attrs[i].defval;
    }
    mainmenu_opts = (charopt_t*)main_alloc(world.numchar * sizeof(charopt_t));
    mainmenu_curr = (int*)main_alloc(world.numchar * sizeof(int));
    mainmenu_hd = 0;
    for(i = 0; i < world.numchar; i++) {
        if(world.chars[i].pal.idx != 0xffff) {
            spr = spr_get(world.chars[i].pal.cat, world.chars[i].pal.idx, 0);
            if(spr) {
                spr_blit(mainmenu_opts[i].pal, 9, 16, 9 * 4, 1, 0, 16, 16, spr, 0, NULL);
                for(k = 0; k < 16; k++) {
                    R = G = B = 0;
                    for(j = 0; j < 8 &&
                      (mainmenu_opts[i].pal[1 + j + k * 9] >> 24) == 255 && (mainmenu_opts[i].pal[1 + j + k * 9] & 0xffffff);
                      j++) {
                        R += (mainmenu_opts[i].pal[1 + j + k * 9] >> 0) & 0xff;
                        G += (mainmenu_opts[i].pal[1 + j + k * 9] >> 8) & 0xff;
                        B += (mainmenu_opts[i].pal[1 + j + k * 9] >> 16) & 0xff;
                    }
                    if(j > 0) {
                        R /= j; G /= j; B /= j;
                        mainmenu_opts[i].pal[k * 9] = 0xff000000 | (B << 16) | (G << 8) | R;
                        mainmenu_opts[i].maxpal++;
                    } else
                        break;
                }
            }
        }
        for(j = 0; !mainmenu_hd && j < world.chars[i].numopts; j++)
            if(world.text && world.chars[i].opts[j].desc >= 0 && world.chars[i].opts[j].desc < world.numtext) mainmenu_hd++;
    }
    mainmenu_rem = world.freetotal;
    if(world.bgmovie >= 0 && world.bgmovie < world.nummovie && world.movie) {
        theora_start(&theora_ctx, NULL, &world.movie[world.bgmovie]);
        s = 1;
    } else {
        theora_ctx.hasAudio = theora_ctx.hasVideo = s = 0;
    }
    mainmenu_getresources(); l = r = h = 0;
    for(i = 0; i < MENU_LAST; i++) {
        spr = spr_get(world.menubtn[i].l.cat, world.menubtn[i].l.idx, 0);
        if(spr) {
            if(spr->h > h) h = spr->h;
            if(spr->w > l) l = spr->w;
        }
        spr = spr_get(world.menubtn[i].m.cat, world.menubtn[i].m.idx, 0);
        if(spr) {
            if(spr->h > h) h = spr->h;
        }
        spr = spr_get(world.menubtn[i].r.cat, world.menubtn[i].r.idx, 0);
        if(spr) {
            if(spr->h > h) h = spr->h;
            if(spr->w > r) r = spr->w;
        }
    }
    mainmenu_l = l; mainmenu_r = r; mainmenu_h = h;
    mainmenu_savsrc.y = 0; mainmenu_savsrc.x = 0;
    mainmenu_setlanguage(world.langidx);
    memset(mainmenu_user, 0, sizeof(mainmenu_user));
    memset(mainmenu_pass, 0, sizeof(mainmenu_pass));
    memset(mainmenu_name, 0, sizeof(mainmenu_name));
    memset(mainmenu_prxtime, 0, sizeof(mainmenu_prxtime));
    memset(mainmenu_res, 0, sizeof(mainmenu_res));
    mainmenu_resi[0] = win_w; mainmenu_resi[1] = win_h;
    for(i = 0; i < (int)(sizeof(mainmenu_resi)/sizeof(mainmenu_resi[0])) &&
      i / 2 < (int)(sizeof(mainmenu_res)/sizeof(mainmenu_res[0])); i += 2) {
        mainmenu_res[i / 2] = (char*)main_alloc(16);
        sprintf(mainmenu_res[i / 2], "%4u x %4u", mainmenu_resi[i], mainmenu_resi[i + 1]);
    }
    mainmenu_t = -1;
    mainmenu_switch(0);
    media_open();
    if(world.bgmusic >= 0 && world.bgmusic < world.nummusic && world.music)
        media_playmus(NULL, &world.music[world.bgmusic]);
    if(s) {
        if(theora_ctx.hasAudio) {
            theora_callback(0);
        }
        theora_ctx.baseticks = main_tick;
    }
    mainmenu_fade = 1; mainmenu_a = 0; mainmenu_fadeb = main_tick;
}

/**
 * Free resources
 */
void mainmenu_free()
{
    int i;

    SDL_RenderSetClipRect(renderer, NULL);
    if(mainmenu_times) { free(mainmenu_times); mainmenu_times = NULL; }
    files_freedir(&mainmenu_saves, &mainmenu_numsaves);
    for(i = 0; i < (int)(sizeof(mainmenu_res)/sizeof(mainmenu_res[0])); i++)
        if(mainmenu_res[i]) { free(mainmenu_res[i]); mainmenu_res[i] = NULL; }
    media_close();
    if(mainmenu_err) { SDL_DestroyTexture(mainmenu_err); mainmenu_err = NULL; }
    if(mainmenu_bg) { SDL_DestroyTexture(mainmenu_bg); mainmenu_bg = NULL; }
    if(mainmenu_title) { SDL_DestroyTexture(mainmenu_title); mainmenu_title = NULL; }
    if(mainmenu_frm) { SDL_DestroyTexture(mainmenu_frm); mainmenu_frm = NULL; }
    if(mainmenu_savs) { SDL_DestroyTexture(mainmenu_savs); mainmenu_savs = NULL; }
    if(screen) { SDL_DestroyTexture(screen); screen = NULL; }
    for(i = 0; i < WORLD_MAXPRX; i++)
        if(mainmenu_prx[i]) { SDL_DestroyTexture(mainmenu_prx[i]); mainmenu_prx[i] = NULL; }
    mainmenu_optsfree();
}

/**
 * Exit main menu
 */
void mainmenu_exit(int t)
{
    mainmenu_n = t;
    mainmenu_fade = -1; mainmenu_fadeb = main_tick;
    if(mainmenu_err) { SDL_DestroyTexture(mainmenu_err); mainmenu_err = NULL; }
    media_fadeout(500);
}

/**
 * Create an error message
 */
void mainmenu_error(char *str)
{
    int i;
    uint32_t *data;

    media_playerr();
    if(mainmenu_err) SDL_DestroyTexture(mainmenu_err);
    mainmenu_err = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, win_w, 42);
    if(mainmenu_err) {
        SDL_SetTextureBlendMode(mainmenu_err, SDL_BLENDMODE_BLEND);
        SDL_LockTexture(mainmenu_err, NULL, (void**)&data, &i);
        for(i = 0; i < win_w * 42; i++) data[i] = 0xff000040;
        SDL_UnlockTexture(mainmenu_err);
        ssfn_dst.fg = 0xffffffff;
        scr_w = win_w; scr_h = 42;
        main_text(mainmenu_err, (win_w - main_textwidth(str)) / 2, 13, str);
    }
}

/**
 * Change mainmenu tab
 */
void mainmenu_switch(int t)
{
    int tab[2] = { 0, 0 }, i, j;

    if(mainmenu_err) { SDL_DestroyTexture(mainmenu_err); mainmenu_err = NULL; }
    if(mainmenu_t == t) return;
    if(mainmenu_savs) { SDL_DestroyTexture(mainmenu_savs); mainmenu_savs = NULL; }
    if(mainmenu_prev) { SDL_DestroyTexture(mainmenu_prev); mainmenu_prev = NULL; }
    if(mainmenu_frm) { SDL_DestroyTexture(mainmenu_frm); mainmenu_frm = NULL; }
    if(screen) { SDL_DestroyTexture(screen); screen = NULL; }
    memset(&mainmenu_src, 0, sizeof(mainmenu_src));
    if(t == GAME_LOADGAME && world.hdr.magic[0] != '#') t = GAME_LOGIN;
    switch(t) {
        case GAME_LOGIN:
            tab[0] = GAME_LOGTOSERVER;
            screen = ui_window(win_w * 2 / 3, world.padtop[PAD_V] + (ui_btnh + world.padtop[PAD_V]) * 3,
                tab, 0, NULL, &mainmenu_src, NULL, 0, 0);
            scr_w = mainmenu_src.w; scr_h = mainmenu_src.h;
            mainmenu_frm = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, scr_w, scr_h);
            memset(mainmenu_pass, 0, sizeof(mainmenu_pass));
        break;
        case GAME_LOADGAME:
            tab[0] = GAME_SAVEDGAMES;
            screen = ui_window(2 * world.padtop[PAD_H] + ui_ol + 512 + ui_or, 2 * world.padtop[PAD_V] + 256+6 + ui_lineh + ui_btnh,
                tab, 0, NULL, &mainmenu_src, NULL, 0, 0);
            scr_w = mainmenu_src.w; scr_h = mainmenu_src.h;
            mainmenu_frm = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, scr_w, scr_h);
            mainmenu_savs = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, 1408, 256);
            if(mainmenu_savs) {
                SDL_SetTextureBlendMode(mainmenu_savs, SDL_BLENDMODE_BLEND);
                for(i = 0; i < 5; i++)
                    mainmenu_getpreview(mainmenu_sav + i - 2, i);
                mainmenu_savsrc.x = 448;
            }
        break;
        case GAME_NEWGAME:
            tab[0] = GAME_CHARGEN;
            j = win_w / 3 - world.padtop[PAD_H]; if(world.numpris <= 1) j /= 2;
            mainmenu_is = world.numchar * (ui_btnh + ui_lineh) + world.numpris * ui_btnh;
            if(mainmenu_is < j) mainmenu_is = j;
            mainmenu_wh = world.padtop[PAD_V] + (ui_btnh + world.padtop[PAD_V]) * (world.hdr.magic[0] == '#' ? 2 : 3) +
                mainmenu_is + (world.freetotal > 0 && world.numpris > 0 ? ui_lineh : 0) + 2 * world.padtop[PAD_V] +
                (mainmenu_hd ? 3 * world.fonts[FNT_TXT].size + 2 * world.padtop[PAD_V] : 0);
            screen = ui_window(win_w * 3 / 4, mainmenu_wh, tab, 0, NULL, &mainmenu_src, NULL, 0, 0);
            scr_w = mainmenu_src.w; scr_h = mainmenu_src.h;
            mainmenu_frm = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, scr_w, scr_h);
            if(world.numpris > 1) mainmenu_is /= 2;
            if(mainmenu_is > mainmenu_src.w / 2 - 2 * world.padtop[PAD_H])
                mainmenu_is = mainmenu_src.w / 2 - 2 * world.padtop[PAD_H];
            memset(mainmenu_pass, 0, sizeof(mainmenu_pass));
            mainmenu_optsrecalc();
        break;
        case GAME_CONFIG:
            tab[0] = GAME_CONFIG;
            screen = ui_window(3 * world.padtop[PAD_H] + 16 + 256, 2 * world.padtop[PAD_V] + (ui_lineh + world.padtop[PAD_V]) * 5
                + ui_btnh, tab, 0, NULL, &mainmenu_src, NULL, 0, 0);
            scr_w = mainmenu_src.w; scr_h = mainmenu_src.h;
            mainmenu_frm = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, scr_w, scr_h);
        break;
        default:
            scr_w = mainmenu_src.w = mainmenu_l + mainmenu_m + mainmenu_r;
            scr_h = mainmenu_src.h = (mainmenu_h + 10) * (GAME_EXIT - GAME_LOADGAME + 1);
            screen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, scr_w, scr_h);
        break;
    }
    if(screen)
        SDL_SetTextureBlendMode(screen, SDL_BLENDMODE_BLEND);
    if(mainmenu_frm)
        SDL_SetTextureBlendMode(mainmenu_frm, SDL_BLENDMODE_BLEND);
    if(t) mainmenu_last = mainmenu_sel;
    switch(t) {
        case GAME_LOGIN: mainmenu_sel = 0; mainmenu_min = 0; mainmenu_max = 3; break;
        case GAME_LOADGAME: mainmenu_sel = 0; mainmenu_min = 0; mainmenu_max = 2; break;
        case GAME_NEWGAME: mainmenu_sel = 0; mainmenu_min = 0; mainmenu_max = world.numchar * 2 +
            (world.freetotal > 0 ? world.numpris : 0) + 3; break;
        case GAME_CONFIG: mainmenu_sel = 0; mainmenu_min = 0; mainmenu_max = 5; break;
        default: mainmenu_sel = mainmenu_last; mainmenu_min = mainmenu_f; mainmenu_max = GAME_EXIT; break;
    }
    mainmenu_t = t;
}

/**
 * Controller
 */
int mainmenu_ctrl()
{
    world_sprite_t *title;
    char *fn, tmp[17];
    int k, l, m, x, y, s;

    if(mainmenu_fade < 0) return 1;
    if(ui_input_str) {
        x = (mainmenu_t == GAME_NEWGAME && !mainmenu_sel && world.hdr.magic[0] == '#' ? 2 : 1);
        switch(ui_input_ctrl()) {
            case -1: mainmenu_select(mainmenu_sel - 1); break;
            case 1: mainmenu_select(mainmenu_sel + x); break;
            case 2:
                if(mainmenu_t == GAME_LOGIN && mainmenu_sel == 1) mainmenu_select(LOGIN_OK ? 3 : 0);
                else { mainmenu_select(mainmenu_sel + x); goto dosel; }
            break;
        }
        return 1;
    }
    scr_w = mainmenu_src.w; scr_h = mainmenu_src.h;
    switch(event.type) {
        case SDL_KEYDOWN:
            ui_input_gp &= ~1;
            switch(event.key.keysym.sym) {
                case SDLK_AC_BACK:
                case SDLK_F10:
                case SDLK_BACKSPACE:
                case SDLK_ESCAPE:
back:               if(!mainmenu_t) { mainmenu_exit(TAB_CREDITS); return 1; }
                    else {
                        if(mainmenu_t == GAME_CONFIG) mainmenu_setres(0);
                        mainmenu_switch(0);
                    }
                break;
                case SDLK_UP:
                    mainmenu_select(mainmenu_sel - 1);
                    if(mainmenu_t == GAME_LOGIN && mainmenu_sel == mainmenu_max && !LOGIN_OK)
                        mainmenu_select(mainmenu_sel - 1);
                    if(mainmenu_t == GAME_NEWGAME) {
                        if(mainmenu_sel == mainmenu_max && !NEWGAME_OK)
                            mainmenu_select(mainmenu_sel - 1);
                        if(mainmenu_sel == 1 && world.hdr.magic[0] == '#') mainmenu_select(0);
                        if(mainmenu_sel >= 2 && mainmenu_sel < 2 + 2 * world.numchar && (mainmenu_sel & 1) &&
                            mainmenu_opts[(mainmenu_sel - 2) / 2].maxpal < 1) mainmenu_select(mainmenu_sel - 1);
                    }
                break;
                case SDLK_TAB:
                case SDLK_DOWN:
                    mainmenu_select(mainmenu_sel + 1);
                    if(mainmenu_t == GAME_LOGIN && mainmenu_sel == mainmenu_max && !LOGIN_OK)
                        mainmenu_select(0);
                    if(mainmenu_t == GAME_NEWGAME) {
                        if(mainmenu_sel == mainmenu_max && !NEWGAME_OK)
                            mainmenu_select(0);
                        if(mainmenu_sel == 1 && world.hdr.magic[0] == '#') mainmenu_select(2);
                        if(mainmenu_sel >= 2 && mainmenu_sel < 2 + 2 * world.numchar && (mainmenu_sel & 1) &&
                            mainmenu_opts[(mainmenu_sel - 2) / 2].maxpal < 1) mainmenu_select(mainmenu_sel + 1);
                    }
                break;
                case SDLK_LEFT:
left:               mainmenu_p = 1;
                    switch(mainmenu_t) {
                        case GAME_LOGIN:
                            if(mainmenu_sel >= mainmenu_max - 1) { mainmenu_p = 0; mainmenu_select(mainmenu_sel - 1); }
                        break;
                        case GAME_LOADGAME:
                            if(!mainmenu_sel) {
                                if(!mainmenu_scr && mainmenu_sav > 0) {
                                    media_playclk(); mainmenu_scr--; mainmenu_sav--; mainmenu_fadeb = main_tick;
                                    /* I've tried a nice sine wave here, but sliding is so fast, you can't really
                                     * see the difference, and slowing down sliding gives bad user experience */
                                    for(x = 0; x < (int)(sizeof(mainmenu_sx)/sizeof(mainmenu_sx[0])); x++)
                                        mainmenu_sx[x] = 448 - (256 + 32) * x / (int)(sizeof(mainmenu_sx)/sizeof(mainmenu_sx[0]) - 1);
                                }
                            } else
                            if(mainmenu_sel >= mainmenu_max - 1) { mainmenu_p = 0; mainmenu_select(mainmenu_sel - 1); }
                        break;
                        case GAME_NEWGAME:
                            media_playclk();
                            if(mainmenu_sel >= 2 && mainmenu_sel < 2 + 2 * world.numchar) {
                                k = (mainmenu_sel - 2) / 2;
                                if((mainmenu_sel & 1) && mainmenu_opts[k].maxpal > 0) {
                                    if(mainmenu_opts[k].currpal > 0) mainmenu_opts[k].currpal--;
                                    else mainmenu_opts[k].currpal = mainmenu_opts[k].maxpal - 1;
                                } else {
                                    if(mainmenu_opts[k].curr > 0) mainmenu_opts[k].curr--;
                                    else mainmenu_opts[k].curr = mainmenu_opts[k].maxcurr - 1;
                                }
                                mainmenu_optsrecalc();
                            } else
                            if(world.freetotal > 0 && mainmenu_attrs && mainmenu_pris && mainmenu_sel >= 2 + 2 * world.numchar &&
                              mainmenu_sel < 2 + 2 * world.numchar + world.numpris) {
                                k = mainmenu_pris[mainmenu_sel - (2 + 2 * world.numchar)];
                                if(mainmenu_attrs[k] > 0) { mainmenu_attrs[k]--; mainmenu_rem++; mainmenu_optsrecalc(); }
                            } else
                            if(mainmenu_sel >= mainmenu_max - 1) { mainmenu_p = 0; mainmenu_select(mainmenu_sel - 1); }
                        break;
                        case GAME_CONFIG:
                            media_playclk();
                            switch(mainmenu_sel) {
                                case 0: if(world.langidx > 0) { mainmenu_setlanguage(world.langidx - 1); } break;
                                case 1: mainmenu_setres(mainmenu_resc - 1); break;
                                case 2: if(main_musvol > 0) { main_musvol--; } media_chime(main_musvol); break;
                                case 3: if(main_sfxvol > 0) { main_sfxvol--; } media_chime(main_sfxvol); break;
                                case 4: if(main_spcvol > 0) { main_spcvol--; } media_chime(main_spcvol); break;
                            }
                        break;
                        default: mainmenu_p = 0; break;
                    }
                break;
                case SDLK_RIGHT:
right:              mainmenu_p = 2;
                    switch(mainmenu_t) {
                        case GAME_LOGIN:
                            if(mainmenu_sel == mainmenu_max - 1) { mainmenu_p = 0; mainmenu_select(mainmenu_sel + 1); }
                            if(mainmenu_sel == mainmenu_max && !LOGIN_OK)
                                mainmenu_select(0);
                        break;
                        case GAME_LOADGAME:
                            if(!mainmenu_sel) {
                                if(!mainmenu_scr && mainmenu_sav + 1 < mainmenu_numsaves) {
                                    media_playclk(); mainmenu_scr++; mainmenu_sav++; mainmenu_fadeb = main_tick;
                                    for(x = 0; x < (int)(sizeof(mainmenu_sx)/sizeof(mainmenu_sx[0])); x++)
                                        mainmenu_sx[x] = 448 + (256 + 32) * x / (int)(sizeof(mainmenu_sx)/sizeof(mainmenu_sx[0]) - 1);
                                }
                            } else
                            if(mainmenu_sel == mainmenu_max - 1) { mainmenu_p = 0; mainmenu_select(mainmenu_sel + 1); }
                        break;
                        case GAME_NEWGAME:
                            media_playclk();
                            if(mainmenu_sel >= 2 && mainmenu_sel < 2 + 2 * world.numchar) {
                                k = (mainmenu_sel - 2) / 2;
                                if((mainmenu_sel & 1) && mainmenu_opts[k].maxpal > 0) {
                                    if(mainmenu_opts[k].currpal < mainmenu_opts[k].maxpal - 1) mainmenu_opts[k].currpal++;
                                    else mainmenu_opts[k].currpal = 0;
                                } else {
                                    if(mainmenu_opts[k].curr < mainmenu_opts[k].maxcurr - 1) mainmenu_opts[k].curr++;
                                    else mainmenu_opts[k].curr = 0;
                                }
                                mainmenu_optsrecalc();
                            } else
                            if(world.freetotal > 0 && mainmenu_attrs && mainmenu_pris && mainmenu_sel >= 2 + 2 * world.numchar &&
                              mainmenu_sel < 2 + 2 * world.numchar + world.numpris && mainmenu_rem > 0) {
                                k = mainmenu_pris[mainmenu_sel - (2 + 2 * world.numchar)];
                                if(mainmenu_attrs[k] < world.freeattrs) {
                                    mainmenu_attrs[k]++; mainmenu_rem--; mainmenu_optsrecalc();
                                }
                            } else
                            if(mainmenu_sel == mainmenu_max - 1) { mainmenu_p = 0; mainmenu_select(mainmenu_sel + 1); }
                            if(mainmenu_sel == mainmenu_max && !NEWGAME_OK)
                                mainmenu_select(0);
                        break;
                        case GAME_CONFIG:
                            media_playclk();
                            switch(mainmenu_sel) {
                                case 0: if(world.langidx + 1 < world.numlang) { mainmenu_setlanguage(world.langidx + 1); } break;
                                case 1: mainmenu_setres(mainmenu_resc + 1); break;
                                case 2: if(main_musvol < MIX_MAX_VOLUME) { main_musvol++; } media_chime(main_musvol); break;
                                case 3: if(main_sfxvol < MIX_MAX_VOLUME) { main_sfxvol++; } media_chime(main_sfxvol); break;
                                case 4: if(main_spcvol < MIX_MAX_VOLUME) { main_spcvol++; } media_chime(main_spcvol); break;
                            }
                        break;
                        default: mainmenu_p = 0; break;
                    }
                break;
                case SDLK_RETURN:
                    if(mainmenu_t == GAME_CONFIG) mainmenu_sel = 4;
                    if(mainmenu_t == GAME_LOADGAME) mainmenu_sel = mainmenu_max;
                    mainmenu_p = 1; media_playclk();
                break;
            }
        break;
        case SDL_KEYUP:
            ui_input_gp &= ~1;
            mainmenu_p = 0;
            switch(event.key.keysym.sym) {
                case SDLK_RETURN:
dosel:              mainmenu_p = 0;
                    switch(mainmenu_t) {
                        case GAME_LOGIN:
                            switch(mainmenu_sel) {
                                case 0:
                                    ui_input_start(
                                        mainmenu_src.x + mainmenu_i,
                                        mainmenu_src.y + (ui_lineh - world.fonts[FNT_SEL].size) / 2,
                                        mainmenu_j, world.fonts[FNT_SEL].size,
                                        world.text && world.text[GAME_USERNAME][0] ? world.text[GAME_USERNAME] : "Username",
                                        mainmenu_user, 255, 0);
                                break;
                                case 1:
                                    ui_input_start(
                                        mainmenu_src.x + mainmenu_i,
                                        mainmenu_src.y + ui_lineh + world.padtop[PAD_V] + (ui_lineh - world.fonts[FNT_SEL].size) / 2,
                                        mainmenu_j, world.fonts[FNT_SEL].size,
                                        world.text && world.text[GAME_PASSWORD][0] ? world.text[GAME_PASSWORD] : "Password",
                                        mainmenu_pass, 255, 1);
                                break;
                                case 2: mainmenu_switch(0); break;
                                case 3:
                                    game_init();
                                    switch(client_login(mainmenu_user, mainmenu_pass)) {
                                        case -1: mainmenu_error(lang[ERR_CONNECT]); break;
                                        case 1:
                                            /* map is always loaded on-demand */
                                            game_loadmap(world.players[0].data.map, world.players[0].data.x, world.players[0].data.y);
                                            mainmenu_exit(TAB_GAME);
                                            return 1;
                                        default:
                                            mainmenu_error(world.text && world.text[GAME_BADUSER][0] ? world.text[GAME_BADUSER] :
                                                "Bad username or password");
                                        break;
                                    }
                                break;
                            }
                        break;
                        case GAME_LOADGAME:
                            switch(mainmenu_sel) {
                                case 1: mainmenu_switch(0); break;
                                default:
                                    if(!mainmenu_saves) break;
                                    fn = (char*)main_alloc(strlen(files_saves) + 256 + 8);
                                    memcpy(tmp, world.hdr.id, 16); tmp[16] = 0;
                                    sprintf(fn, "%s" SEP "%s" SEP "%s.sav", files_saves, tmp, mainmenu_saves[mainmenu_sav]);
                                    game_init();
                                    x = world_loadstate(fn);
                                    free(fn);
                                    if(x && world.players && world.numplayers > 0) {
                                        main_timetotal = mainmenu_times ? mainmenu_times[mainmenu_sav] : 0;
                                        main_start = main_tick;
                                        world.players[0].logind = time(NULL);
                                        /* map is always loaded on-demand */
                                        game_loadmap(world.players[0].data.map, world.players[0].data.x, world.players[0].data.y);
                                        mainmenu_exit(TAB_GAME);
                                        return 1;
                                    }
                                    mainmenu_error(lang[ERR_TNG]);
                                break;
                            }
                        break;
                        case GAME_NEWGAME:
                            if(mainmenu_sel == 0) {
                                ui_input_start(
                                    mainmenu_src.x + mainmenu_i,
                                    mainmenu_src.y + (ui_lineh - world.fonts[FNT_SEL].size) / 2,
                                    mainmenu_j, world.fonts[FNT_SEL].size,
                                    world.text && world.text[GAME_USERNAME][0] ? world.text[GAME_USERNAME] : "Username",
                                    mainmenu_name, 255, 0);
                            } else
                            if(mainmenu_sel == 1) {
                                if(world.hdr.magic[0] == '#') break;
                                ui_input_start(
                                    mainmenu_src.x + mainmenu_i,
                                    mainmenu_src.y + ui_lineh + world.padtop[PAD_V] + (ui_lineh - world.fonts[FNT_SEL].size) / 2,
                                    mainmenu_j, world.fonts[FNT_SEL].size,
                                    world.text && world.text[GAME_PASSWORD][0] ? world.text[GAME_PASSWORD] : "Password",
                                    mainmenu_pass, 255, 1);
                            } else
                            if(mainmenu_sel == mainmenu_max - 1) mainmenu_switch(0); else
                            if(mainmenu_sel == mainmenu_max) {
                                if(mainmenu_curr && mainmenu_opts)
                                    for(k = 0; k < world.numchar; k++)
                                        mainmenu_curr[k] = (mainmenu_opts[k].curr << 8) | (mainmenu_opts[k].currpal & 0xff);
                                world_flush();
                                game_init();
                                k = mainmenu_numsaves;
                                if(world.hdr.magic[0] != '#') {
                                    switch(client_register(mainmenu_name, mainmenu_pass, mainmenu_curr, mainmenu_attrs)) {
                                        case -1: mainmenu_error(lang[ERR_CONNECT]); k = -1; break;
                                        case 1: mainmenu_exit(TAB_STARTGAME); break;
                                        default: k = -2; break;
                                    }
                                } else {
                                    if(mainmenu_saves)
                                        for(k = 0; k < mainmenu_numsaves && strcmp(mainmenu_saves[k], mainmenu_name); k++);
                                    if(k >= mainmenu_numsaves && world_register(mainmenu_name, (uint8_t*)mainmenu_name,
                                      mainmenu_curr, mainmenu_attrs) != -1) {
                                        main_timetotal = 0; main_start = main_tick;
                                        world.players[0].logind = time(NULL);
                                        mainmenu_exit(TAB_STARTGAME);
                                    } else k = -2;
                                }
                                if(k == -2)
                                    mainmenu_error(world.text && world.text[GAME_EXISTS][0] ? world.text[GAME_EXISTS] :
                                        "A player by that name already exists.");
                            }
                        break;
                        case GAME_CONFIG: main_saveconfig(); mainmenu_switch(0); break;
                        default:
                            switch(mainmenu_sel) {
                                case GAME_LOADGAME: mainmenu_switch(GAME_LOADGAME); break;
                                case GAME_NEWGAME: mainmenu_switch(GAME_NEWGAME); break;
                                case GAME_CONFIG: mainmenu_switch(GAME_CONFIG); break;
                                case GAME_EXIT: mainmenu_exit(TAB_CREDITS); return 1;
                            }
                        break;
                    }
                break;
            }
        break;
        case SDL_MOUSEMOTION:
            ui_input_gp &= ~1;
            x = (win_w - scr_w) / 2;
            y = mainmenu_dst.y;
            switch(mainmenu_t) {
                case GAME_LOGIN:
                    if(event.motion.x > x && event.motion.x < x + scr_w && event.motion.y > y && event.motion.y < y + scr_h) {
                        s = (event.motion.y - y - mainmenu_src.y) / (ui_btnh + world.padtop[PAD_V]);
                        if(s < mainmenu_min) s = mainmenu_min;
                        if(s > mainmenu_max - 1) s = mainmenu_max - 1;
                        if(s == mainmenu_max - 1 && event.motion.x >= x + scr_w / 2)
                            s = mainmenu_max;
                        if(s != mainmenu_max || LOGIN_OK)
                            mainmenu_select(s);
                    }
                break;
                case GAME_LOADGAME:
                    if(event.motion.x > x && event.motion.x < x + scr_w && event.motion.y > y && event.motion.y < y + scr_h) {
                        if(event.motion.y < y + 2 * world.padtop[PAD_V] + 256 + ui_lineh)
                            s = 0;
                        else
                            s = event.motion.x >= x + scr_w / 2 ? 2 : 1;
                        mainmenu_select(s);
                    }
                break;
                case GAME_NEWGAME:
                    if(event.motion.x > x && event.motion.x < x + scr_w && event.motion.y > y && event.motion.y < y + scr_h) {
                        if(event.motion.y > y + mainmenu_wh + mainmenu_src.y - world.padtop[PAD_V] - ui_btnh) {
                            if(event.motion.x >= x + scr_w / 2) s = mainmenu_max;
                            else s = mainmenu_max - 1;
                            if(s != mainmenu_max || NEWGAME_OK)
                                mainmenu_select(s);
                        } else {
                            if(event.motion.y < y + mainmenu_src.y + 2 * world.padtop[PAD_V] + ui_btnh)
                                s = 0;
                            else {
                                if(world.hdr.magic[0] != '#') {
                                    k = y + mainmenu_src.y + world.padtop[PAD_V] + 2 * (ui_btnh + world.padtop[PAD_V]);
                                    if(event.motion.y < k)
                                        s = 1;
                                } else
                                    k = y + mainmenu_src.y + 2 * world.padtop[PAD_V] + ui_btnh;
                                if(event.motion.x >= x + scr_w / 2) break;
                                if(event.motion.y >= k) {
                                    if(event.motion.y < k + world.numchar * (ui_btnh + ui_lineh)) {
                                        k = event.motion.y - k;
                                        s = 2 + (k / (ui_btnh + ui_lineh)) * 2;
                                        if(k % (ui_btnh + ui_lineh) > ui_btnh && mainmenu_opts[(s - 2) / 2].maxpal > 0)
                                            s++;
                                    } else {
                                        k += world.numchar * (ui_btnh + ui_lineh) + world.padtop[PAD_V];
                                        if(world.freetotal > 0 && event.motion.y < k + world.numpris * ui_btnh)
                                            s = (event.motion.y - k) / ui_btnh + 2 + 2 * world.numchar;
                                    }
                                }
                            }
                            if(s < mainmenu_min) s = mainmenu_min;
                            if(s > mainmenu_max - 2) s = mainmenu_max - 2;
                            mainmenu_select(s);
                        }
                    }
                break;
                case GAME_CONFIG:
                    if(mainmenu_wasres) mainmenu_wasres = 0;
                    else {
                        if(mainmenu_vp) {
                            x += mainmenu_src.x + 2 * world.padtop[PAD_H] + 16;
                            *mainmenu_vp = (event.motion.x - x - ui_sldb / 2) * MIX_MAX_VOLUME / (255 - ui_sldb);
                            if(*mainmenu_vp < 0) *mainmenu_vp = 0;
                            if(*mainmenu_vp > MIX_MAX_VOLUME) *mainmenu_vp = MIX_MAX_VOLUME;
                        } else {
                            if(event.motion.x > x && event.motion.x < x + scr_w && event.motion.y > y + mainmenu_src.y &&
                              event.motion.y < y + scr_h) {
                                s = (event.motion.y - y - mainmenu_src.y) / (ui_lineh + world.padtop[PAD_V]);
                                if(s < mainmenu_min) s = mainmenu_min;
                                if(s > mainmenu_max) s = mainmenu_max;
                                mainmenu_select(s);
                            }
                        }
                    }
                break;
                default:
                    if(event.motion.x > x && event.motion.x < x + scr_w && event.motion.y > y && event.motion.y < y + scr_h &&
                      (event.motion.y - y) % (mainmenu_h + 10) < mainmenu_h) {
                        s = (event.motion.y - y) / (mainmenu_h + 10) + GAME_LOADGAME;
                        if(s >= mainmenu_f)
                            mainmenu_select(s);
                    }
                break;
            }
        break;
        case SDL_MOUSEBUTTONDOWN:
            ui_input_gp &= ~1;
            x = (win_w - scr_w) / 2;
            y = mainmenu_dst.y;
            switch(mainmenu_t) {
                case GAME_LOGIN:
                    if(event.button.x > x && event.button.x < x + scr_w && event.button.y > y && event.button.y < y + scr_h) {
                        s = (event.button.y - y - mainmenu_src.y) / (ui_btnh + world.padtop[PAD_V]);
                        if(s < mainmenu_min) s = mainmenu_min;
                        if(s > mainmenu_max - 1) s = mainmenu_max - 1;
                        if(s == mainmenu_max - 1 && event.button.x >= x + scr_w / 2)
                            s = mainmenu_max;
                        if(s != mainmenu_max || LOGIN_OK) {
                            mainmenu_p = 1; media_playclk();
                            mainmenu_select(s);
                        }
                    }
                break;
                case GAME_LOADGAME:
                    if(event.button.x > x && event.button.x < x + scr_w && event.button.y > y && event.button.y < y + scr_h) {
                        if(event.button.y < y + 2 * world.padtop[PAD_V] + 256 + ui_lineh) {
                            mainmenu_sel = 0;
                            if(event.button.x <= x + scr_w / 2 - 128) goto left; else
                            if(event.button.x >= x + scr_w / 2 + 128) goto right; else {
                                mainmenu_p = 1; s = mainmenu_sel = 2; goto dosel;
                            }
                        } else {
                            mainmenu_p = 1;
                            s = event.button.x >= x + scr_w / 2 ? 2 : 1;
                        }
                        media_playclk();
                        mainmenu_select(s);
                    }
                break;
                case GAME_NEWGAME:
                    if(event.button.x > x && event.button.x < x + scr_w && event.button.y > y && event.button.y < y + scr_h) {
                        if(event.motion.y > y + mainmenu_wh + mainmenu_src.y - world.padtop[PAD_V] - ui_btnh) {
                            if(event.motion.x >= x + scr_w / 2) s = mainmenu_max;
                            else s = mainmenu_max - 1;
                            if(s != mainmenu_max || NEWGAME_OK) {
                                mainmenu_p = 1; media_playclk();
                                mainmenu_select(s);
                            }
                        } else {
                            if(event.motion.y < y + mainmenu_src.y + 2 * world.padtop[PAD_V] + ui_btnh)
                                s = 0;
                            else {
                                if(world.hdr.magic[0] != '#') {
                                    k = y + mainmenu_src.y + world.padtop[PAD_V] + 2 * (ui_btnh + world.padtop[PAD_V]);
                                    if(event.motion.y < k)
                                        s = 1;
                                } else
                                    k = y + mainmenu_src.y + 2 * world.padtop[PAD_V] + ui_btnh;
                                if(event.motion.x >= x + scr_w / 2) break;
                                if(event.motion.y >= k) {
                                    if(event.motion.y < k + world.numchar * (ui_btnh + ui_lineh)) {
                                        k = event.motion.y - k;
                                        s = 2 + (k / (ui_btnh + ui_lineh)) * 2;
                                        if(k % (ui_btnh + ui_lineh) > ui_btnh && mainmenu_opts[(s - 2) / 2].maxpal > 0) {
                                            m = (mainmenu_src.w / 2 - 2 * world.padtop[PAD_H] - ui_ol) / 16;
                                            if(m > ui_lineh) m = ui_lineh - 2;
                                            l = mainmenu_src.x + 2 * world.padtop[PAD_H] + ui_ol;
                                            if(event.motion.x >= x + l && event.motion.x < x + l + mainmenu_opts[(s - 2) / 2].maxpal * m) {
                                                mainmenu_opts[(s - 2) / 2].currpal = (event.motion.x - x - l) / m;
                                                mainmenu_optsrecalc();
                                                s++;
                                            } else break;
                                        } else {
                                            if(event.motion.x < x + mainmenu_src.x + 2 * world.padtop[PAD_H] +
                                              (mainmenu_src.w / 2 - 2 * world.padtop[PAD_H]) / 2) goto left;
                                            else goto right;
                                        }
                                    } else {
                                        if(event.motion.x < x + mainmenu_src.x + mainmenu_src.w / 2 - 100) break;
                                        k += world.numchar * (ui_btnh + ui_lineh) + world.padtop[PAD_V];
                                        if(world.freetotal > 0 && event.motion.y < k + world.numpris * ui_btnh) {
                                            if(event.motion.x < x + mainmenu_src.x + mainmenu_src.w / 2 - 50) goto left;
                                            else goto right;
                                        }
                                    }
                                }
                            }
                            if(s < mainmenu_min) s = mainmenu_min;
                            if(s > mainmenu_max - 2) s = mainmenu_max - 2;
                            mainmenu_p = 1; media_playclk();
                            mainmenu_select(s);
                        }
                    }
                break;
                case GAME_CONFIG:
                    if(event.button.x > x && event.button.x < x + scr_w && event.button.y > y + mainmenu_src.y &&
                      event.button.y < y + scr_h) {
                        s = (event.button.y - y - mainmenu_src.y) / (ui_lineh + world.padtop[PAD_V]);
                        if(s < mainmenu_min) s = mainmenu_min;
                        if(s > mainmenu_max) s = mainmenu_max;
                        mainmenu_p = 1; mainmenu_select(s);
                        x += mainmenu_src.x + 2 * world.padtop[PAD_H] + 16;
                        switch(mainmenu_sel) {
                            case 0:
                            case 1:
                                if(event.button.x >= x && event.button.x <= x + 127) goto left;
                                if(event.button.x >= x + 128 && event.button.x <= x + 255) goto right;
                            break;
                            case 2:
                                if(event.button.x >= x && event.button.x <= x + 255) mainmenu_vp = &main_musvol;
                            break;
                            case 3:
                                if(event.button.x >= x && event.button.x <= x + 255) mainmenu_vp = &main_sfxvol;
                            break;
                            case 4:
                                if(event.button.x >= x && event.button.x <= x + 255) mainmenu_vp = &main_spcvol;
                            break;
                            case 5: media_playclk(); break;
                        }
                        if(mainmenu_vp) {
                            *mainmenu_vp = (event.button.x - x - ui_sldb / 2) * MIX_MAX_VOLUME / (255 - ui_sldb);
                            if(*mainmenu_vp < 0) *mainmenu_vp = 0;
                            if(*mainmenu_vp > MIX_MAX_VOLUME) *mainmenu_vp = MIX_MAX_VOLUME;
                        }
                    }
                break;
                default:
                    if(event.button.x > x && event.button.x < x + scr_w && event.button.y > y && event.button.y < y + scr_h &&
                      (event.button.y - y) % (mainmenu_h + 10) < mainmenu_h) {
                        s = (event.button.y - y) / (mainmenu_h + 10) + GAME_LOADGAME;
                        if(s >= mainmenu_f) {
                            mainmenu_p = 1; media_playclk();
                            mainmenu_select(s);
                        }
                    } else {
                        title = spr_get(world.titimg.cat, world.titimg.idx, 0);
                        k = mainmenu_bgdst.y + (title ? title->h : 32) + mainmenu_titledst.h;
                        l = title ? title->w : 0; if(mainmenu_title && mainmenu_titledst.w > l) l = mainmenu_titledst.w;
                        if(l > 0 && event.button.y > mainmenu_bgdst.y && event.button.y < k &&
                          event.button.x > (win_w - l) / 2 && event.button.x < (win_w - l) / 2 + l) {
                            media_playclk(); main_open_url(world.url && *world.url ? world.url : main_url);
                        } else
                        if(event.button.y > mainmenu_bgdst.y + mainmenu_bgdst.h - 42 && event.button.x > mainmenu_bgdst.x &&
                          event.button.x < mainmenu_bgdst.x + 120) {
                            media_playclk(); main_open_url(main_url);
                        }
                    }
                break;
            }
        break;
        case SDL_MOUSEBUTTONUP:
            mainmenu_p = 0; ui_input_gp &= ~1;
            x = (win_w - scr_w) / 2;
            y = mainmenu_dst.y;
            switch(mainmenu_t) {
                case GAME_LOGIN:
                    if(event.button.x > x && event.button.x < x + scr_w && event.button.y > y && event.button.y < y + scr_h) {
                        s = (event.button.y - y - mainmenu_src.y) / (ui_btnh + world.padtop[PAD_V]);
                        if(s < mainmenu_min) s = mainmenu_min;
                        if(s > mainmenu_max - 1) s = mainmenu_max - 1;
                        if(s == mainmenu_max - 1 && event.button.x >= x + scr_w / 2)
                            s = mainmenu_max;
                        if(s != mainmenu_max || LOGIN_OK) {
                            if(mainmenu_sel == s) goto dosel;
                            mainmenu_select(s);
                        }
                    }
                break;
                case GAME_LOADGAME:
                    if(event.button.x > x && event.button.x < x + scr_w && event.button.y > y && event.button.y < y + scr_h) {
                        if(event.button.y >= y + 2 * world.padtop[PAD_V] + 256 + ui_lineh) {
                            s = event.button.x >= x + scr_w / 2 ? 2 : 1;
                            if(mainmenu_sel == s) goto dosel;
                            mainmenu_select(s);
                        }
                    }
                break;
                case GAME_NEWGAME:
                    if(event.button.x > x && event.button.x < x + scr_w && event.button.y > y && event.button.y < y + scr_h) {
                        if(event.motion.y > y + mainmenu_wh + mainmenu_src.y - world.padtop[PAD_V] - ui_btnh) {
                            if(event.motion.x >= x + scr_w / 2) s = mainmenu_max;
                            else s = mainmenu_max - 1;
                            if(s != mainmenu_max || NEWGAME_OK) {
                                if(mainmenu_sel == s) goto dosel;
                                mainmenu_select(s);
                            }
                        } else {
                            if(event.motion.y < y + mainmenu_src.y + 2 * world.padtop[PAD_V] + ui_btnh)
                                s = 0;
                            else {
                                if(world.hdr.magic[0] != '#') {
                                    k = y + mainmenu_src.y + world.padtop[PAD_V] + 2 * (ui_btnh + world.padtop[PAD_V]);
                                    if(event.motion.y < k)
                                        s = 1;
                                    else break;
                                } else break;
                            }
                            if(s < mainmenu_min) s = mainmenu_min;
                            if(s > mainmenu_max - 2) s = mainmenu_max - 2;
                            if(mainmenu_sel == s) goto dosel;
                            mainmenu_select(s);
                        }
                    }
                break;
                case GAME_CONFIG:
                    if(mainmenu_vp) { media_chime(*mainmenu_vp); mainmenu_vp = NULL; }
                    if(event.button.x > x && event.button.x < x + scr_w && event.button.y > y + mainmenu_src.y &&
                      event.button.y < y + scr_h && mainmenu_sel == 5) {
                        s = (event.button.y - y - mainmenu_src.y) / (ui_lineh + world.padtop[PAD_V]);
                        if(s < mainmenu_min) s = mainmenu_min;
                        if(s > mainmenu_max) s = mainmenu_max;
                        if(s == 5) goto dosel;
                    }
                break;
                default:
                    if(event.button.x > x && event.button.x < x + scr_w && event.button.y > y && event.button.y < y + scr_h &&
                      (event.button.y - y) % (mainmenu_h + 10) < mainmenu_h) {
                        s = (event.button.y - y) / (mainmenu_h + 10) + GAME_LOADGAME;
                        if(s > mainmenu_max) s = mainmenu_max;
                        if(s >= mainmenu_f) {
                            if(mainmenu_sel == s) goto dosel;
                            mainmenu_select(s);
                        }
                    }
                break;
            }
        break;
        case SDL_CONTROLLERBUTTONDOWN:
            ui_input_gp |= 1;
            switch(event.cbutton.button) {
                case SDL_CONTROLLER_BUTTON_START:
                case SDL_CONTROLLER_BUTTON_A:
                case SDL_CONTROLLER_BUTTON_B:
                case SDL_CONTROLLER_BUTTON_X:
                case SDL_CONTROLLER_BUTTON_Y:
                    if(mainmenu_t == GAME_CONFIG) mainmenu_sel = 4;
                    mainmenu_p = 1; media_playclk();
                break;
                case SDL_CONTROLLER_BUTTON_DPAD_UP: mainmenu_select(mainmenu_sel - 1); break;
                case SDL_CONTROLLER_BUTTON_DPAD_DOWN: mainmenu_select(mainmenu_sel + 1); break;
                case SDL_CONTROLLER_BUTTON_DPAD_LEFT: goto left;
                case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: goto right;
            }
        break;
        case SDL_CONTROLLERBUTTONUP:
            ui_input_gp |= 1;
            mainmenu_p = 0;
            switch(event.cbutton.button) {
                case SDL_CONTROLLER_BUTTON_BACK: goto back;
                case SDL_CONTROLLER_BUTTON_START:
                case SDL_CONTROLLER_BUTTON_A:
                case SDL_CONTROLLER_BUTTON_B:
                case SDL_CONTROLLER_BUTTON_X:
                case SDL_CONTROLLER_BUTTON_Y: goto dosel;
            }
        break;
        case SDL_CONTROLLERAXISMOTION:
            ui_input_gp |= 1;
            if(event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY || event.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY) {
                if(event.caxis.value < -main_gptres) mainmenu_select(mainmenu_sel - 1);
                if(event.caxis.value > main_gptres) mainmenu_select(mainmenu_sel + 1);
            }
            if(event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX || event.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX) {
                if(event.caxis.value < -main_gptres) goto left;
                if(event.caxis.value > main_gptres) goto right;
            }
        break;
    }
    return 1;
}

/**
 * View layer
 */
void mainmenu_view()
{
    world_sprite_t *title;
    SDL_Rect src, dst;
    int i;

    if(!main_draw) return;
    theora_produce(&theora_ctx);
    mainmenu_redraw();
    title = spr_get(world.titimg.cat, world.titimg.idx, 0);
    if(mainmenu_fade) {
        if(mainmenu_fade > 0) {
            mainmenu_a = (main_tick - mainmenu_fadeb) * 255 / 500;
            if(!mainmenu_fst) SDL_SetTextureAlphaMod(logo, mainmenu_a > 255 ? 255 : mainmenu_a);
            if(mainmenu_a >= 255) { mainmenu_a = 255; mainmenu_fade = 0; mainmenu_fst = 0; }
            if(mainmenu_bg) SDL_SetTextureAlphaMod(mainmenu_bg, mainmenu_a);
        } else {
            /* make sure we don't fade out background if it's the same as credits' when quitting */
            mainmenu_a = 255 - ((main_tick - mainmenu_fadeb) * 255 / 500);
            if(mainmenu_a <= 0) {
                spr_fade(255); SDL_SetTextureAlphaMod(logo, 255);
                /* right now the screen is cleared. If we switch tabs now, there'll be blink of one blank
                 * screen unless we draw something on it. It should be the same background as creadit's */
                if(mainmenu_bg && mainmenu_n == TAB_CREDITS && world.crdimage.idx == world.bgimage.idx)
                    SDL_RenderCopy(renderer, mainmenu_bg, &mainmenu_bgsrc, &mainmenu_bgdst);
                main_switchtab(mainmenu_n);
                return;
            }
            if(mainmenu_prev) SDL_SetTextureAlphaMod(mainmenu_prev, mainmenu_a);
            SDL_SetTextureAlphaMod(logo, mainmenu_a);
            if(mainmenu_bg && (mainmenu_n != TAB_CREDITS || world.crdimage.idx != world.bgimage.idx))
                SDL_SetTextureAlphaMod(mainmenu_bg, mainmenu_a);
        }
        if(mainmenu_np)
            for(i = 0; i < WORLD_MAXPRX; i++)
                if(mainmenu_prx[i]) SDL_SetTextureAlphaMod(mainmenu_prx[i], mainmenu_a);
        if(title) spr_fade(mainmenu_a);
        if(mainmenu_title) SDL_SetTextureAlphaMod(mainmenu_title, mainmenu_a);
        if(mainmenu_frm) SDL_SetTextureAlphaMod(mainmenu_frm, mainmenu_a);
        if(screen) SDL_SetTextureAlphaMod(screen, mainmenu_a);
    }
    if(mainmenu_bg) {
        if(theora_ctx.hasVideo)
            theora_video(&theora_ctx, mainmenu_bg);
        SDL_RenderCopy(renderer, mainmenu_bg, &mainmenu_bgsrc, &mainmenu_bgdst);
    }
    if(mainmenu_np) {
        for(i = 0; i < WORLD_MAXPRX; i++)
            if(mainmenu_prx[i] && mainmenu_prxtime[i] + world.menuprx[i].t * 10 < main_tick) {
                mainmenu_prxtime[i] = main_tick;
                mainmenu_prxrect[i].x += world.menuprx[i].dx;
                mainmenu_prxrect[i].y += world.menuprx[i].dy;
                if(mainmenu_prxrect[i].x + mainmenu_prxrect[i].w < 0 && world.menuprx[i].dx < 0)
                    mainmenu_prxrect[i].x += 2 * mainmenu_prxrect[i].w;
                if(mainmenu_prxrect[i].x > mainmenu_bgdst.w && world.menuprx[i].dx > 0)
                    mainmenu_prxrect[i].x -= 2 * mainmenu_prxrect[i].w;
                if(mainmenu_prxrect[i].y + mainmenu_prxrect[i].h < 0 && world.menuprx[i].dy < 0)
                    mainmenu_prxrect[i].y += 2 * mainmenu_prxrect[i].h;
                if(mainmenu_prxrect[i].y > mainmenu_bgdst.h && world.menuprx[i].dy > 0)
                    mainmenu_prxrect[i].y -= 2 * mainmenu_prxrect[i].h;
                src.w = dst.w = mainmenu_prxrect[i].w; src.x = 0;
                src.h = dst.h = mainmenu_prxrect[i].h; src.y = 0;
                for(dst.y = mainmenu_bgdst.y - (dst.h - mainmenu_prxrect[i].y % dst.h);
                  dst.y < mainmenu_bgdst.y + mainmenu_bgdst.h; dst.y += dst.h)
                    for(dst.x = mainmenu_bgdst.x - (dst.w - mainmenu_prxrect[i].x % dst.w);
                      dst.x < mainmenu_bgdst.x + mainmenu_bgdst.w; dst.x += dst.w)
                        SDL_RenderCopy(renderer, mainmenu_prx[i], &src, &dst);
            }
    }
    mainmenu_titledst.y = mainmenu_bgdst.y + 32;
    if(title) {
        spr_render((win_w - title->w) / 2, mainmenu_bgdst.y, title, ui_anim(title->t, title->f, -1), 0, title->w, title->h,
            mainmenu_a);
        mainmenu_titledst.y = mainmenu_bgdst.y + title->h;
    }
    if(mainmenu_title) {
        mainmenu_titledst.x = (win_w - mainmenu_titledst.w) / 2;
        SDL_RenderCopy(renderer, mainmenu_title, NULL, &mainmenu_titledst);
        mainmenu_titledst.y += mainmenu_titledst.h;
    }
    if(screen)
        SDL_RenderCopy(renderer, screen, NULL, &mainmenu_dst);
    if(mainmenu_frm)
        SDL_RenderCopy(renderer, mainmenu_frm, NULL, &mainmenu_dst);
    if(mainmenu_t == GAME_LOADGAME && mainmenu_savs) {
        if(mainmenu_scr) {
            i = main_tick - mainmenu_fadeb;
            if(i < (int)(sizeof(mainmenu_sx)/sizeof(mainmenu_sx[0]))) {
                mainmenu_savsrc.x = mainmenu_sx[i];
                if(mainmenu_savsrc.x < 0) mainmenu_savsrc.x = 0;
                if(mainmenu_savsrc.x > 1408 - 512) mainmenu_savsrc.x = 1408 - 512;
            } else
                mainmenu_savscr();
        } else
            mainmenu_savsrc.x = 448;
        mainmenu_savsrc.w = dst.w = 512; mainmenu_savsrc.h = dst.h = 256; dst.y = mainmenu_dst.y + mainmenu_src.y + world.padtop[PAD_V];
        dst.x = (win_w - dst.w) / 2;
        SDL_SetTextureAlphaMod(mainmenu_savs, mainmenu_a);
        SDL_RenderCopy(renderer, mainmenu_savs, &mainmenu_savsrc, &dst);
    }
    if(mainmenu_t == GAME_NEWGAME && mainmenu_prev) {
        dst.x = mainmenu_dst.x + mainmenu_prevdst.x; dst.y = mainmenu_dst.y + mainmenu_prevdst.y;
        dst.w = mainmenu_prevdst.w; dst.h = mainmenu_prevdst.h;
        mainmenu_prevsrc.x = mainmenu_prevsrc.w * ui_anim(mainmenu_prevt, mainmenu_prevf, -1);
        SDL_RenderCopy(renderer, mainmenu_prev, &mainmenu_prevsrc, &dst);
    }
    if(world.hdr.magic[0] == '#' && !world.hdr.enc) {
        if(world.gpl) { src.x = 120; src.w = 100; } else { src.x = 0; src.w = 120; }
        src.y = 158; dst.h = src.h = 42; dst.w = src.w;
        dst.x = mainmenu_bgdst.x; dst.y = mainmenu_bgdst.y + mainmenu_bgdst.h - src.h;
        SDL_RenderCopy(renderer, logo, &src, &dst);
    }
    if(mainmenu_err) {
        dst.x = 0; dst.y = mainmenu_bgdst.y + mainmenu_bgdst.h - 42; dst.w = win_w; dst.h = 42;
        SDL_RenderCopy(renderer, mainmenu_err, NULL, &dst);
    }
}
