code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior 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 2
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

#ifndef SPRITE_H

#define SPRITE_H

void KillSprite(int16_t SpriteNum);
int16_t SpawnSprite(short stat, short id, STATEp state, short sectnum, int x, int y, int z, int ang, int vel);
void SpriteSetup(void);
int move_actor(short SpriteNum, int xchange, int ychange, int zchange);
short GetSpriteDir(short sn);
short GetDirToPlayer(short sn);
short PlayerInVision(short sn, short view_deg);
short PlayerVisible(short sn);
short SpriteLookSector(short SpriteNum, short range);
short SpriteCanGoForward(short SpriteNum, short range);
void  SpriteFindNewDirection(short SpriteNum, short range);
int DoWalk(short SpriteNum);
int DoBody(short SpriteNum);
SWBOOL CanMoveHere(int16_t spritenum);
SWBOOL SpriteOverlap(int16_t spritenum_a, int16_t spritenum_b);
int DoActorDie(short SpriteNum, short weapon);
int DoGet(short SpriteNum);
void SpriteControl(void);
void SetEnemyInactive(short SpriteNum);
void DoActorZrange(short SpriteNum);
void PreMapCombineFloors(void);
void SpriteSetupPost(void);
int ActorCoughItem(short SpriteNum);
SWBOOL ActorSpawn(SPRITEp sp);
int SpawnItemsMatch(short match);
void PicAnimOff(short picnum);
int MissileWaterAdjust(short SpriteNum);
SWBOOL SpriteOverlapZ(int16_t spritenum_a,int16_t spritenum_b,int z_overlap);

enum
{
    InvDecl_Armor,      // ie. +50 armour
    InvDecl_Kevlar,     // ie. +100 armour
    InvDecl_SmMedkit,
    InvDecl_Booster,    // ie. fortune cookie
    InvDecl_Medkit,
    InvDecl_ChemBomb,   // ie. gas bomb
    InvDecl_FlashBomb,
    InvDecl_Caltrops,
    InvDecl_NightVision,
    InvDecl_RepairKit,
    InvDecl_Cloak,
    InvDecl_TOTAL
};

struct InventoryDecl_t
{
    const char *name;
    int  amount;
};
extern struct InventoryDecl_t InventoryDecls[InvDecl_TOTAL];

#endif

repo_name: orochi/nblood

license: mit


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior 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 2
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

#ifdef MAKE_STAG_ENUM
#define STAG_ENTRY(st1_name, number, flgs)  st1_name = number,
#endif

#ifdef MAKE_STAG_TABLE
#define STAG_ENTRY(st1_name, number, flgs)                      \
    strcpy(StagInfo[number].name, #st1_name);                       \
    StagInfo[number].flags = flgs;
#endif

#define STAGF_MATCH_TAG2 (BIT(31))

STAG_ENTRY(SECT_SINK,                   0,      0)
STAG_ENTRY(SECT_OPERATIONAL,            1,      0)
STAG_ENTRY(SECT_CURRENT,                3,      0)
STAG_ENTRY(SECT_NO_RIDE,                5,      0)
STAG_ENTRY(SECT_DIVE_AREA,              7,      STAGF_MATCH_TAG2)
STAG_ENTRY(SECT_UNDERWATER,             8,      STAGF_MATCH_TAG2)
STAG_ENTRY(SECT_UNDERWATER2,            9,      STAGF_MATCH_TAG2)

STAG_ENTRY(SO_ANGLE,                    16,     0)
STAG_ENTRY(SECT_FLOOR_PAN,              19,     0)

STAG_ENTRY(SECT_CEILING_PAN,            21,     0)

STAG_ENTRY(SECT_WALL_PAN_SPEED,         23,     0)
STAG_ENTRY(SECT_WALL_PAN_ANG,           24,     0)

STAG_ENTRY(SECT_DEBRIS_SEWER,           25,     0)

STAG_ENTRY(SECT_SO_CENTER,              27,     0)

STAG_ENTRY(SECT_MATCH,                  28,     STAGF_MATCH_TAG2)
STAG_ENTRY(SECT_LOCK_DOOR,              29,     0)
STAG_ENTRY(SPRI_CLIMB_MARKER,           30,     0)
STAG_ENTRY(SECT_SO_SPRITE_OBJ,          31,     0)
STAG_ENTRY(SECT_SO_DONT_BOB,            32,     0)

STAG_ENTRY(SECT_SO_SINK_DEST,           33,     0)
STAG_ENTRY(SECT_SO_DONT_SINK,           34,     0)

STAG_ENTRY(SECT_SO_FORM_WHIRLPOOL,      37,     0)
STAG_ENTRY(SECT_ACTOR_BLOCK,            38,     0)
STAG_ENTRY(SECT_SO_CLIP_DIST,           39,     0)

STAG_ENTRY(MULTI_PLAYER_START,          42,     0)

STAG_ENTRY(FIREBALL_TRAP,               43,     STAGF_MATCH_TAG2)
STAG_ENTRY(BOLT_TRAP,                   44,     STAGF_MATCH_TAG2)
STAG_ENTRY(SECT_SO_DONT_ROTATE,         45,     0)

STAG_ENTRY(PARALLAX_LEVEL,              46,     0)
STAG_ENTRY(SECT_DONT_COPY_PALETTE,      47,     0)

STAG_ENTRY(MULTI_COOPERATIVE_START,     48,     0)

STAG_ENTRY(SO_SET_SPEED,                49,     0)
STAG_ENTRY(SO_SPIN,                     50,     0)
STAG_ENTRY(SO_SPIN_REVERSE,             51,     0)
STAG_ENTRY(SO_BOB_START,                52,     0)
STAG_ENTRY(SO_BOB_SPEED,                53,     0)

STAG_ENTRY(SO_TURN_SPEED,               55,     0)

STAG_ENTRY(LAVA_ERUPT,                  56,     0)

STAG_ENTRY(SECT_EXPLODING_CEIL_FLOOR,   57,     STAGF_MATCH_TAG2)
STAG_ENTRY(SECT_COPY_DEST,              58,     STAGF_MATCH_TAG2)
STAG_ENTRY(SECT_COPY_SOURCE,            59,     STAGF_MATCH_TAG2)

STAG_ENTRY(SO_SHOOT_POINT,              62,     0)
STAG_ENTRY(SPEAR_TRAP,                  63,     STAGF_MATCH_TAG2)

STAG_ENTRY(SO_SYNC1,                    64,     0)
STAG_ENTRY(SO_SYNC2,                    65,     0)

STAG_ENTRY(DEMO_CAMERA,                 66,     0)
STAG_ENTRY(SO_LIMIT_TURN,               68,     0)

STAG_ENTRY(SPAWN_SPOT,                  69,     STAGF_MATCH_TAG2)
STAG_ENTRY(SO_MATCH_EVENT,              70,     STAGF_MATCH_TAG2)

STAG_ENTRY(SO_SLOPE_FLOOR_TO_POINT,     71,     0)
STAG_ENTRY(SO_SLOPE_CEILING_TO_POINT,   72,     0)

STAG_ENTRY(SO_TORNADO,                  73,     0)
STAG_ENTRY(SO_FLOOR_MORPH,              74,     0)
STAG_ENTRY(SO_AMOEBA,                   75,     0)

STAG_ENTRY(SO_MAX_DAMAGE,               76,     0)
STAG_ENTRY(SO_RAM_DAMAGE,               77,     0)
STAG_ENTRY(SO_CLIP_BOX,                 78,     0)

STAG_ENTRY(SO_SLIDE,                    79,     0)
STAG_ENTRY(SO_KILLABLE,                 80,     0)
STAG_ENTRY(SO_AUTO_TURRET,              81,     0)
STAG_ENTRY(SECT_DAMAGE,                 82,     0)

STAG_ENTRY(WARP_TELEPORTER,             84,     STAGF_MATCH_TAG2)
STAG_ENTRY(WARP_CEILING_PLANE,          85,     STAGF_MATCH_TAG2)
STAG_ENTRY(WARP_FLOOR_PLANE,            86,     STAGF_MATCH_TAG2)
STAG_ENTRY(WARP_COPY_SPRITE1,           87,     STAGF_MATCH_TAG2)
STAG_ENTRY(WARP_COPY_SPRITE2,           88,     STAGF_MATCH_TAG2)

STAG_ENTRY(PLAX_GLOB_Z_ADJUST,          90,     0)
STAG_ENTRY(PLAX_Z_ADJUST,               91,     0)

STAG_ENTRY(SECT_VATOR,                  92,     STAGF_MATCH_TAG2)
STAG_ENTRY(SECT_VATOR_DEST,             94,     0)

STAG_ENTRY(CEILING_Z_ADJUST,            97,     0)
STAG_ENTRY(FLOOR_Z_ADJUST,              98,     0)
STAG_ENTRY(FLOOR_SLOPE_DONT_DRAW,       99,     0)

STAG_ENTRY(SO_SCALE_INFO,               100,    0)
STAG_ENTRY(SO_SCALE_POINT_INFO,         101,    0)
STAG_ENTRY(SO_SCALE_XY_MULT,            102,    0)
STAG_ENTRY(SECT_WALL_MOVE,              103,    STAGF_MATCH_TAG2)
STAG_ENTRY(SECT_WALL_MOVE_CANSEE,       104,    STAGF_MATCH_TAG2)

STAG_ENTRY(SECT_SPIKE,                  106,    STAGF_MATCH_TAG2)
STAG_ENTRY(LIGHTING,                    108,    STAGF_MATCH_TAG2)
STAG_ENTRY(LIGHTING_DIFFUSE,            109,    STAGF_MATCH_TAG2)

STAG_ENTRY(VIEW_THRU_CEILING,           120,    0)
STAG_ENTRY(VIEW_THRU_FLOOR,             121,    0)

STAG_ENTRY(VIEW_LEVEL1,                 110,    0)
STAG_ENTRY(VIEW_LEVEL2,                 111,    0)
STAG_ENTRY(VIEW_LEVEL3,                 112,    0)
STAG_ENTRY(VIEW_LEVEL4,                 113,    0)
STAG_ENTRY(VIEW_LEVEL5,                 114,    0)
STAG_ENTRY(VIEW_LEVEL6,                 115,    0)

STAG_ENTRY(SO_WALL_DONT_MOVE_UPPER,     130,    STAGF_MATCH_TAG2)
STAG_ENTRY(SO_WALL_DONT_MOVE_LOWER,     131,    STAGF_MATCH_TAG2)
STAG_ENTRY(BREAKABLE,                   132,    STAGF_MATCH_TAG2)
STAG_ENTRY(QUAKE_SPOT,                  133,    STAGF_MATCH_TAG2)
STAG_ENTRY(SOUND_SPOT,                  134,    STAGF_MATCH_TAG2)
STAG_ENTRY(SLIDE_SECTOR,                135,    0)
STAG_ENTRY(CEILING_FLOOR_PIC_OVERRIDE,  136,    0)
STAG_ENTRY(TRIGGER_SECTOR,              140,    STAGF_MATCH_TAG2)
STAG_ENTRY(DELETE_SPRITE,               141,    STAGF_MATCH_TAG2)
STAG_ENTRY(SECT_ROTATOR,                143,    STAGF_MATCH_TAG2)
STAG_ENTRY(SECT_ROTATOR_PIVOT,          144,    STAGF_MATCH_TAG2)
STAG_ENTRY(SECT_SLIDOR,                 145,    STAGF_MATCH_TAG2)
STAG_ENTRY(SECT_CHANGOR,                146,    STAGF_MATCH_TAG2)
STAG_ENTRY(SO_DRIVABLE_ATTRIB,          147,    0)
STAG_ENTRY(WALL_DONT_STICK,             148,    0)
STAG_ENTRY(SPAWN_ITEMS,                 149,    0)
STAG_ENTRY(STOP_SOUND_SPOT,             150,    0)

STAG_ENTRY(BOUND_FLOOR_UPPER,           200,    0)
STAG_ENTRY(BOUND_FLOOR_LOWER,           201,    0)
STAG_ENTRY(BOUND_FLOOR_BASE_OFFSET,     202,    0)
STAG_ENTRY(BOUND_FLOOR_OFFSET,          203,    0)

STAG_ENTRY(BOUND_SO_UPPER0,             500,    0)
STAG_ENTRY(BOUND_SO_LOWER0,             501,    0)
STAG_ENTRY(BOUND_SO_UPPER1,             505,    0)
STAG_ENTRY(BOUND_SO_LOWER1,             506,    0)
STAG_ENTRY(BOUND_SO_UPPER2,             510,    0)
STAG_ENTRY(BOUND_SO_LOWER2,             511,    0)
STAG_ENTRY(BOUND_SO_UPPER3,             515,    0)
STAG_ENTRY(BOUND_SO_LOWER3,             516,    0)
STAG_ENTRY(BOUND_SO_UPPER4,             520,    0)
STAG_ENTRY(BOUND_SO_LOWER4,             521,    0)
STAG_ENTRY(BOUND_SO_UPPER5,             525,    0)
STAG_ENTRY(BOUND_SO_LOWER5,             526,    0)
STAG_ENTRY(BOUND_SO_UPPER6,             530,    0)
STAG_ENTRY(BOUND_SO_LOWER6,             531,    0)
STAG_ENTRY(BOUND_SO_UPPER7,             535,    0)
STAG_ENTRY(BOUND_SO_LOWER7,             536,    0)
STAG_ENTRY(BOUND_SO_UPPER8,             540,    0)
STAG_ENTRY(BOUND_SO_LOWER8,             541,    0)
STAG_ENTRY(BOUND_SO_UPPER9,             545,    0)
STAG_ENTRY(BOUND_SO_LOWER9,             546,    0)
STAG_ENTRY(BOUND_SO_UPPER10,            550,    0)
STAG_ENTRY(BOUND_SO_LOWER10,            551,    0)
STAG_ENTRY(BOUND_SO_UPPER11,            555,    0)
STAG_ENTRY(BOUND_SO_LOWER11,            556,    0)
STAG_ENTRY(BOUND_SO_UPPER12,            560,    0)
STAG_ENTRY(BOUND_SO_LOWER12,            561,    0)
STAG_ENTRY(BOUND_SO_UPPER13,            565,    0)
STAG_ENTRY(BOUND_SO_LOWER13,            566,    0)
STAG_ENTRY(BOUND_SO_UPPER14,            570,    0)
STAG_ENTRY(BOUND_SO_LOWER14,            571,    0)
STAG_ENTRY(BOUND_SO_UPPER15,            575,    0)
STAG_ENTRY(BOUND_SO_LOWER15,            576,    0)
STAG_ENTRY(BOUND_SO_UPPER16,            580,    0)
STAG_ENTRY(BOUND_SO_LOWER16,            581,    0)
STAG_ENTRY(BOUND_SO_UPPER17,            585,    0)
STAG_ENTRY(BOUND_SO_LOWER17,            586,    0)
STAG_ENTRY(BOUND_SO_UPPER18,            590,    0)
STAG_ENTRY(BOUND_SO_LOWER18,            591,    0)
STAG_ENTRY(BOUND_SO_UPPER19,            595,    0)
STAG_ENTRY(BOUND_SO_LOWER19,            596,    0)
STAG_ENTRY(TV_CAMERA,                  1000,    0)
STAG_ENTRY(AMBIENT,                    1002,    0)
STAG_ENTRY(ECHOSPOT,                   1005,    0)
STAG_ENTRY(DRIP_GENERATOR,             1006,    0)

#undef STAG_ENTRY


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#include "build.h"
#include "common.h"
#include "common_game.h"
#include "compat.h"
#include "dynamicgtk.h"
#include "game.h"
#include "grpscan.h"
#include "gtkpixdata.h"
#include "config.h"

#if defined POLYMER && 0
# define POLYMEROPTION
#endif

enum
{
    NONE,
    ALL,
    POPULATE_VIDEO,
    POPULATE_CONFIG,
    POPULATE_GAME,
};

enum
{
    TAB_CONFIG,
    TAB_GAME,
    TAB_MESSAGES,
};

enum
{
    INPUT_KB,
    INPUT_MOUSE,
    INPUT_JOYSTICK,
    INPUT_ALL,
};

static struct
{
    GtkWidget *startwin;
    GtkWidget *hlayout;
    GtkWidget *banner;
    GtkWidget *vlayout;
    GtkWidget *tabs;
    GtkWidget *configtlayout;
    GtkWidget *displayvlayout;
    GtkWidget *vmode3dlabel;
    GtkWidget *vmode3dcombo;
    GtkWidget *fullscreencheck;
#ifdef POLYMEROPTION
    GtkWidget *polymercheck;
#endif
    GtkWidget *inputdevlabel;
    GtkWidget *inputdevcombo;
    GtkWidget *emptyhlayout;
    GtkWidget *alwaysshowcheck;
    GtkWidget *configtab;
    GtkWidget *gamevlayout;
    GtkWidget *gamelabel;
    GtkWidget *gamescroll;
    GtkWidget *gamelist;
    GtkWidget *gametab;
    GtkWidget *messagesscroll;
    GtkWidget *messagestext;
    GtkWidget *messagestab;
    GtkWidget *buttons;
    GtkWidget *cancelbutton;
    GtkWidget *cancelbuttonalign;
    GtkWidget *cancelbuttonlayout;
    GtkWidget *cancelbuttonicon;
    GtkWidget *cancelbuttonlabel;
    GtkWidget *startbutton;
    GtkWidget *startbuttonalign;
    GtkWidget *startbuttonlayout;
    GtkWidget *startbuttonicon;
    GtkWidget *startbuttonlabel;
} stwidgets;

static struct
{
    grpfile_t const * grp;
    ud_setup_t shared;
#ifdef POLYMEROPTION
    int polymer;
#endif
} settings;

static int32_t retval = -1, mode = TAB_MESSAGES;
extern int32_t gtkenabled;
static void PopulateForm(unsigned char pgs);

// -- EVENT CALLBACKS AND CREATION STUFF --------------------------------------

static void on_vmode3dcombo_changed(GtkComboBox *combobox, gpointer user_data)
{
    GtkTreeModel *data;
    GtkTreeIter iter;
    int32_t val;
    UNREFERENCED_PARAMETER(user_data);

    if (!gtk_combo_box_get_active_iter(combobox, &iter)) return;
    if (!(data = gtk_combo_box_get_model(combobox))) return;
    gtk_tree_model_get(data, &iter, 1, &val, -1);
    settings.shared.xdim = validmode[val].xdim;
    settings.shared.ydim = validmode[val].ydim;
    settings.shared.bpp = validmode[val].bpp;
}

static void on_fullscreencheck_toggled(GtkToggleButton *togglebutton, gpointer user_data)
{
    UNREFERENCED_PARAMETER(user_data);
    settings.shared.fullscreen = gtk_toggle_button_get_active(togglebutton);
    PopulateForm(POPULATE_VIDEO);
}

#ifdef POLYMEROPTION
static void on_polymercheck_toggled(GtkToggleButton *togglebutton, gpointer user_data)
{
    UNREFERENCED_PARAMETER(user_data);
    if (gtk_toggle_button_get_active(togglebutton))
    {
        glrendmode = REND_POLYMER;
        settings.polymer = TRUE;
        if (settings.shared.bpp == 8)
        {
            settings.shared.bpp = 32;
            PopulateForm(POPULATE_VIDEO);
        }
    }
    else
    {
        glrendmode = REND_POLYMOST;
        settings.polymer = FALSE;
    }
}
#endif

static void on_inputdevcombo_changed(GtkComboBox *combobox, gpointer user_data)
{
    UNREFERENCED_PARAMETER(user_data);
    switch (gtk_combo_box_get_active(combobox))
    {
    case 0: settings.shared.usemouse = 0; settings.shared.usejoystick = 0; break;
    case 1:	settings.shared.usemouse = 1; settings.shared.usejoystick = 0; break;
    case 2:	settings.shared.usemouse = 0; settings.shared.usejoystick = 1; break;
    case 3:	settings.shared.usemouse = 1; settings.shared.usejoystick = 1; break;
    }
}

static void on_alwaysshowcheck_toggled(GtkToggleButton *togglebutton, gpointer user_data)
{
    UNREFERENCED_PARAMETER(user_data);
    settings.shared.forcesetup = gtk_toggle_button_get_active(togglebutton);
}

static void on_cancelbutton_clicked(GtkButton *button, gpointer user_data)
{
    UNREFERENCED_PARAMETER(button);
    UNREFERENCED_PARAMETER(user_data);
    if (mode == TAB_CONFIG) { retval = 0; gtk_main_quit(); }
    else quitevent++;
}

static void on_startbutton_clicked(GtkButton *button, gpointer user_data)
{
    UNREFERENCED_PARAMETER(button);
    UNREFERENCED_PARAMETER(user_data);
    retval = 1;
    gtk_main_quit();
}

static void on_gamelist_selection_changed(GtkTreeSelection *selection, gpointer user_data)
{
    GtkTreeIter iter;
    GtkTreeModel *model;
    UNREFERENCED_PARAMETER(user_data);

    if (gtk_tree_selection_get_selected(selection, &model, &iter))
    {
        grpfile_t const *fg;
        gtk_tree_model_get(model, &iter, 2, (gpointer)&fg, -1);
        settings.grp = fg;
    }
}

static gboolean on_startwin_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
    UNREFERENCED_PARAMETER(widget);
    UNREFERENCED_PARAMETER(event);
    UNREFERENCED_PARAMETER(user_data);
    if (mode == TAB_CONFIG) { retval = 0; gtk_main_quit(); }
    else quitevent++;
    return TRUE;	// FALSE would let the event go through. we want the game to decide when to close
}

// -- SUPPORT FUNCTIONS -------------------------------------------------------

static GdkPixbuf *load_banner(void)
{
    return gdk_pixbuf_from_pixdata((GdkPixdata const *)&startbanner_pixdata, FALSE, NULL);
}

static void SetPage(int32_t n)
{
    if (!gtkenabled || !stwidgets.startwin) return;
    mode = n;
    gtk_notebook_set_current_page(GTK_NOTEBOOK(stwidgets.tabs), n);

    // each control in the config page vertical layout plus the start button should be made (in)sensitive
    if (n == TAB_CONFIG) n = TRUE; else n = FALSE;
    gtk_widget_set_sensitive(stwidgets.startbutton, n);
    gtk_container_foreach(GTK_CONTAINER(stwidgets.configtlayout),
                          (GtkCallback)gtk_widget_set_sensitive,
                          (gpointer)&n);
}

static void PopulateForm(unsigned char pgs)
{
    if ((pgs == ALL) || (pgs == POPULATE_VIDEO))
    {
        int32_t mode3d, i;
        GtkListStore *modes3d;
        GtkTreeIter iter;
        char buf[64];

        mode3d = videoCheckMode(&settings.shared.xdim, &settings.shared.ydim, settings.shared.bpp, settings.shared.fullscreen, 1);
        if (mode3d < 0)
        {
            int32_t i, cd[] = { 32, 24, 16, 15, 8, 0 };

            for (i=0; cd[i];) { if (cd[i] >= settings.shared.bpp) i++; else break; }
            for (; cd[i]; i++)
            {
                mode3d = videoCheckMode(&settings.shared.xdim, &settings.shared.ydim, cd[i], settings.shared.fullscreen, 1);
                if (mode3d < 0) continue;
                settings.shared.bpp = cd[i];
                break;
            }
        }

        modes3d = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(stwidgets.vmode3dcombo)));
        gtk_list_store_clear(modes3d);

        for (i=0; i<validmodecnt; i++)
        {
            if (validmode[i].fs != settings.shared.fullscreen) continue;

            // all modes get added to the 3D mode list
            Bsprintf(buf, "%dx%d %s", validmode[i].xdim, validmode[i].ydim, validmode[i].bpp == 8 ? "software" : "OpenGL");
            gtk_list_store_append(modes3d, &iter);
            gtk_list_store_set(modes3d, &iter, 0,buf, 1,i, -1);
            if (i == mode3d)
            {
                g_signal_handlers_block_by_func(stwidgets.vmode3dcombo, (gpointer)on_vmode3dcombo_changed, NULL);
                gtk_combo_box_set_active_iter(GTK_COMBO_BOX(stwidgets.vmode3dcombo), &iter);
                g_signal_handlers_unblock_by_func(stwidgets.vmode3dcombo, (gpointer)on_vmode3dcombo_changed, NULL);
            }
        }
    }

    if ((pgs == ALL) || (pgs == POPULATE_CONFIG))
    {
        GtkTreeIter iter;
        unsigned char i;
        const char *availabledev[] =
        {
            "Keyboard only",
            "Keyboard and mouse",
            "Keyboard and joystick",
            "All supported devices"
        };

        // populate input devices combo
        GtkListStore * devlist = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(stwidgets.inputdevcombo)));
        gtk_list_store_clear(devlist);

        for (i=0; i<(int32_t)G_N_ELEMENTS(availabledev); i++)
        {
            gtk_list_store_append(devlist, &iter);
            gtk_list_store_set(devlist, &iter, 0,availabledev[i], -1);
        }
        switch (settings.shared.usemouse)
        {
        case 0: if (settings.shared.usejoystick)
                gtk_combo_box_set_active(GTK_COMBO_BOX(stwidgets.inputdevcombo), INPUT_JOYSTICK);
            else
                gtk_combo_box_set_active(GTK_COMBO_BOX(stwidgets.inputdevcombo), INPUT_KB);
            break;
        case 1:	if (settings.shared.usejoystick)
                gtk_combo_box_set_active(GTK_COMBO_BOX(stwidgets.inputdevcombo), INPUT_ALL);
            else
                gtk_combo_box_set_active(GTK_COMBO_BOX(stwidgets.inputdevcombo), INPUT_MOUSE);
            break;
        }

        // populate check buttons
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(stwidgets.fullscreencheck), settings.shared.fullscreen);
#ifdef POLYMEROPTION
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(stwidgets.polymercheck), settings.polymer);
#endif
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(stwidgets.alwaysshowcheck), settings.shared.forcesetup);
    }

    if ((pgs == ALL) || (pgs == POPULATE_GAME))
    {
        GtkListStore *list;
        GtkTreeIter iter;
        GtkTreeView *gamelist;

        gamelist = GTK_TREE_VIEW(stwidgets.gamelist);
        list = GTK_LIST_STORE(gtk_tree_view_get_model(gamelist));
        gtk_list_store_clear(list);

        for (grpfile_t const * fg = foundgrps; fg; fg=fg->next)
        {
            gtk_list_store_append(list, &iter);
            gtk_list_store_set(list, &iter, 0, fg->type->name, 1, fg->filename, 2, (void const *)fg, -1);
            if (settings.grp == fg)
            {
                GtkTreeSelection *sel = gtk_tree_view_get_selection(gamelist);
                g_signal_handlers_block_by_func(sel, (gpointer)on_gamelist_selection_changed, NULL);
                gtk_tree_selection_select_iter(sel, &iter);
                g_signal_handlers_unblock_by_func(sel, (gpointer)on_gamelist_selection_changed, NULL);
            }
        }
    }
}

static gint name_sorter(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
{
    gchar *as, *bs;
    gint r;
    UNREFERENCED_PARAMETER(user_data);
    gtk_tree_model_get(model, a, 0, &as, -1);
    gtk_tree_model_get(model, b, 0, &bs, -1);

    r = g_utf8_collate(as,bs);

    g_free(as);
    g_free(bs);

    return r;
}

static GtkWidget *create_window(void)
{
    // Basic window
    stwidgets.startwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(stwidgets.startwin), apptitle);	// NOTE: use global app title
    gtk_window_set_position(GTK_WINDOW(stwidgets.startwin), GTK_WIN_POS_CENTER);
    gtk_window_set_resizable(GTK_WINDOW(stwidgets.startwin), FALSE);
    gtk_window_set_type_hint(GTK_WINDOW(stwidgets.startwin), GDK_WINDOW_TYPE_HINT_DIALOG);

    // Horizontal layout of banner and controls
    stwidgets.hlayout = gtk_hbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(stwidgets.startwin), stwidgets.hlayout);

    // banner
    {
        GdkPixbuf *pixbuf = load_banner();
        stwidgets.banner = gtk_image_new_from_pixbuf(pixbuf);
        g_object_unref((gpointer)pixbuf);
    }
    gtk_box_pack_start(GTK_BOX(stwidgets.hlayout), stwidgets.banner, FALSE, FALSE, 0);
    gtk_misc_set_alignment(GTK_MISC(stwidgets.banner), 0.5, 0);

    // Vertical layout of tab control and start+cancel buttons
    stwidgets.vlayout = gtk_vbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(stwidgets.hlayout), stwidgets.vlayout, TRUE, TRUE, 0);

    // Tab control
    stwidgets.tabs = gtk_notebook_new();
    gtk_box_pack_start(GTK_BOX(stwidgets.vlayout), stwidgets.tabs, TRUE, TRUE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(stwidgets.tabs), 4);

    // layout table of config page
    stwidgets.configtlayout = gtk_table_new(6, 3, FALSE);
    gtk_container_add(GTK_CONTAINER(stwidgets.tabs), stwidgets.configtlayout);

    // 3D video mode LabelText
    stwidgets.vmode3dlabel = gtk_label_new_with_mnemonic("_Video mode:");
    gtk_misc_set_alignment(GTK_MISC(stwidgets.vmode3dlabel), 0.3, 0);
#ifdef POLYMEROPTION
    gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.vmode3dlabel, 0,1, 0,1, GTK_FILL, (GtkAttachOptions)0, 4, 0);
#else
    gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.vmode3dlabel, 0,1, 0,1, GTK_FILL, (GtkAttachOptions)0, 4, 7);
#endif

    // 3D video mode combo
    {
        GtkListStore *list = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
        GtkCellRenderer *cell;

        stwidgets.vmode3dcombo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(list));
        g_object_unref(G_OBJECT(list));

        cell = gtk_cell_renderer_text_new();
        gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(stwidgets.vmode3dcombo), cell, FALSE);
        gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(stwidgets.vmode3dcombo), cell, "text", 0, nullptr);
    }

#ifdef POLYMEROPTION
   gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.vmode3dcombo, 1,2, 0,1,
       (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), (GtkAttachOptions)0, 4, 0);
#else
   gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.vmode3dcombo, 1,2, 0,1, (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), (GtkAttachOptions)0, 4, 7);
#endif

    // Fullscreen checkbox
    stwidgets.displayvlayout = gtk_vbox_new(TRUE, 0);
#ifdef POLYMEROPTION
    gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.displayvlayout, 2,3, 0,1, GTK_FILL, (GtkAttachOptions)0, 4, 0);
#else
    gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.displayvlayout, 2,3, 0,1, GTK_FILL, (GtkAttachOptions)0, 4, 7);
#endif

    stwidgets.fullscreencheck = gtk_check_button_new_with_mnemonic("_Fullscreen");
    gtk_box_pack_start(GTK_BOX(stwidgets.displayvlayout), stwidgets.fullscreencheck, FALSE, FALSE, 0);

#ifdef POLYMEROPTION
    // Polymer checkbox
    stwidgets.polymercheck = gtk_check_button_new_with_mnemonic("_Polymer");
    gtk_box_pack_start(GTK_BOX(stwidgets.displayvlayout), stwidgets.polymercheck, FALSE, FALSE, 0);
#endif

    // Input devices LabelText
    stwidgets.inputdevlabel = gtk_label_new_with_mnemonic("_Input devices:");
    gtk_misc_set_alignment(GTK_MISC(stwidgets.inputdevlabel), 0.3, 0);
    gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.inputdevlabel, 0,1, 1,2, GTK_FILL, (GtkAttachOptions)0, 4, 0);

    // Input devices combo
    {
        GtkListStore *list = gtk_list_store_new(1, G_TYPE_STRING);
        GtkCellRenderer *cell;

        stwidgets.inputdevcombo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(list));
        g_object_unref(G_OBJECT(list));

        cell = gtk_cell_renderer_text_new();
        gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(stwidgets.inputdevcombo), cell, FALSE);
        gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(stwidgets.inputdevcombo), cell, "text", 0, nullptr);
    }
    gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.inputdevcombo, 1,2, 1,2,
        (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), (GtkAttachOptions)0, 4, 0);

    // Empty horizontal layout
    stwidgets.emptyhlayout = gtk_hbox_new(TRUE, 0);
    gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.emptyhlayout, 0,3, 3,4, (GtkAttachOptions)0,
        (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 4, 0);

    // Always show config checkbox
    stwidgets.alwaysshowcheck = gtk_check_button_new_with_mnemonic("_Always show this window at startup");
    gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.alwaysshowcheck, 0,3, 5,6, GTK_FILL, (GtkAttachOptions)0, 2, 2);

    // Configuration tab
    stwidgets.configtab = gtk_label_new("Configuration");
    gtk_notebook_set_tab_label(GTK_NOTEBOOK(stwidgets.tabs), gtk_notebook_get_nth_page(GTK_NOTEBOOK(stwidgets.tabs), 0), stwidgets.configtab);

    // Game data layout
    stwidgets.gamevlayout = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(stwidgets.tabs), stwidgets.gamevlayout);
    gtk_container_set_border_width(GTK_CONTAINER(stwidgets.gamevlayout), 4);

    // Game data field LabelText
    stwidgets.gamelabel = gtk_label_new_with_mnemonic("_Game:");
    gtk_box_pack_start(GTK_BOX(stwidgets.gamevlayout), stwidgets.gamelabel, FALSE, FALSE, 0);
    gtk_misc_set_alignment(GTK_MISC(stwidgets.gamelabel), 0, 0.5);

    // Game data scrollable area
    stwidgets.gamescroll = gtk_scrolled_window_new(NULL, NULL);
    gtk_box_pack_start(GTK_BOX(stwidgets.gamevlayout), stwidgets.gamescroll, TRUE, TRUE, 0);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(stwidgets.gamescroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(stwidgets.gamescroll), GTK_SHADOW_IN);

    // Game data list
    {
        GtkListStore *list = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
        GtkCellRenderer *cell;
        GtkTreeViewColumn *col;

        gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(list), 0, name_sorter, NULL, NULL);
        gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(list), 0, GTK_SORT_ASCENDING);

        stwidgets.gamelist = gtk_tree_view_new_with_model(GTK_TREE_MODEL(list));
        g_object_unref(G_OBJECT(list));

        cell = gtk_cell_renderer_text_new();
        col = gtk_tree_view_column_new_with_attributes("Game", cell, "text", 0, nullptr);
        gtk_tree_view_column_set_expand(col, TRUE);
        gtk_tree_view_append_column(GTK_TREE_VIEW(stwidgets.gamelist), col);
        col = gtk_tree_view_column_new_with_attributes("GRP file", cell, "text", 1, nullptr);
        gtk_tree_view_column_set_min_width(col, 64);
        gtk_tree_view_append_column(GTK_TREE_VIEW(stwidgets.gamelist), col);
    }
    gtk_container_add(GTK_CONTAINER(stwidgets.gamescroll), stwidgets.gamelist);

    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(stwidgets.gamelist), FALSE);
    gtk_tree_view_set_enable_search(GTK_TREE_VIEW(stwidgets.gamelist), FALSE);

    // Game tab
    stwidgets.gametab = gtk_label_new("Game");
    gtk_notebook_set_tab_label(GTK_NOTEBOOK(stwidgets.tabs), gtk_notebook_get_nth_page(GTK_NOTEBOOK(stwidgets.tabs), 1), stwidgets.gametab);

    // Messages scrollable area
    stwidgets.messagesscroll = gtk_scrolled_window_new(NULL, NULL);
    gtk_container_add(GTK_CONTAINER(stwidgets.tabs), stwidgets.messagesscroll);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(stwidgets.messagesscroll), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);

    // Messages text area
    stwidgets.messagestext = gtk_text_view_new();
    gtk_container_add(GTK_CONTAINER(stwidgets.messagesscroll), stwidgets.messagestext);
    gtk_text_view_set_editable(GTK_TEXT_VIEW(stwidgets.messagestext), FALSE);
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(stwidgets.messagestext), GTK_WRAP_WORD);
    gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(stwidgets.messagestext), FALSE);
    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(stwidgets.messagestext), 2);
    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(stwidgets.messagestext), 2);

    // Messages tab
    stwidgets.messagestab = gtk_label_new("Messages");
    gtk_notebook_set_tab_label(GTK_NOTEBOOK(stwidgets.tabs), gtk_notebook_get_nth_page(GTK_NOTEBOOK(stwidgets.tabs), 2), stwidgets.messagestab);

    // Dialogue box buttons layout
    stwidgets.buttons = gtk_hbutton_box_new();
    gtk_box_pack_start(GTK_BOX(stwidgets.vlayout), stwidgets.buttons, FALSE, TRUE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(stwidgets.buttons), 3);
    gtk_button_box_set_layout(GTK_BUTTON_BOX(stwidgets.buttons), GTK_BUTTONBOX_END);

    // Cancel button
    stwidgets.cancelbutton = gtk_button_new();
    gtk_container_add(GTK_CONTAINER(stwidgets.buttons), stwidgets.cancelbutton);
    GTK_WIDGET_SET_FLAGS(stwidgets.cancelbutton, GTK_CAN_DEFAULT);

    stwidgets.cancelbuttonalign = gtk_alignment_new(0.5, 0.5, 0, 0);
    gtk_container_add(GTK_CONTAINER(stwidgets.cancelbutton), stwidgets.cancelbuttonalign);

    stwidgets.cancelbuttonlayout = gtk_hbox_new(FALSE, 2);
    gtk_container_add(GTK_CONTAINER(stwidgets.cancelbuttonalign), stwidgets.cancelbuttonlayout);

    stwidgets.cancelbuttonicon = gtk_image_new_from_stock("gtk-cancel", GTK_ICON_SIZE_BUTTON);
    gtk_box_pack_start(GTK_BOX(stwidgets.cancelbuttonlayout), stwidgets.cancelbuttonicon, FALSE, FALSE, 0);

    stwidgets.cancelbuttonlabel = gtk_label_new_with_mnemonic("_Cancel");
    gtk_box_pack_start(GTK_BOX(stwidgets.cancelbuttonlayout), stwidgets.cancelbuttonlabel, FALSE, FALSE, 0);

    // Start button
    stwidgets.startbutton = gtk_button_new();
    gtk_container_add(GTK_CONTAINER(stwidgets.buttons), stwidgets.startbutton);
    GTK_WIDGET_SET_FLAGS(stwidgets.startbutton, GTK_CAN_DEFAULT);

    gtk_window_set_default(GTK_WINDOW(stwidgets.startwin), stwidgets.startbutton);

    stwidgets.startbuttonalign = gtk_alignment_new(0.5, 0.5, 0, 0);
    gtk_container_add(GTK_CONTAINER(stwidgets.startbutton), stwidgets.startbuttonalign);

    stwidgets.startbuttonlayout = gtk_hbox_new(FALSE, 2);
    gtk_container_add(GTK_CONTAINER(stwidgets.startbuttonalign), stwidgets.startbuttonlayout);

    stwidgets.startbuttonicon = gtk_image_new_from_stock("gtk-execute", GTK_ICON_SIZE_BUTTON);
    gtk_box_pack_start(GTK_BOX(stwidgets.startbuttonlayout), stwidgets.startbuttonicon, FALSE, FALSE, 0);

    stwidgets.startbuttonlabel = gtk_label_new_with_mnemonic("_Start");
    gtk_box_pack_start(GTK_BOX(stwidgets.startbuttonlayout), stwidgets.startbuttonlabel, FALSE, FALSE, 0);

    // Wire up the signals
    g_signal_connect((gpointer) stwidgets.startwin, "delete_event",
                     G_CALLBACK(on_startwin_delete_event),
                     NULL);
    g_signal_connect((gpointer) stwidgets.vmode3dcombo, "changed",
                     G_CALLBACK(on_vmode3dcombo_changed),
                     NULL);
    g_signal_connect((gpointer) stwidgets.fullscreencheck, "toggled",
                     G_CALLBACK(on_fullscreencheck_toggled),
                     NULL);
#ifdef POLYMEROPTION
    g_signal_connect((gpointer) stwidgets.polymercheck, "toggled",
                     G_CALLBACK(on_polymercheck_toggled),
                     NULL);
#endif
    g_signal_connect((gpointer) stwidgets.inputdevcombo, "changed",
                     G_CALLBACK(on_inputdevcombo_changed),
                     NULL);
    g_signal_connect((gpointer) stwidgets.alwaysshowcheck, "toggled",
                     G_CALLBACK(on_alwaysshowcheck_toggled),
                     NULL);
    g_signal_connect((gpointer) stwidgets.cancelbutton, "clicked",
                     G_CALLBACK(on_cancelbutton_clicked),
                     NULL);
    g_signal_connect((gpointer) stwidgets.startbutton, "clicked",
                     G_CALLBACK(on_startbutton_clicked),
                     NULL);
    {
        GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(stwidgets.gamelist));
        gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE);
        g_signal_connect((gpointer) sel, "changed",
                         G_CALLBACK(on_gamelist_selection_changed),
                         NULL);
    }

    // Associate labels with their controls
    gtk_label_set_mnemonic_widget(GTK_LABEL(stwidgets.vmode3dlabel), stwidgets.vmode3dcombo);
    gtk_label_set_mnemonic_widget(GTK_LABEL(stwidgets.inputdevlabel), stwidgets.inputdevcombo);
    gtk_label_set_mnemonic_widget(GTK_LABEL(stwidgets.gamelabel), stwidgets.gamelist);

    return stwidgets.startwin;
}

// -- BUILD ENTRY POINTS ------------------------------------------------------
bool startwin_isopen(void)
{
    return gtkenabled && stwidgets.startwin;
}

int32_t startwin_open(void)
{
    if (!gtkenabled) return 0;
    if (stwidgets.startwin) return 1;

    stwidgets.startwin = create_window();
    if (stwidgets.startwin)
    {
        SetPage(TAB_MESSAGES);
        gtk_widget_show_all(stwidgets.startwin);
        gtk_main_iteration_do(FALSE);
        return 0;
    }
    return -1;
}

int32_t startwin_close(void)
{
    if (!gtkenabled) return 0;
    if (!stwidgets.startwin) return 1;
    gtk_widget_destroy(stwidgets.startwin);
    stwidgets.startwin = NULL;
    return 0;
}

int32_t startwin_puts(const char *str)
{
    GtkWidget *textview;
    GtkTextBuffer *textbuffer;
    GtkTextIter enditer;
    GtkTextMark *mark;
    const char *aptr, *bptr;

    if (!gtkenabled || !str) return 0;
    if (!stwidgets.startwin) return 1;
    if (!(textview = stwidgets.messagestext)) return -1;
    textbuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));

    gtk_text_buffer_get_end_iter(textbuffer, &enditer);
    for (aptr = bptr = str; *aptr != 0;)
    {
        switch (*bptr)
        {
        case '\b':
            if (bptr > aptr)
                gtk_text_buffer_insert(textbuffer, &enditer, (const gchar *)aptr, (gint)(bptr-aptr)-1);
#if GTK_CHECK_VERSION(2,6,0)
            gtk_text_buffer_backspace(textbuffer, &enditer, FALSE, TRUE);
#else
            {
                GtkTextIter iter2 = enditer;
                gtk_text_iter_backward_cursor_position(&iter2);
                //FIXME: this seems be deleting one too many chars somewhere!
                if (!gtk_text_iter_equal(&iter2, &enditer))
                    gtk_text_buffer_delete_interactive(textbuffer, &iter2, &enditer, TRUE);
            }
#endif
            aptr = ++bptr;
            break;
        case 0:
            if (bptr > aptr)
                gtk_text_buffer_insert(textbuffer, &enditer, (const gchar *)aptr, (gint)(bptr-aptr));
            aptr = bptr;
            break;
        case '\r':	// FIXME
        default:
            bptr++;
            break;
        }
    }

    mark = gtk_text_buffer_create_mark(textbuffer, NULL, &enditer, 1);
    gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(textview), mark, 0.0, FALSE, 0.0, 1.0);
    gtk_text_buffer_delete_mark(textbuffer, mark);

    return 0;
}

int32_t startwin_settitle(const char *title)
{
    if (!gtkenabled) return 0;
    if (!stwidgets.startwin) return 1;
    gtk_window_set_title(GTK_WINDOW(stwidgets.startwin), title);
    return 0;
}

int32_t startwin_idle(void *s)
{
    UNREFERENCED_PARAMETER(s);
    if (!gtkenabled) return 0;
    //if (!stwidgets.startwin) return 1;
    gtk_main_iteration_do(FALSE);
    return 0;
}

int32_t startwin_run(void)
{
    if (!gtkenabled) return 1;
    if (!stwidgets.startwin) return 1;

    SetPage(TAB_CONFIG);

    settings.shared = ud_setup;
    settings.grp = g_selectedGrp;
#ifdef POLYMEROPTION
    settings.polymer = (glrendmode == REND_POLYMER) & (settings.shared.bpp != 8);
#endif
    PopulateForm(ALL);

    gtk_main();

    SetPage(TAB_MESSAGES);
    if (retval) // launch the game with these parameters
    {
        ud_setup = settings.shared;
#ifdef POLYMEROPTION
        glrendmode = (settings.polymer) ? REND_POLYMER : REND_POLYMOST;
#endif
        g_selectedGrp = settings.grp;

    }

    return retval;
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef _WIN32
#error Only for Windows
#endif

#include "build.h"

#define NEED_WINDOWSX_H
#define NEED_COMMCTRL_H
#include "windows_inc.h"

#include "renderlayer.h"

#include "common.h"
#include "common_game.h"

#include "gamedefs.h"
#include "config.h"

#include "grpscan.h"

#include "startwin.game.h"

#define TAB_CONFIG 0
#define TAB_MESSAGES 1

static struct
{
    grpfile_t const * grp;
    ud_setup_t shared;
    int polymer;
}
settings;

static HWND startupdlg;
static HWND pages[3];
static int done = -1;
static int mode = TAB_CONFIG;

#define POPULATE_VIDEO 1
#define POPULATE_CONFIG 2
#define POPULATE_GAME 4

#ifdef INPUT_MOUSE
#undef INPUT_MOUSE
#endif

#define INPUT_KB 0
#define INPUT_MOUSE 1
#define INPUT_JOYSTICK 2
#define INPUT_ALL 3

const char *controlstrings[] = { "Keyboard only", "Keyboard and mouse", "Keyboard and joystick", "All supported devices" };

static void PopulateForm(int32_t pgs)
{
    char buf[512];

    if (pgs & POPULATE_VIDEO)
    {
        HWND hwnd = GetDlgItem(pages[TAB_CONFIG], IDCVMODE);
        int mode = videoCheckMode(&settings.shared.xdim, &settings.shared.ydim, settings.shared.bpp, settings.shared.fullscreen, 1);

        if (mode < 0 || (settings.shared.bpp < 15 && (settings.polymer)))
        {
            int CONSTEXPR cd[] = { 32, 24, 16, 15, 8, 0 };
            int i;

            for (i=0; cd[i];)
            {
                if (cd[i] >= settings.shared.bpp) i++;
                else break;
            }
            for (; cd[i]; i++)
            {
                mode = videoCheckMode(&settings.shared.xdim, &settings.shared.ydim, cd[i], settings.shared.fullscreen, 1);
                if (mode < 0) continue;
                settings.shared.bpp = cd[i];
                break;
            }
        }

        Button_SetCheck(GetDlgItem(pages[TAB_CONFIG], IDCFULLSCREEN), ((settings.shared.fullscreen) ? BST_CHECKED : BST_UNCHECKED));
        Button_SetCheck(GetDlgItem(pages[TAB_CONFIG], IDCPOLYMER), ((settings.polymer) ? BST_CHECKED : BST_UNCHECKED));

        (void)ComboBox_ResetContent(hwnd);

        for (int i=0; i<validmodecnt; i++)
        {
            if (validmode[i].fs != (settings.shared.fullscreen)) continue;
            if ((validmode[i].bpp < 15) && (settings.polymer)) continue;

            // all modes get added to the 3D mode list
            Bsprintf(buf, "%dx%d %s", validmode[i].xdim, validmode[i].ydim, validmode[i].bpp == 8 ? "software" : "OpenGL");
            int const j = ComboBox_AddString(hwnd, buf);
            (void)ComboBox_SetItemData(hwnd, j, i);
            if (i == mode)(void)ComboBox_SetCurSel(hwnd, j);
        }
    }

    if (pgs & POPULATE_CONFIG)
    {
        Button_SetCheck(GetDlgItem(pages[TAB_CONFIG], IDCALWAYSSHOW), (settings.shared.forcesetup ? BST_CHECKED : BST_UNCHECKED));

        HWND hwnd = GetDlgItem(pages[TAB_CONFIG], IDCINPUT);

        (void)ComboBox_ResetContent(hwnd);
        (void)ComboBox_SetCurSel(hwnd, 0);

        int j = 4;

#ifdef RENDERTYPEWIN
        if (di_disabled) j = 2;
#endif

        for (int i=0; i<j; i++)
        {
            (void)ComboBox_InsertString(hwnd, i, controlstrings[i]);
            (void)ComboBox_SetItemData(hwnd, i, i);

            switch (i)
            {
            case INPUT_MOUSE:
                if (settings.shared.usemouse && !settings.shared.usejoystick)(void)ComboBox_SetCurSel(hwnd, i);
                break;
            case INPUT_JOYSTICK:
                if (!settings.shared.usemouse && settings.shared.usejoystick)(void)ComboBox_SetCurSel(hwnd, i);
                break;
            case INPUT_ALL:
                if (settings.shared.usemouse && settings.shared.usejoystick)(void)ComboBox_SetCurSel(hwnd, i);
                break;
            }
        }
    }

    if (pgs & POPULATE_GAME)
    {
        HWND hwnd = GetDlgItem(pages[TAB_CONFIG], IDCDATA);

        for (auto fg = foundgrps; fg; fg=fg->next)
        {
            Bsprintf(buf, "%s\t%s", fg->type->name, fg->filename);
            int const j = ListBox_AddString(hwnd, buf);
            (void)ListBox_SetItemData(hwnd, j, (LPARAM)fg);
            if (settings.grp == fg)
                (void)ListBox_SetCurSel(hwnd, j);
        }
    }
}

static INT_PTR CALLBACK ConfigPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDCFULLSCREEN:
            settings.shared.fullscreen = !settings.shared.fullscreen;
            PopulateForm(POPULATE_VIDEO);
            return TRUE;
        case IDCPOLYMER:
            settings.polymer = !settings.polymer;
            if (settings.shared.bpp == 8) settings.shared.bpp = 32;
            PopulateForm(POPULATE_VIDEO);
            return TRUE;
        case IDCVMODE:
            if (HIWORD(wParam) == CBN_SELCHANGE)
            {
                int i = ComboBox_GetCurSel((HWND)lParam);
                if (i != CB_ERR) i = ComboBox_GetItemData((HWND)lParam, i);
                if (i != CB_ERR)
                {
                    settings.shared.xdim = validmode[i].xdim;
                    settings.shared.ydim = validmode[i].ydim;
                    settings.shared.bpp  = validmode[i].bpp;
                }
            }
            return TRUE;
        case IDCALWAYSSHOW:
            settings.shared.forcesetup = IsDlgButtonChecked(hwndDlg, IDCALWAYSSHOW) == BST_CHECKED;
            return TRUE;
            return TRUE;
        case IDCINPUT:
            if (HIWORD(wParam) == CBN_SELCHANGE)
            {
                int i = ComboBox_GetCurSel((HWND)lParam);
                if (i != CB_ERR) i = ComboBox_GetItemData((HWND)lParam, i);
                if (i != CB_ERR)
                {
                    switch (i)
                    {
                    case INPUT_KB:
                        settings.shared.usemouse = settings.shared.usejoystick = 0;
                        break;
                    case INPUT_MOUSE:
                        settings.shared.usemouse = 1;
                        settings.shared.usejoystick = 0;
                        break;
                    case INPUT_JOYSTICK:
                        settings.shared.usemouse = 0;
                        settings.shared.usejoystick = 1;
                        break;
                    case INPUT_ALL:
                        settings.shared.usemouse = settings.shared.usejoystick = 1;
                        break;
                    }
                }
            }
            return TRUE;

        case IDCDATA:
        {
            if (HIWORD(wParam) != LBN_SELCHANGE) break;
            intptr_t i = ListBox_GetCurSel((HWND)lParam);
            if (i != CB_ERR) i = ListBox_GetItemData((HWND)lParam, i);
            if (i != CB_ERR)
            {
                settings.grp = (grpfile_t const *)i;
            }
            return TRUE;
        }
        default:
            break;
        }
        break;
    default:
        break;
    }
    return FALSE;
}

static void SetPage(int pageNum)
{
    HWND tab = GetDlgItem(startupdlg, WIN_STARTWIN_TABCTL);
    auto const cur = SendMessage(tab, TCM_GETCURSEL, 0, 0);
    ShowWindow(pages[cur], SW_HIDE);
    SendMessage(tab, TCM_SETCURSEL, pageNum, 0);
    ShowWindow(pages[pageNum], SW_SHOW);
    mode = pageNum;

    SetFocus(GetDlgItem(startupdlg, WIN_STARTWIN_TABCTL));
}

static void EnableConfig(bool n)
{
    //EnableWindow(GetDlgItem(startupdlg, WIN_STARTWIN_CANCEL), n);
    EnableWindow(GetDlgItem(startupdlg, WIN_STARTWIN_START), n);
    EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDCDATA), n);
    EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDCFULLSCREEN), n);
    EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDCINPUT), n);
    EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDCPOLYMER), n);
    EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDCVMODE), n);
}

static INT_PTR CALLBACK startup_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HBITMAP hbmp = NULL;

    switch (uMsg)
    {
    case WM_INITDIALOG:
    {
        // Fetch the positions (in screen coordinates) of all the windows we need to tweak
        RECT chrome = {};
        AdjustWindowRect(&chrome, GetWindowLongPtr(hwndDlg, GWL_STYLE), FALSE);
        RECT rdlg;
        GetWindowRect(hwndDlg, &rdlg);

        // Knock off the non-client area of the main dialogue to give just the client area
        rdlg.left -= chrome.left;
        rdlg.top -= chrome.top;
        rdlg.right -= chrome.right;
        rdlg.bottom -= chrome.bottom;

        RECT rtab;
        GetWindowRect(GetDlgItem(hwndDlg, WIN_STARTWIN_TABCTL), &rtab);

        // Translate them to client-relative coordinates wrt the main dialogue window
        rtab.right -= rtab.left - 1;
        rtab.bottom -= rtab.top - 1;
        rtab.left  -= rdlg.left;
        rtab.top -= rdlg.top;

        RECT rcancel;
        GetWindowRect(GetDlgItem(hwndDlg, WIN_STARTWIN_CANCEL), &rcancel);

        rcancel.right -= rcancel.left - 1;
        rcancel.bottom -= rcancel.top - 1;
        rcancel.left -= rdlg.left;
        rcancel.top -= rdlg.top;

        RECT rstart;
        GetWindowRect(GetDlgItem(hwndDlg, WIN_STARTWIN_START), &rstart);

        rstart.right -= rstart.left - 1;
        rstart.bottom -= rstart.top - 1;
        rstart.left -= rdlg.left;
        rstart.top -= rdlg.top;

        // And then convert the main dialogue coordinates to just width/length
        rdlg.right -= rdlg.left - 1;
        rdlg.bottom -= rdlg.top - 1;
        rdlg.left = 0;
        rdlg.top = 0;

        // Load the bitmap into the bitmap control and fetch its dimensions
        hbmp = LoadBitmap((HINSTANCE)win_gethinstance(), MAKEINTRESOURCE(RSRC_BMP));

        HWND hwnd = GetDlgItem(hwndDlg, WIN_STARTWIN_BITMAP);

        RECT r;
        GetClientRect(hwnd, &r);

        int const xoffset = r.right;
        int const yoffset = r.bottom - rdlg.bottom;

        // Shift and resize the controls that require it
        rtab.left += xoffset;
        rtab.bottom += yoffset;
        rcancel.left += xoffset;
        rcancel.top += yoffset;
        rstart.left += xoffset;
        rstart.top += yoffset;
        rdlg.right += xoffset;
        rdlg.bottom += yoffset;

        // Move the controls to their new positions
        MoveWindow(GetDlgItem(hwndDlg, WIN_STARTWIN_TABCTL), rtab.left, rtab.top, rtab.right, rtab.bottom, FALSE);
        MoveWindow(GetDlgItem(hwndDlg, WIN_STARTWIN_CANCEL), rcancel.left, rcancel.top, rcancel.right, rcancel.bottom, FALSE);
        MoveWindow(GetDlgItem(hwndDlg, WIN_STARTWIN_START), rstart.left, rstart.top, rstart.right, rstart.bottom, FALSE);

        // Move the main dialogue to the centre of the screen
        HDC hdc = GetDC(NULL);
        rdlg.left = (GetDeviceCaps(hdc, HORZRES) - rdlg.right) / 2;
        rdlg.top = (GetDeviceCaps(hdc, VERTRES) - rdlg.bottom) / 2;
        ReleaseDC(NULL, hdc);
        MoveWindow(hwndDlg, rdlg.left + chrome.left, rdlg.top + chrome.left,
                   rdlg.right + (-chrome.left+chrome.right), rdlg.bottom + (-chrome.top+chrome.bottom), TRUE);

        // Add tabs to the tab control
        {
            static char textSetup[] = TEXT("Setup");
            static char textMessageLog[] = TEXT("Message Log");

            hwnd = GetDlgItem(hwndDlg, WIN_STARTWIN_TABCTL);

            TCITEM tab = {};
            tab.mask = TCIF_TEXT;
            tab.pszText = textSetup;
            SendMessage(hwnd, TCM_INSERTITEM, (WPARAM)TAB_CONFIG, (LPARAM)&tab);
            tab.mask = TCIF_TEXT;
            tab.pszText = textMessageLog;
            SendMessage(hwnd, TCM_INSERTITEM, (WPARAM)TAB_MESSAGES, (LPARAM)&tab);

            // Work out the position and size of the area inside the tab control for the pages
            ZeroMemory(&r, sizeof(r));
            GetClientRect(hwnd, &r);
            SendMessage(hwnd, TCM_ADJUSTRECT, FALSE, (LPARAM)&r);
            r.right -= r.left-1;
            r.bottom -= r.top-1;
            r.top += rtab.top;
            r.left += rtab.left;

            // Create the pages and position them in the tab control, but hide them
            pages[TAB_CONFIG] = CreateDialog((HINSTANCE)win_gethinstance(), MAKEINTRESOURCE(WIN_STARTWINPAGE_CONFIG), hwndDlg, ConfigPageProc);
            SetWindowPos(pages[TAB_CONFIG], hwnd, r.left, r.top, r.right, r.bottom, SWP_HIDEWINDOW);

            pages[TAB_MESSAGES] = GetDlgItem(hwndDlg, WIN_STARTWIN_MESSAGES);
            SetWindowPos(pages[TAB_MESSAGES], hwnd, r.left, r.top, r.right, r.bottom, SWP_HIDEWINDOW);

            // Tell the editfield acting as the console to exclude the width of the scrollbar
            GetClientRect(pages[TAB_MESSAGES], &r);
            r.right -= GetSystemMetrics(SM_CXVSCROLL)+4;
            r.left = r.top = 0;
            SendMessage(pages[TAB_MESSAGES], EM_SETRECTNP,0,(LPARAM)&r);

            // Set a tab stop in the game data listbox
            {
                DWORD tabs[1] = { 150 };
                (void)ListBox_SetTabStops(GetDlgItem(pages[TAB_CONFIG], IDCDATA), 1, tabs);
            }

            SetFocus(GetDlgItem(hwndDlg, WIN_STARTWIN_START));
            SetWindowText(hwndDlg, apptitle);
        }
        return FALSE;
    }

    case WM_NOTIFY:
    {
        auto nmhdr = (LPNMHDR)lParam;
        if (nmhdr->idFrom != WIN_STARTWIN_TABCTL) break;
        int const cur = SendMessage(nmhdr->hwndFrom, TCM_GETCURSEL,0,0);
        switch (nmhdr->code)
        {
            case TCN_SELCHANGING:
            case TCN_SELCHANGE:
                if (cur < 0 || !pages[cur])
                    break;
                ShowWindow(pages[cur], nmhdr->code == TCN_SELCHANGING ? SW_HIDE : SW_SHOW);
                return TRUE;
        }
        break;
    }

    case WM_CLOSE:
        if (mode == TAB_CONFIG) done = 0;
        else quitevent++;
        return TRUE;

    case WM_DESTROY:
        if (hbmp)
        {
            DeleteObject(hbmp);
            hbmp = NULL;
        }

        if (pages[TAB_CONFIG])
        {
            DestroyWindow(pages[TAB_CONFIG]);
            pages[TAB_CONFIG] = NULL;
        }

        startupdlg = NULL;
        return TRUE;

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case WIN_STARTWIN_CANCEL:
            if (mode == TAB_CONFIG) done = 0;
            else quitevent++;
            return TRUE;
        case WIN_STARTWIN_START:
            done = 1;
            return TRUE;
        }
        return FALSE;

    case WM_CTLCOLORSTATIC:
        if ((HWND)lParam == pages[TAB_MESSAGES])
            return (BOOL)(intptr_t)GetSysColorBrush(COLOR_WINDOW);
        break;

    case WM_PAINT:
    {
        // manually paint the banner with antialiasing
        HWND hwnd = GetDlgItem(hwndDlg, WIN_STARTWIN_BITMAP);

        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        if (ps.rcPaint.right > ps.rcPaint.left && ps.rcPaint.bottom > ps.rcPaint.top)
        {
            RECT r;
            GetClientRect(hwnd, &r);

            BITMAP bm;
            GetObject(hbmp, sizeof(BITMAP), &bm);

            HDC hdcbmp = CreateCompatibleDC(hdc);
            SelectObject(hdcbmp, hbmp);

            SetStretchBltMode(hdc, HALFTONE);
            StretchBlt(hdc, r.left,r.top,r.right,r.bottom, hdcbmp, 0,0,bm.bmWidth,bm.bmHeight, SRCCOPY);

            DeleteDC(hdcbmp);
        }

        EndPaint(hwnd, &ps);
        return 0;
    }

    default:
        break;
    }

    return FALSE;
}

bool startwin_isopen(void)
{
    return !!startupdlg;
}

int32_t startwin_open(void)
{
    if (startupdlg) return 1;
    INITCOMMONCONTROLSEX icc = { sizeof(icc), ICC_TAB_CLASSES };
    InitCommonControlsEx(&icc);
    startupdlg = CreateDialog((HINSTANCE)win_gethinstance(), MAKEINTRESOURCE(WIN_STARTWIN), NULL, startup_dlgproc);
    if (startupdlg)
    {
        SetPage(TAB_MESSAGES);
        EnableConfig(0);
        return 0;
    }
    return -1;
}

int32_t startwin_close(void)
{
    if (!startupdlg) return 1;
    DestroyWindow(startupdlg);
    startupdlg = NULL;
    return 0;
}

int32_t startwin_puts(const char *buf)
{
    if (!startupdlg) return 1;

    const HWND edctl = pages[TAB_MESSAGES];

    if (!edctl) return -1;

    static HWND dactrl = NULL;
    if (!dactrl) dactrl = GetDlgItem(startupdlg, WIN_STARTWIN_TABCTL);

    int const vis = ((int)SendMessage(dactrl, TCM_GETCURSEL,0,0) == TAB_MESSAGES);

    if (vis)
        SendMessage(edctl, WM_SETREDRAW, FALSE, 0);

    int const curlen = SendMessage(edctl, WM_GETTEXTLENGTH, 0,0);
    SendMessage(edctl, EM_SETSEL, (WPARAM)curlen, (LPARAM)curlen);

    int const   numlines = SendMessage(edctl, EM_GETLINECOUNT, 0, 0);
    static bool newline  = false;
    const char *p        = buf;

    while (*p)
    {
        if (newline)
        {
            SendMessage(edctl, EM_REPLACESEL, 0, (LPARAM)"\r\n");
            newline = false;
        }
        const char *q = p;
        while (*q && *q != '\n') q++;
        static char workbuf[1024];
        Bmemcpy(workbuf, p, q-p);
        if (*q == '\n')
        {
            if (!q[1])
            {
                newline = true;
                workbuf[q-p] = 0;
            }
            else
            {
                workbuf[q-p] = '\r';
                workbuf[q-p+1] = '\n';
                workbuf[q-p+2] = 0;
            }
            p = q+1;
        }
        else
        {
            workbuf[q-p] = 0;
            p = q;
        }
        SendMessage(edctl, EM_REPLACESEL, 0, (LPARAM)workbuf);
    }

    int const newnumlines = SendMessage(edctl, EM_GETLINECOUNT, 0, 0);
    SendMessage(edctl, EM_LINESCROLL, 0, newnumlines - numlines);

    if (vis)
        SendMessage(edctl, WM_SETREDRAW, TRUE, 0);

    return 0;
}

int32_t startwin_settitle(const char *str)
{
    if (!startupdlg) return 1;
    SetWindowText(startupdlg, str);
    return 0;
}

int32_t startwin_idle(void *v)
{
    if (!startupdlg || !IsWindow(startupdlg)) return 0;
    if (IsDialogMessage(startupdlg, (MSG *)v)) return 1;
    return 0;
}

int32_t startwin_run(void)
{
    if (!startupdlg) return 1;

    done = -1;

    SetPage(TAB_CONFIG);
    EnableConfig(1);

#ifdef POLYMER
    settings.polymer = (glrendmode == REND_POLYMER);
#else
    settings.polymer = 0;
#endif

    settings.shared = ud_setup;
    settings.grp = g_selectedGrp;

    PopulateForm(-1);

    do
    {
        MSG msg;

        switch (GetMessage(&msg, NULL, 0,0))
        {
        case 0:
            done = 1;
            break;
        case -1:
            return -1;
        default:
            if (IsWindow(startupdlg) && IsDialogMessage(startupdlg, &msg))
                break;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            break;
        }
    }
    while (done < 0);

    SetPage(TAB_MESSAGES);
    EnableConfig(0);

    if (done)
    {
        ud_setup = settings.shared;
#ifdef USE_OPENGL
        glrendmode = (settings.polymer) ? REND_POLYMER : REND_POLYMOST;
#endif
        g_selectedGrp = settings.grp;
    }

    return done;
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

// resource ids
#define WIN_STARTWIN		1000
#define WIN_STARTWINPAGE_CONFIG 2000
#define WIN_STARTWIN_BITMAP	100	// banner bitmap
#define WIN_STARTWIN_TABCTL	101
#define WIN_STARTWIN_CANCEL	IDCANCEL
#define WIN_STARTWIN_START	IDOK

#define WIN_STARTWIN_MESSAGES	104	// output list box

#define RSRC_ICON		100
#define RSRC_BMP		200

// config page
#define IDCFULLSCREEN	100
#define IDCVMODE	    101
#define IDCALWAYSSHOW	105
#define IDCDATA         106
#define IDCPOLYMER      108
#define IDCINPUT	    110


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior 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 2
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "build.h"

#include "keys.h"
#include "names2.h"
#include "panel.h"
#include "game.h"
#include "tags.h"
#include "ai.h"
#include "quake.h"
#include "actor.h"
#include "track.h"
#include "weapon.h"
#include "sector.h"

extern uint8_t RedBookSong[40];
extern uint8_t playTrack;
SWBOOL serpwasseen = FALSE;
SWBOOL sumowasseen = FALSE;
SWBOOL zillawasseen = FALSE;

short BossSpriteNum[3] = {-1,-1,-1};

ANIMATOR InitSumoCharge;

DECISION SumoBattle[] =
{
    {690,   InitActorMoveCloser   },
    {692,   InitActorAlertNoise   },
    {1024,  InitActorAttack       }
};

DECISION SumoOffense[] =
{
    {690,   InitActorMoveCloser   },
    {692,   InitActorAlertNoise   },
    {1024,  InitActorAttack       }
};

DECISION SumoBroadcast[] =
{
    {2,     InitActorAlertNoise   },
    {4,     InitActorAmbientNoise  },
    {1024,  InitActorDecide       }
};

DECISION SumoSurprised[] =
{
    {700,   InitActorMoveCloser   },
    {703,   InitActorAlertNoise   },
    {1024,  InitActorDecide       }
};

DECISION SumoEvasive[] =
{
    {1024, InitActorAttack }
};

DECISION SumoLostTarget[] =
{
    {900,   InitActorFindPlayer         },
    {1024,  InitActorWanderAround       }
};

DECISION SumoCloseRange[] =
{
    {1024,  InitActorAttack         }
};

PERSONALITY SumoPersonality =
{
    SumoBattle,
    SumoOffense,
    SumoBroadcast,
    SumoSurprised,
    SumoEvasive,
    SumoLostTarget,
    SumoCloseRange,
    SumoCloseRange
};

ATTRIBUTE SumoAttrib =
{
    {160, 180, 180, 180},             // Speeds
    {3, 0, 0, 0},                     // Tic Adjusts
    3,                               // MaxWeapons;
    {
        DIGI_SUMOAMBIENT, DIGI_SUMOALERT, DIGI_SUMOSCREAM,
        DIGI_SUMOPAIN, DIGI_SUMOSCREAM, 0,0,0,0,0
    }
};

//////////////////////
//
// SUMO RUN
//
//////////////////////

#define SUMO_RATE 24

ANIMATOR DoSumoMove,NullSumo,DoStayOnFloor,
         DoActorDebris, SpawnSumoExp,
         SpawnCoolg;

STATE s_SumoRun[5][4] =
{
    {
        {SUMO_RUN_R0 + 0, SUMO_RATE, DoSumoMove, &s_SumoRun[0][1]},
        {SUMO_RUN_R0 + 1, SUMO_RATE, DoSumoMove, &s_SumoRun[0][2]},
        {SUMO_RUN_R0 + 2, SUMO_RATE, DoSumoMove, &s_SumoRun[0][3]},
        {SUMO_RUN_R0 + 3, SUMO_RATE, DoSumoMove, &s_SumoRun[0][0]}
    },
    {
        {SUMO_RUN_R1 + 0, SUMO_RATE, DoSumoMove, &s_SumoRun[1][1]},
        {SUMO_RUN_R1 + 1, SUMO_RATE, DoSumoMove, &s_SumoRun[1][2]},
        {SUMO_RUN_R1 + 2, SUMO_RATE, DoSumoMove, &s_SumoRun[1][3]},
        {SUMO_RUN_R1 + 3, SUMO_RATE, DoSumoMove, &s_SumoRun[1][0]}
    },
    {
        {SUMO_RUN_R2 + 0, SUMO_RATE, DoSumoMove, &s_SumoRun[2][1]},
        {SUMO_RUN_R2 + 1, SUMO_RATE, DoSumoMove, &s_SumoRun[2][2]},
        {SUMO_RUN_R2 + 2, SUMO_RATE, DoSumoMove, &s_SumoRun[2][3]},
        {SUMO_RUN_R2 + 3, SUMO_RATE, DoSumoMove, &s_SumoRun[2][0]}
    },
    {
        {SUMO_RUN_R3 + 0, SUMO_RATE, DoSumoMove, &s_SumoRun[3][1]},
        {SUMO_RUN_R3 + 1, SUMO_RATE, DoSumoMove, &s_SumoRun[3][2]},
        {SUMO_RUN_R3 + 2, SUMO_RATE, DoSumoMove, &s_SumoRun[3][3]},
        {SUMO_RUN_R3 + 3, SUMO_RATE, DoSumoMove, &s_SumoRun[3][0]}
    },
    {
        {SUMO_RUN_R4 + 0, SUMO_RATE, DoSumoMove, &s_SumoRun[4][1]},
        {SUMO_RUN_R4 + 1, SUMO_RATE, DoSumoMove, &s_SumoRun[4][2]},
        {SUMO_RUN_R4 + 2, SUMO_RATE, DoSumoMove, &s_SumoRun[4][3]},
        {SUMO_RUN_R4 + 3, SUMO_RATE, DoSumoMove, &s_SumoRun[4][0]},
    }
};

STATEp sg_SumoRun[] =
{
    &s_SumoRun[0][0],
    &s_SumoRun[1][0],
    &s_SumoRun[2][0],
    &s_SumoRun[3][0],
    &s_SumoRun[4][0]
};

//////////////////////
//
// SUMO CHARGE
//
//////////////////////
#if 0

#define SUMO_RATE 12

ANIMATOR DoSumoMove,NullSumo,DoStayOnFloor,
         DoActorDebris, DoSumoRumble;

STATE s_SumoCharge[5][4] =
{
    {
        {SUMO_RUN_R0 + 0, SUMO_RATE, DoSumoMove, &s_SumoCharge[0][1]},
        {SUMO_RUN_R0 + 1, SUMO_RATE, DoSumoMove, &s_SumoCharge[0][2]},
        {SUMO_RUN_R0 + 2, SUMO_RATE, DoSumoMove, &s_SumoCharge[0][3]},
        {SUMO_RUN_R0 + 3, SUMO_RATE, DoSumoMove, &s_SumoCharge[0][0]},
    },
    {
        {SUMO_RUN_R1 + 0, SUMO_RATE, DoSumoMove, &s_SumoCharge[1][1]},
        {SUMO_RUN_R1 + 1, SUMO_RATE, DoSumoMove, &s_SumoCharge[1][2]},
        {SUMO_RUN_R1 + 2, SUMO_RATE, DoSumoMove, &s_SumoCharge[1][3]},
        {SUMO_RUN_R1 + 3, SUMO_RATE, DoSumoMove, &s_SumoCharge[1][0]},
    },
    {
        {SUMO_RUN_R2 + 0, SUMO_RATE, DoSumoMove, &s_SumoCharge[2][1]},
        {SUMO_RUN_R2 + 1, SUMO_RATE, DoSumoMove, &s_SumoCharge[2][2]},
        {SUMO_RUN_R2 + 2, SUMO_RATE, DoSumoMove, &s_SumoCharge[2][3]},
        {SUMO_RUN_R2 + 3, SUMO_RATE, DoSumoMove, &s_SumoCharge[2][0]},
    },
    {
        {SUMO_RUN_R3 + 0, SUMO_RATE, DoSumoMove, &s_SumoCharge[3][1]},
        {SUMO_RUN_R3 + 1, SUMO_RATE, DoSumoMove, &s_SumoCharge[3][2]},
        {SUMO_RUN_R3 + 2, SUMO_RATE, DoSumoMove, &s_SumoCharge[3][3]},
        {SUMO_RUN_R3 + 3, SUMO_RATE, DoSumoMove, &s_SumoCharge[3][0]},
    },
    {
        {SUMO_RUN_R4 + 0, SUMO_RATE, DoSumoMove, &s_SumoCharge[4][1]},
        {SUMO_RUN_R4 + 1, SUMO_RATE, DoSumoMove, &s_SumoCharge[4][2]},
        {SUMO_RUN_R4 + 2, SUMO_RATE, DoSumoMove, &s_SumoCharge[4][3]},
        {SUMO_RUN_R4 + 3, SUMO_RATE, DoSumoMove, &s_SumoCharge[4][0]},
    }
};

STATEp sg_SumoCharge[] =
{
    &s_SumoCharge[0][0],
    &s_SumoCharge[1][0],
    &s_SumoCharge[2][0],
    &s_SumoCharge[3][0],
    &s_SumoCharge[4][0]
};
#endif

//////////////////////
//
// SUMO STAND
//
//////////////////////

STATE s_SumoStand[5][1] =
{
    {
        {SUMO_RUN_R0 + 0, SUMO_RATE, DoSumoMove, &s_SumoStand[0][0]}
    },
    {
        {SUMO_RUN_R1 + 0, SUMO_RATE, DoSumoMove, &s_SumoStand[1][0]}
    },
    {
        {SUMO_RUN_R2 + 0, SUMO_RATE, DoSumoMove, &s_SumoStand[2][0]}
    },
    {
        {SUMO_RUN_R3 + 0, SUMO_RATE, DoSumoMove, &s_SumoStand[3][0]}
    },
    {
        {SUMO_RUN_R4 + 0, SUMO_RATE, DoSumoMove, &s_SumoStand[4][0]}
    }
};

STATEp sg_SumoStand[] =
{
    &s_SumoStand[0][0],
    &s_SumoStand[1][0],
    &s_SumoStand[2][0],
    &s_SumoStand[3][0],
    &s_SumoStand[4][0]
};

//////////////////////
//
// SUMO PAIN
//
//////////////////////

#define SUMO_PAIN_RATE 30

STATE s_SumoPain[5][2] =
{
    {
        {SUMO_PAIN_R0 + 0, SUMO_PAIN_RATE, NullSumo, &s_SumoPain[0][1]},
        {SUMO_PAIN_R0 + 0, 0|SF_QUICK_CALL, InitActorDecide, &s_SumoPain[0][0]}
    },
    {
        {SUMO_PAIN_R1 + 0, SUMO_PAIN_RATE, NullSumo, &s_SumoPain[1][1]},
        {SUMO_PAIN_R1 + 0, 0|SF_QUICK_CALL, InitActorDecide, &s_SumoPain[1][0]}
    },
    {
        {SUMO_PAIN_R2 + 0, SUMO_PAIN_RATE, NullSumo, &s_SumoPain[2][1]},
        {SUMO_PAIN_R2 + 0, 0|SF_QUICK_CALL, InitActorDecide, &s_SumoPain[2][0]}
    },
    {
        {SUMO_PAIN_R3 + 0, SUMO_PAIN_RATE, NullSumo, &s_SumoPain[3][1]},
        {SUMO_PAIN_R3 + 0, 0|SF_QUICK_CALL, InitActorDecide, &s_SumoPain[3][0]}
    },
    {
        {SUMO_PAIN_R4 + 0, SUMO_PAIN_RATE, NullSumo, &s_SumoPain[4][1]},
        {SUMO_PAIN_R4 + 0, 0|SF_QUICK_CALL, InitActorDecide, &s_SumoPain[4][0]}
    }
};

STATEp sg_SumoPain[] =
{
    &s_SumoPain[0][0],
    &s_SumoPain[1][0],
    &s_SumoPain[2][0],
    &s_SumoPain[3][0],
    &s_SumoPain[4][0]
};

//////////////////////
//
// SUMO FART
//
//////////////////////

#define SUMO_FART_RATE 12
ANIMATOR InitSumoFart;

STATE s_SumoFart[5][6] =
{
    {
        {SUMO_FART_R0 + 0, SUMO_FART_RATE, NullSumo, &s_SumoFart[0][1]},
        {SUMO_FART_R0 + 0, SF_QUICK_CALL, InitSumoFart, &s_SumoFart[0][2]},
        {SUMO_FART_R0 + 1, SUMO_FART_RATE, NullSumo, &s_SumoFart[0][3]},
        {SUMO_FART_R0 + 2, SUMO_FART_RATE, NullSumo, &s_SumoFart[0][4]},
        {SUMO_FART_R0 + 3, SUMO_FART_RATE*10, NullSumo, &s_SumoFart[0][5]},
        {SUMO_FART_R0 + 3, SF_QUICK_CALL, InitActorDecide, &s_SumoFart[0][0]}
    },
    {
        {SUMO_FART_R1 + 0, SUMO_FART_RATE, NullSumo, &s_SumoFart[1][1]},
        {SUMO_FART_R1 + 0, SF_QUICK_CALL, InitSumoFart, &s_SumoFart[1][2]},
        {SUMO_FART_R1 + 1, SUMO_FART_RATE, NullSumo, &s_SumoFart[1][3]},
        {SUMO_FART_R1 + 2, SUMO_FART_RATE, NullSumo, &s_SumoFart[1][4]},
        {SUMO_FART_R1 + 3, SUMO_FART_RATE*10, NullSumo, &s_SumoFart[1][5]},
        {SUMO_FART_R1 + 0, SF_QUICK_CALL, InitActorDecide, &s_SumoFart[1][0]}
    },
    {
        {SUMO_FART_R2 + 0, SUMO_FART_RATE, NullSumo, &s_SumoFart[2][1]},
        {SUMO_FART_R2 + 0, SF_QUICK_CALL, InitSumoFart, &s_SumoFart[2][2]},
        {SUMO_FART_R2 + 1, SUMO_FART_RATE, NullSumo, &s_SumoFart[2][3]},
        {SUMO_FART_R2 + 2, SUMO_FART_RATE, NullSumo, &s_SumoFart[2][4]},
        {SUMO_FART_R2 + 3, SUMO_FART_RATE*10, NullSumo, &s_SumoFart[2][5]},
        {SUMO_FART_R2 + 0, SF_QUICK_CALL, InitActorDecide, &s_SumoFart[2][0]}
    },
    {
        {SUMO_FART_R3 + 0, SUMO_FART_RATE, NullSumo, &s_SumoFart[3][1]},
        {SUMO_FART_R3 + 0, SF_QUICK_CALL, InitSumoFart, &s_SumoFart[3][2]},
        {SUMO_FART_R3 + 1, SUMO_FART_RATE, NullSumo, &s_SumoFart[3][3]},
        {SUMO_FART_R3 + 2, SUMO_FART_RATE, NullSumo, &s_SumoFart[3][4]},
        {SUMO_FART_R3 + 3, SUMO_FART_RATE*10, NullSumo, &s_SumoFart[3][5]},
        {SUMO_FART_R3 + 0, SF_QUICK_CALL, InitActorDecide, &s_SumoFart[3][0]}
    },
    {
        {SUMO_FART_R4 + 0, SUMO_FART_RATE, NullSumo, &s_SumoFart[4][1]},
        {SUMO_FART_R4 + 0, SF_QUICK_CALL, InitSumoFart, &s_SumoFart[4][2]},
        {SUMO_FART_R4 + 1, SUMO_FART_RATE, NullSumo, &s_SumoFart[4][3]},
        {SUMO_FART_R4 + 2, SUMO_FART_RATE, NullSumo, &s_SumoFart[4][4]},
        {SUMO_FART_R4 + 3, SUMO_FART_RATE*10, NullSumo, &s_SumoFart[4][5]},
        {SUMO_FART_R4 + 0, SF_QUICK_CALL, InitActorDecide, &s_SumoFart[4][0]}
    }
};

STATEp sg_SumoFart[] =
{
    &s_SumoFart[0][0],
    &s_SumoFart[1][0],
    &s_SumoFart[2][0],
    &s_SumoFart[3][0],
    &s_SumoFart[4][0]
};

//////////////////////
//
// SUMO CLAP
//
//////////////////////

#define SUMO_CLAP_RATE 12
ANIMATOR InitSumoClap;

STATE s_SumoClap[5][6] =
{
    {
        {SUMO_CLAP_R0 + 0, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[0][1]},
        {SUMO_CLAP_R0 + 1, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[0][2]},
        {SUMO_CLAP_R0 + 2, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[0][3]},
        {SUMO_CLAP_R0 + 2, SF_QUICK_CALL, InitSumoClap, &s_SumoClap[0][4]},
        {SUMO_CLAP_R0 + 3, SUMO_CLAP_RATE*10, NullSumo, &s_SumoClap[0][5]},
        {SUMO_CLAP_R0 + 3, SF_QUICK_CALL, InitActorDecide, &s_SumoClap[0][5]}
    },
    {
        {SUMO_CLAP_R1 + 0, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[1][1]},
        {SUMO_CLAP_R1 + 1, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[1][2]},
        {SUMO_CLAP_R1 + 2, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[1][3]},
        {SUMO_CLAP_R1 + 2, SF_QUICK_CALL, InitSumoClap, &s_SumoClap[1][4]},
        {SUMO_CLAP_R1 + 3, SUMO_CLAP_RATE*10, NullSumo, &s_SumoClap[1][5]},
        {SUMO_CLAP_R1 + 3, SF_QUICK_CALL, InitActorDecide, &s_SumoClap[1][5]}
    },
    {
        {SUMO_CLAP_R2 + 0, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[2][1]},
        {SUMO_CLAP_R2 + 1, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[2][2]},
        {SUMO_CLAP_R2 + 2, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[2][3]},
        {SUMO_CLAP_R2 + 2, SF_QUICK_CALL, InitSumoClap, &s_SumoClap[2][4]},
        {SUMO_CLAP_R2 + 3, SUMO_CLAP_RATE*10, NullSumo, &s_SumoClap[2][5]},
        {SUMO_CLAP_R2 + 3, SF_QUICK_CALL, InitActorDecide, &s_SumoClap[2][5]}
    },
    {
        {SUMO_CLAP_R3 + 0, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[3][1]},
        {SUMO_CLAP_R3 + 1, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[3][2]},
        {SUMO_CLAP_R3 + 2, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[3][3]},
        {SUMO_CLAP_R3 + 2, SF_QUICK_CALL, InitSumoClap, &s_SumoClap[3][4]},
        {SUMO_CLAP_R3 + 3, SUMO_CLAP_RATE*10, NullSumo, &s_SumoClap[3][5]},
        {SUMO_CLAP_R3 + 3, SF_QUICK_CALL, InitActorDecide, &s_SumoClap[3][5]}
    },
    {
        {SUMO_CLAP_R4 + 0, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[4][1]},
        {SUMO_CLAP_R4 + 1, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[4][2]},
        {SUMO_CLAP_R4 + 2, SUMO_CLAP_RATE, NullSumo, &s_SumoClap[4][3]},
        {SUMO_CLAP_R4 + 2, SF_QUICK_CALL, InitSumoClap, &s_SumoClap[4][4]},
        {SUMO_CLAP_R4 + 3, SUMO_CLAP_RATE*10, NullSumo, &s_SumoClap[4][5]},
        {SUMO_CLAP_R4 + 3, SF_QUICK_CALL, InitActorDecide, &s_SumoClap[4][5]}
    }
};

STATEp sg_SumoClap[] =
{
    &s_SumoClap[0][0],
    &s_SumoClap[1][0],
    &s_SumoClap[2][0],
    &s_SumoClap[3][0],
    &s_SumoClap[4][0]
};

//////////////////////
//
// SUMO STOMP
//
//////////////////////

#define SUMO_STOMP_RATE 30
ANIMATOR InitSumoStomp;

STATE s_SumoStomp[5][6] =
{
    {
        {SUMO_STOMP_R0 + 0, SUMO_STOMP_RATE, NullSumo, &s_SumoStomp[0][1]},
        {SUMO_STOMP_R0 + 1, SUMO_STOMP_RATE*3, NullSumo, &s_SumoStomp[0][2]},
        {SUMO_STOMP_R0 + 2, SUMO_STOMP_RATE, NullSumo, &s_SumoStomp[0][3]},
        {SUMO_STOMP_R0 + 2, 0|SF_QUICK_CALL, InitSumoStomp, &s_SumoStomp[0][4]},
        {SUMO_STOMP_R0 + 2, 8, NullSumo, &s_SumoStomp[0][5]},
        {SUMO_STOMP_R0 + 2, 0|SF_QUICK_CALL, InitActorDecide, &s_SumoStomp[0][5]}
    },
    {
        {SUMO_STOMP_R1 + 0, SUMO_STOMP_RATE, NullSumo, &s_SumoStomp[1][1]},
        {SUMO_STOMP_R1 + 1, SUMO_STOMP_RATE*3, NullSumo, &s_SumoStomp[1][2]},
        {SUMO_STOMP_R1 + 2, SUMO_STOMP_RATE, NullSumo, &s_SumoStomp[1][3]},
        {SUMO_STOMP_R1 + 2, 0|SF_QUICK_CALL, InitSumoStomp, &s_SumoStomp[1][4]},
        {SUMO_STOMP_R1 + 2, 8, NullSumo, &s_SumoStomp[1][5]},
        {SUMO_STOMP_R1 + 2, 0|SF_QUICK_CALL, InitActorDecide, &s_SumoStomp[1][5]}
    },
    {
        {SUMO_STOMP_R2 + 0, SUMO_STOMP_RATE, NullSumo, &s_SumoStomp[2][1]},
        {SUMO_STOMP_R2 + 1, SUMO_STOMP_RATE*3, NullSumo, &s_SumoStomp[2][2]},
        {SUMO_STOMP_R2 + 2, SUMO_STOMP_RATE, NullSumo, &s_SumoStomp[2][3]},
        {SUMO_STOMP_R2 + 2, 0|SF_QUICK_CALL, InitSumoStomp, &s_SumoStomp[2][4]},
        {SUMO_STOMP_R2 + 2, 8, NullSumo, &s_SumoStomp[2][5]},
        {SUMO_STOMP_R2 + 2, 0|SF_QUICK_CALL, InitActorDecide, &s_SumoStomp[2][5]}
    },
    {
        {SUMO_STOMP_R3 + 0, SUMO_STOMP_RATE, NullSumo, &s_SumoStomp[3][1]},
        {SUMO_STOMP_R3 + 1, SUMO_STOMP_RATE*3, NullSumo, &s_SumoStomp[3][2]},
        {SUMO_STOMP_R3 + 2, SUMO_STOMP_RATE, NullSumo, &s_SumoStomp[3][3]},
        {SUMO_STOMP_R3 + 2, 0|SF_QUICK_CALL, InitSumoStomp, &s_SumoStomp[3][4]},
        {SUMO_STOMP_R3 + 2, 8, NullSumo, &s_SumoStomp[3][5]},
        {SUMO_STOMP_R3 + 2, 0|SF_QUICK_CALL, InitActorDecide, &s_SumoStomp[3][5]}
    },
    {
        {SUMO_STOMP_R4 + 0, SUMO_STOMP_RATE, NullSumo, &s_SumoStomp[4][1]},
        {SUMO_STOMP_R4 + 1, SUMO_STOMP_RATE*3, NullSumo, &s_SumoStomp[4][2]},
        {SUMO_STOMP_R4 + 2, SUMO_STOMP_RATE, NullSumo, &s_SumoStomp[4][3]},
        {SUMO_STOMP_R4 + 2, 0|SF_QUICK_CALL, InitSumoStomp, &s_SumoStomp[4][4]},
        {SUMO_STOMP_R4 + 2, 8, NullSumo, &s_SumoStomp[4][5]},
        {SUMO_STOMP_R4 + 2, 0|SF_QUICK_CALL, InitActorDecide, &s_SumoStomp[4][5]}
    }
};

STATEp sg_SumoStomp[] =
{
    &s_SumoStomp[0][0],
    &s_SumoStomp[1][0],
    &s_SumoStomp[2][0],
    &s_SumoStomp[3][0],
    &s_SumoStomp[4][0]
};

//////////////////////
//
// SUMO DIE
//
//////////////////////

#define SUMO_DIE_RATE 30
ANIMATOR DoSumoDeathMelt;

STATE s_SumoDie[] =
{
    {SUMO_DIE + 0, SUMO_DIE_RATE*2, NullSumo, &s_SumoDie[1]},
    {SUMO_DIE + 1, SUMO_DIE_RATE, NullSumo, &s_SumoDie[2]},
    {SUMO_DIE + 2, SUMO_DIE_RATE, NullSumo, &s_SumoDie[3]},
    {SUMO_DIE + 3, SUMO_DIE_RATE, NullSumo, &s_SumoDie[4]},
    {SUMO_DIE + 4, SUMO_DIE_RATE, NullSumo, &s_SumoDie[5]},
    {SUMO_DIE + 5, SUMO_DIE_RATE, NullSumo, &s_SumoDie[6]},
    {SUMO_DIE + 6, SUMO_DIE_RATE, NullSumo, &s_SumoDie[7]},
    {SUMO_DIE + 6, SUMO_DIE_RATE*3, NullSumo, &s_SumoDie[8]},
    {SUMO_DIE + 7, SUMO_DIE_RATE, NullSumo, &s_SumoDie[9]},
    {SUMO_DIE + 6, SUMO_DIE_RATE, NullSumo, &s_SumoDie[10]},
    {SUMO_DIE + 7, SUMO_DIE_RATE, NullSumo, &s_SumoDie[11]},
    {SUMO_DIE + 6, SUMO_DIE_RATE-8, NullSumo, &s_SumoDie[12]},
    {SUMO_DIE + 7, SUMO_DIE_RATE, NullSumo, &s_SumoDie[13]},
    {SUMO_DIE + 7, SF_QUICK_CALL, DoSumoDeathMelt, &s_SumoDie[14]},
    {SUMO_DIE + 6, SUMO_DIE_RATE-15, NullSumo, &s_SumoDie[15]},
    {SUMO_DEAD, SF_QUICK_CALL, QueueFloorBlood, &s_SumoDie[16]},
    {SUMO_DEAD, SUMO_DIE_RATE, DoActorDebris, &s_SumoDie[16]}
};

STATEp sg_SumoDie[] =
{
    s_SumoDie
};

STATE s_SumoDead[] =
{
    {SUMO_DEAD, SUMO_DIE_RATE, DoActorDebris, &s_SumoDead[0]},
};

STATEp sg_SumoDead[] =
{
    s_SumoDead
};

/*
typedef struct
{
#define MAX_ACTOR_CLOSE_ATTACK 2
#define MAX_ACTOR_ATTACK 6
STATEp *Stand;
STATEp *Run;
STATEp *Jump;
STATEp *Fall;
STATEp *Crawl;
STATEp *Swim;
STATEp *Fly;
STATEp *Rise;
STATEp *Sit;
STATEp *Look;
STATEp *Climb;
STATEp *Pain;
STATEp *Death1;
STATEp *Death2;
STATEp *Dead;
STATEp *DeathJump;
STATEp *DeathFall;

STATEp *CloseAttack[MAX_ACTOR_CLOSE_ATTACK];
short  CloseAttackPercent[MAX_ACTOR_CLOSE_ATTACK];

STATEp *Attack[MAX_ACTOR_ATTACK];
short  AttackPercent[MAX_ACTOR_ATTACK];

STATEp *Special[2];
STATEp *Duck;
STATEp *Dive;
}ACTOR_ACTION_SET,*ACTOR_ACTION_SETp;
*/

ACTOR_ACTION_SET SumoActionSet =
{
    sg_SumoStand,
    sg_SumoRun,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL, //climb
    sg_SumoPain, //pain
    sg_SumoDie,
    NULL,
    sg_SumoDead,
    NULL,
    NULL,
    {sg_SumoStomp,sg_SumoFart},
    {800,1024},
    {sg_SumoClap,sg_SumoStomp,sg_SumoFart},
    {400,750,1024},
    {NULL},
    NULL,
    NULL
};

ACTOR_ACTION_SET MiniSumoActionSet =
{
    sg_SumoStand,
    sg_SumoRun,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL, //climb
    sg_SumoPain, //pain
    sg_SumoDie,
    NULL,
    sg_SumoDead,
    NULL,
    NULL,
    {sg_SumoClap},
    {1024},
    {sg_SumoClap},
    {1024},
    {NULL},
    NULL,
    NULL
};

int
SetupSumo(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u;
    ANIMATOR DoActorDecide;

    if (TEST(sp->cstat, CSTAT_SPRITE_RESTORE))
    {
        u = User[SpriteNum];
        ASSERT(u);
    }
    else
    {
        User[SpriteNum] = u = SpawnUser(SpriteNum,SUMO_RUN_R0,s_SumoRun[0]);
        u->Health = 6000;
    }

    if (Skill == 0) u->Health = 2000;
    if (Skill == 1) u->Health = 4000;

    ChangeState(SpriteNum,s_SumoRun[0]);
    u->Attrib = &SumoAttrib;
    DoActorSetSpeed(SpriteNum, NORM_SPEED);
    u->StateEnd = s_SumoDie;
    u->Rot = sg_SumoRun;

    EnemyDefaults(SpriteNum, &SumoActionSet, &SumoPersonality);

    sp->clipdist = (512) >> 2;
    if (sp->pal == 16)
    {
        // Mini Sumo
        sp->xrepeat = 43;
        sp->yrepeat = 29;
        u->ActorActionSet = &MiniSumoActionSet;
        u->Health = 500;
    }
    else
    {
        sp->xrepeat = 115;
        sp->yrepeat = 75;
    }

    //SET(u->Flags, SPR_XFLIP_TOGGLE);

    return 0;
}

int NullSumo(short SpriteNum)
{
    USERp u = User[SpriteNum];

    //if (TEST(u->Flags,SPR_SLIDING))
    //DoActorSlide(SpriteNum);

    if (!TEST(u->Flags,SPR_CLIMBING))
        KeepActorOnFloor(SpriteNum);

    DoActorSectorDamage(SpriteNum);

    return 0;
}

int DoSumoMove(short SpriteNum)
{
    USERp u = User[SpriteNum];

    //if (TEST(u->Flags,SPR_SLIDING))
    //DoActorSlide(SpriteNum);

    if (u->track >= 0)
        ActorFollowTrack(SpriteNum, ACTORMOVETICS);
    else
        (*u->ActorActionFunc)(SpriteNum);

    KeepActorOnFloor(SpriteNum);

    if (DoActorSectorDamage(SpriteNum))
    {
        return 0;
    }

    return 0;
}

#if 0
int InitSumoCharge(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];

    if (RANDOM_P2(1024) > 950)
        PlaySound(DIGI_SUMOALERT, &sp->x, &sp->y, &sp->z, v3df_follow);

    DoActorSetSpeed(SpriteNum, FAST_SPEED);

    InitActorMoveCloser(SpriteNum);

    NewStateGroup(SpriteNum, sg_SumoCharge);

    return 0;
}
#endif

int DoSumoRumble(short SpriteNum)
{
    SetSumoQuake(SpriteNum);

    return 0;
}

int InitSumoFart(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    extern int InitSumoNapalm(short SpriteNum);

    PlaySound(DIGI_SUMOFART, &sp->x, &sp->y, &sp->z, v3df_follow);

    InitChemBomb(SpriteNum);

    SetSumoFartQuake(SpriteNum);
    InitSumoNapalm(SpriteNum);

    return 0;
}

int InitSumoStomp(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    extern int InitSumoStompAttack(short SpriteNum);

    PlaySound(DIGI_SUMOSTOMP, &sp->x, &sp->y, &sp->z, v3df_none);
    SetSumoQuake(SpriteNum);
    InitSumoStompAttack(SpriteNum);

    return 0;
}

int InitSumoClap(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    extern int InitMiniSumoClap(short SpriteNum);
    extern int InitSumoSkull(short SpriteNum);

    if (sp->pal == 16 && RANDOM_RANGE(1000) <= 800)
        InitMiniSumoClap(SpriteNum);
    else
        InitSumoSkull(SpriteNum);
    return 0;
}

int DoSumoDeathMelt(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];

    PlaySound(DIGI_SUMOFART, &sp->x, &sp->y, &sp->z, v3df_follow);

    u->ID = SUMO_RUN_R0;
    InitChemBomb(SpriteNum);
    u->ID = 0;

    DoMatchEverything(NULL, sp->lotag, ON);
    if (!SW_SHAREWARE && gs.MusicOn)
        PlaySong(0, RedBookSong[Level], TRUE, FALSE);

    BossSpriteNum[1] = -2; // Sprite is gone, set it back to keep it valid!

    return 0;
}

void
BossHealthMeter(void)
{
    SPRITEp sp;
    USERp u;
    PLAYERp pp = Player + myconnectindex;
    short color=0,i=0,nexti,metertics,meterunit;
    int y;
    extern SWBOOL NoMeters;
    short health;

    if (NoMeters) return;

    if (Level != 20 && Level != 4 && Level != 11 && Level != 5) return;

    // Don't draw bar for other players
    if (pp != Player+myconnectindex)
        return;

    // all enemys
    if ((Level == 20 && (BossSpriteNum[0] == -1 || BossSpriteNum[1] == -1 || BossSpriteNum[2] == -1)) ||
        (Level == 4 && BossSpriteNum[0] == -1) ||
        (Level == 5 && BossSpriteNum[0] == -1) ||
        (Level == 11 && BossSpriteNum[1] == -1))
    {
        TRAVERSE_SPRITE_STAT(headspritestat[STAT_ENEMY], i, nexti)
        {
            sp = &sprite[i];
            u = User[i];

            if ((u->ID == SERP_RUN_R0 || u->ID == SUMO_RUN_R0 || u->ID == ZILLA_RUN_R0) && sp->pal != 16)
            {
                if (u->ID == SERP_RUN_R0)
                    BossSpriteNum[0] = i;
                else if (u->ID == SUMO_RUN_R0)
                    BossSpriteNum[1] = i;
                else if (u->ID == ZILLA_RUN_R0)
                    BossSpriteNum[2] = i;
            }
        }
    }

    if (BossSpriteNum[0] <= -1 && BossSpriteNum[1] <= -1 && BossSpriteNum[2] <= -1)
        return;

    // Only show the meter when you can see the boss
    if (!serpwasseen || !sumowasseen || !zillawasseen)
    {
        for (i=0; i<3; i++)
        {
            if (BossSpriteNum[i] >= 0)
            {
                sp = &sprite[BossSpriteNum[i]];
                u = User[BossSpriteNum[i]];

                if (cansee(sp->x, sp->y, SPRITEp_TOS(sp), sp->sectnum, pp->posx, pp->posy, pp->posz - Z(40), pp->cursectnum))
                {
                    if (i == 0 && !serpwasseen)
                    {
                        serpwasseen = TRUE;
                        if (!SW_SHAREWARE && gs.MusicOn)
                        {
                            PlaySong(0, 13, TRUE, FALSE);
                        }
                    }
                    else if (i == 1 && !sumowasseen)
                    {
                        sumowasseen = TRUE;
                        if (!SW_SHAREWARE && gs.MusicOn)
                        {
                            PlaySong(0, 13, TRUE, FALSE);
                        }
                    }
                    else if (i == 2 && !zillawasseen)
                    {
                        zillawasseen = TRUE;
                        if (!SW_SHAREWARE && gs.MusicOn)
                        {
                            PlaySong(0, 13, TRUE, FALSE);
                        }
                    }
                }
            }
        }
    }

    for (i=0; i<3; i++)
    {

        if (i == 0 && (!serpwasseen || BossSpriteNum[0] < 0))
            continue;
        if (i == 1 && (!sumowasseen || BossSpriteNum[1] < 0))
            continue;
        if (i == 2 && (!zillawasseen || BossSpriteNum[2] < 0))
            continue;

        sp = &sprite[BossSpriteNum[i]];
        u = User[BossSpriteNum[i]];

        if (u->ID == SERP_RUN_R0 && serpwasseen)
        {
            if (Skill == 0) health = 1100;
            else if (Skill == 1) health = 2200;
            else
                health = HEALTH_SERP_GOD;
            meterunit = health / 30;
        }
        else if (u->ID == SUMO_RUN_R0 && sumowasseen)
        {
            if (Skill == 0) health = 2000;
            else if (Skill == 1) health = 4000;
            else
                health = 6000;
            meterunit = health / 30;
        }
        else if (u->ID == ZILLA_RUN_R0 && zillawasseen)
        {
            if (Skill == 0) health = 2000;
            else if (Skill == 1) health = 4000;
            else
                health = 6000;
            meterunit = health / 30;
        }
        else
            continue;

        if (meterunit > 0)
        {
            if (u->Health < meterunit && u->Health > 0)
                metertics = 1;
            else
                metertics = u->Health / meterunit;
        }
        else
            continue;

        if (metertics <= 0)
        {
            SetRedrawScreen(pp);
            continue;
        }

        if (numplayers < 2) y = 10;
        else if (numplayers >=2 && numplayers <= 4) y = 20;
        else
            y = 30;

        if (Level == 20 && numplayers >= 2)
        {
            if (u->ID == SUMO_RUN_R0 && sumowasseen) y += 10;
            else if (u->ID == ZILLA_RUN_R0 && zillawasseen) y += 20;
        }

        if (metertics <= 12 && metertics > 6)
            color = 20;
        else if (metertics <= 6)
            color = 25;
        else
            color = 22;

        rotatesprite((73+12)<<16,y<<16,65536L,0,5407,1,1,
                     (ROTATE_SPRITE_SCREEN_CLIP),0,0,xdim-1,ydim-1);

        rotatesprite((100+47)<<16,y<<16,65536L,0,5406-metertics,1,color,
                     (ROTATE_SPRITE_SCREEN_CLIP),0,0,xdim-1,ydim-1);
    }

}

#include "saveable.h"

static saveable_code saveable_sumo_code[] =
{
    SAVE_CODE(SetupSumo),
    SAVE_CODE(NullSumo),
    SAVE_CODE(DoSumoMove),
    //SAVE_CODE(InitSumoCharge),
    SAVE_CODE(DoSumoRumble),
    SAVE_CODE(InitSumoFart),
    SAVE_CODE(InitSumoStomp),
    SAVE_CODE(InitSumoClap),
    SAVE_CODE(DoSumoDeathMelt),
};

static saveable_data saveable_sumo_data[] =
{
    SAVE_DATA(SumoBattle),
    SAVE_DATA(SumoOffense),
    SAVE_DATA(SumoBroadcast),
    SAVE_DATA(SumoSurprised),
    SAVE_DATA(SumoEvasive),
    SAVE_DATA(SumoLostTarget),
    SAVE_DATA(SumoCloseRange),

    SAVE_DATA(SumoPersonality),

    SAVE_DATA(SumoAttrib),

    SAVE_DATA(s_SumoRun),
    SAVE_DATA(sg_SumoRun),
    //SAVE_DATA(s_SumoCharge),
    //SAVE_DATA(sg_SumoCharge),
    SAVE_DATA(s_SumoStand),
    SAVE_DATA(sg_SumoStand),
    SAVE_DATA(s_SumoPain),
    SAVE_DATA(sg_SumoPain),
    SAVE_DATA(s_SumoFart),
    SAVE_DATA(sg_SumoFart),
    SAVE_DATA(s_SumoClap),
    SAVE_DATA(sg_SumoClap),
    SAVE_DATA(s_SumoStomp),
    SAVE_DATA(sg_SumoStomp),
    SAVE_DATA(s_SumoDie),
    SAVE_DATA(sg_SumoDie),
    SAVE_DATA(s_SumoDead),
    SAVE_DATA(sg_SumoDead),

    SAVE_DATA(SumoActionSet),
    SAVE_DATA(MiniSumoActionSet),
};

saveable_module saveable_sumo =
{
    // code
    saveable_sumo_code,
    SIZ(saveable_sumo_code),

    // data
    saveable_sumo_data,
    SIZ(saveable_sumo_data)
};


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior 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 2
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

#define S_INAGAME   "YOU ARE IN A GAME!"
#define S_PRESSKEY  "PRESS ANY KEY WHEN READY"
#define S_MESSAGEON "GAME MESSAGES ARE NOW ON"
#define S_MESSAGEOFF "GAME MESSAGES ARE NOW OFF"

#define S_QUITYN "PRESS (Y) TO QUIT, (N) TO FIGHT ON."
#define S_QUIT1  "CONFUCIOUS SAY, 'HE WHO QUITS,"
#define S_QUIT1A "TOTALLY SUCKS!'"
#define S_QUIT2  "GO AHEAD, LEAVE, BUT WHILE YOU'RE"
#define S_QUIT2A "GONE, EVIL WILL BE TAKING OVER!"
#define S_QUIT3  "ALL OF JAPAN IS COUNTING ON YOU!"
#define S_QUIT3A "ONLY A LOSER COULD LET THEM DOWN."
#define S_QUIT4  "THIS IS TOO MUCH!  NOW WHERE"
#define S_QUIT4A "DID I PUT THOSE CHEAT CODES?"
#define S_QUIT5  "PRESS (Y) TO RUN HOME TO MOMMY,"
#define S_QUIT5A "(N) TO BE A MAN."
#define S_QUIT6  "YOU DO KNOW, THAT WHILE YOU'RE"
#define S_QUIT6A "AWAY, WE'LL BE RESTOCKING MT. FUJI"
#define S_QUIT6B "WITH A MILLION NEW MONSTERS?"
#define S_QUIT7  "COME BACK ANY TIME, WE ARE ALWAYS"
#define S_QUIT7A "LOOKING FOR A SACK OF RIPPER CHOW!"
#define S_QUIT8  "LET'S MAKE A STEALTHY EXIT TO DOS!"