#include "game.hh"
#include "absorber.hh"
#include "adventure.hh"
#include "basepixel.hh"
#include "cable.hh"
#include "chunk.hh"
#include "connection.hh"
#include "cursorfield.hh"
#include "decorations.hh"
#include "display.hh"
#include "dragfield.hh"
#include "emitter.hh"
#include "escript.hh"
#include "fan.hh"
#include "fluid.hh"
#include "fluidbuffer.hh"
#include "font.hh"
#include "fxemitter.hh"
#include "game-message.hh"
#include "gravityman.hh"
#include "grid.hh"
#include "group.hh"
#include "gui.hh"
#include "i0o1gate.hh"
#include "i1o1gate.hh"
#include "i2o1gate.hh"
#include "item.hh"
#include "key_listener.hh"
#include "ladder.hh"
#include "ledbuffer.hh"
#include "linebuffer.hh"
#include "loading_screen.hh"
#include "main.hh"
#include "material.hh"
#include "menu_main.hh"
#include "menu_pkg.hh"
#include "motor.hh"
#include "object_factory.hh"
#include "object_finder.hh"
#include "objectfield.hh"
#include "panel.hh"
#include "pixel.hh"
#include "plant.hh"
#include "player_activator.hh"
#include "polygon.hh"
#include "progress.hh"
#include "proximitysensor.hh"
#include "ragdoll.hh"
#include "rand.hh"
#include "rc_activator.hh"
#include "robot.hh"
#include "robot_parts.hh"
#include "robotman.hh"
#include "rope.hh"
#include "rotozoom.hh"
#include "screenshot_marker.hh"
#include "scup.hh"
#include "settings.hh"
#include "shape_extruder.hh"
#include "simplebg.hh"
#include "soundman.hh"
#include "soundmanager.hh"
#include "spritebuffer.hh"
#include "terrain.hh" /* for print_screen_point_info */
#include "text.hh"
#include "textbuffer.hh"
#include "tpixel.hh"
#include "ui.hh"
#include "vendor.hh"
#include "widget_manager.hh"
#include "worker.hh"
#include "world.hh"
#include <SDL3/SDL.h>
#include <glad/gl.h>
#include <iterator>
#include <map>
#include <unistd.h>

#define MAX_COPY_ENTITIES 10

#define CSCONN_OFFSX .55f
#define CSCONN_OFFSY .0f

#define MAX_BTN 5
#define MAX_DIR_BTN 1
#define MAX_DIR_SLIDERS 1
#define MAX_MISC_WIDGETS 4

#define INTERACT_TRAIL_LEN 10
#define INTERACT_REACH 20
#define INTERACT_REACH_SQUARED (INTERACT_REACH*INTERACT_REACH)

#define DROP_FREQUENCY 50
#define DROP_SPEEDUP 0.05f

#define BAR_WIDTH       1.05f
#define BAR_HEIGHT      0.15f
#define BAR_Y_OFFSET    BAR_HEIGHT + 0.15f

//#define PROFILING

static bool enable_culling = true;

static const float RH_MAX_DIST = 15.f;
static const float RH_MAX_DIST_ALPHA = 13.f;
static const float RH_MIN_DIST = 1.f;

static tvec3 touch_quickplug_pos;
static tvec2 touch_pos[MAX_P];
static tvec2 touch_proj[MAX_P]; /* touch position projected onto z = 0 */
static uint64_t touch_time[MAX_P];

tvec2 move_pos;
static uint64_t move_time = 0;
static bool move_queried = false;

static uint64_t hov_time = 0;
static bool hov_fadeout = false;
static uint64_t hov_fadeout_time = 0;

#define HOVER_TIME 45000
#define HOVER_TIME_ACTIVE 5000

static float cam_move_x[2];
static float cam_move_y[2];

static cursorfield *in_cursorfield[MAX_P];
static cursorfield *drag_cursorfield[MAX_P];
static cursorfield *hover_cursorfield;

#define MAX_INTERACTING 1

static entity *interacting[MAX_INTERACTING];
static int current_interacting = -1;
static discharge_effect *interacting_discharge[MAX_INTERACTING];
static b2Vec2 interacting_discharge_lp[MAX_INTERACTING];
static float interacting_M[MAX_INTERACTING][INTERACT_TRAIL_LEN][16];
static float interacting_N[MAX_INTERACTING][INTERACT_TRAIL_LEN][9];
static int interacting_p[MAX_INTERACTING];
static uint32_t layer[MAX_P];

static bool dragging[MAX_P];
static bool moving[MAX_P];
static bool down[MAX_P];
static bool snap[MAX_P];
static uint8_t rotating[MAX_P];
static bool resizing[MAX_P];
static int resize_index = -1; /* index of the vertex or edge */
static uint8_t resize_type; /* 0 for vertex, 1 for edge */

static bool disable_menu = false;
static bool zooming = false;
static bool drawing = false;
static bool zoom_stopped = false;
static float zoom_dist = 0.f;
static panel::widget *wdg_up[MAX_DIR_BTN];
static panel::widget *wdg_down[MAX_DIR_BTN];
static panel::widget *wdg_left[MAX_DIR_BTN];
static panel::widget *wdg_right[MAX_DIR_BTN];
static panel::widget *wdg_btn[MAX_BTN];
static panel::widget *wdg_misc[MAX_MISC_WIDGETS];
static uint8_t wdg_up_i = 0;
static uint8_t wdg_down_i = 0;
static uint8_t wdg_left_i = 0;
static uint8_t wdg_right_i = 0;
static uint8_t wdg_btn_i = 0;
static uint8_t wdg_misc_i = 0;
static entity *copy_entity[MAX_COPY_ENTITIES];
static uint64_t prompt_slot[NUM_PROMPT_SLOTS];
static tvec3 old_cam_pos;

static int box_select_pid = 0;
static tvec3 begin_box_select;
static tvec3 end_box_select;
static entity_set box_select_entities;

static b2MotorJoint *mover_joint[MAX_INTERACTING];

static const char *trans_sources[] = {
    "attribute vec2 position;"
    "attribute vec2 texcoord;"
    "varying lowp vec2 FS_texcoord;"

    "uniform vec2 texcoord_trans;"
    "uniform vec2 position_trans;"
    "uniform vec2 position_trans_lower;"

    "void main(void) {"
        "vec2 tx = texcoord+texcoord_trans;"
        "vec2 trans = position_trans;"
        "if (position.y < 0.01) trans = position_trans_lower;"
        "FS_texcoord = tx;"
        "gl_Position = vec4(position+trans, .99, 1.);"
    "}",

    "uniform sampler2D tex_0;"
    "varying lowp vec2 FS_texcoord;"

    "void main(void) {"
        "gl_FragColor = texture2D(tex_0, FS_texcoord);"
    "}"
};

const char *src_brightpass[2] = {
    "attribute vec2 position;"
    "attribute vec2 texcoord;"
    "varying lowp vec2 FS_texcoord;"

    "void main(void) {"
        "FS_texcoord = texcoord;"
        "gl_Position = vec4(position, 0., 1.);"
    "}"
    ,
    "uniform mediump sampler2D tex_0;"
    "varying lowp vec2 FS_texcoord;"

    "void main(void) {"
        "vec3 color = texture2D(tex_0, FS_texcoord).rgb;"
        "float lum = dot(color, vec3(0.33, 0.33, 0.33));"
        "lum = lum*lum;"
        "gl_FragColor = vec4((lum - .5)*2.);"
    "}"
};

const char *src_output[2] = {
    "attribute vec2 position;"
    "attribute vec2 texcoord;"
    "varying lowp vec2 FS_texcoord;"

    "void main(void) {"
        "FS_texcoord = texcoord;"
        "gl_Position = vec4(position, 0., 1.);"
    "}"
    ,

    "uniform mediump sampler2D tex_0;"
    "varying lowp vec2 FS_texcoord;"

    /*
    "const float A = .2;"
    "const float B = .34;"
    "const float C = .3;"
    "const float D = .2;"
    "const float E = .069;"
    "const float F = .25;"
    */

    /*
    "const float A = 0.15;"
    "const float B = 0.50;"
    "const float C = 0.10;"
    "const float D = 0.20;"
    "const float E = 0.02;"
    "const float F = 0.25;"
    */

    "const float A = .25;"
    "const float B = .11;"
    "const float C = .2;"
    "const float D = .3;"
    "const float E = .07;"
    "const float F = .25;"

    "const vec3 W = vec3(0.45, 0.45, 0.45);"

    "vec3 tonemap(vec3 x) {"
        "return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F)) - E/F;"
    "}"

    "void main(void) {"
        /*
        "float vignette;"
        "vignette = distance(vec2(.5, .5), FS_texcoord);"
        "vignette *= vignette;"
        "vignette = 1.-vignette;"
        "vignette = 1.;"
        //"gl_FragColor = vec4(sqrt(vec3(tonemap(texture2D(tex_0, FS_texcoord).rgb)"
        //" * (1./.1880678)))*vignette, 1.);"
        "gl_FragColor = vec4(tonemap(texture2D(tex_0, FS_texcoord).rgb)/tonemap(W)*vignette, 1.);"
        //"gl_FragColor = vec4(sqrt(texture2D(tex_0, FS_texcoord).rgb), 1.);"
        //
        //
        */

        /*
        "vec3 col = texture2D(tex_0, FS_texcoord).rgb;"
        "col = max(vec3(0.), col-.01);"
        "gl_FragColor = vec4("
            "(col*(6.2*col+.5))/(col*(6.2*col+1.7))+.06"
            ", 1.);"
            */

        //"vec3 col = pow(texture2D(tex_0, FS_texcoord).rgb, vec3(1./2.2));"
        //"vec3 col = sqrt(texture2D(tex_0, FS_texcoord).rgb);"
        "vec3 col = sqrt(texture2D(tex_0, FS_texcoord).rgb);"
        "gl_FragColor = vec4(col, 1.);"
    "}"

    //"void main(void) {"
        //"gl_FragColor = pow(texture2D(tex_0, FS_texcoord), vec4(1./2.2, 1./2.2, 1./2.2, 1.));"
        //"gl_FragColor = sqrt(texture2D(tex_0, FS_texcoord));"
        //"gl_FragColor = vec4(sqrt(texture2D(tex_0, FS_texcoord).rgb), 1.);"
        //"gl_FragColor = pow(texture2D(tex_0, FS_texcoord), vec4(1./2.2, 1./2.2, 1./2.2, 1.));"
    //"}"

};

const char *tutorial_texts[NUM_TUTORIAL_TEXTS] =
{
    /* TUTORIAL_TEXT_PICKUP_EQUIPMENT */
    "When you find loose robot equipment, use the\n"
    "compressor tool to pick it up, bring it to a\n"
    "repair station to install it.",

    /* TUTORIAL_TEXT_ZAP_WOOD */
    "Use the Zapper tool to cut down trees.",

    /* TUTORIAL_TEXT_CAVE_FIRST_TIME */
    "Caves can be dangerous, you should gather some\n"
    "wood above ground before going too deep.\n"
    "Wood can be used to create ladder steps using\n"
    "the Builder tool."
        ,

    /* TUTORIAL_TEXT_REPAIR_STATION_DROP */
    "To drop equipment into the repair station,\n"
    "climb the ladder, aim at the blue antenna and\n"
    "charge the Compressor until it releases an item.",

    /* TUTORIAL_TEXT_BUILD_LADDERS */
    "BLA"
};

const char *tutorial_text_4th_pc =
    "Ladder steps can help you get out of deep caves,\n"
    "Create one by equipping the Builder tool, then \n"
    "click and drag from your character to where you\n"
    "want to create the ladder step.";

const char *tutorial_text_4th_mobile =
    "Ladder steps can help you get out of deep caves,\n"
    "Create one by equipping the Builder tool, then \n"
    "touch and swipe from your character to where you\n"
    "want to create the ladder step.";

bool
game_sorter::distance_to_creature::operator()(activator *a, activator *b)
{
    const b2Vec2 &player_pos = this->c->get_position();
    const float dist_a = b2Distance(a->get_activator_pos(), player_pos);
    const float dist_b = b2Distance(b->get_activator_pos(), player_pos);

    return dist_a < dist_b;
}

static tms_program *prg_output;
static tms_program *prg_brightpass;

static tms_program *trans_program;
GLuint trans_program_shift_loc;
GLuint trans_program_scale_loc;
GLuint trans_program_pos_loc;
GLuint trans_program_poslower_loc;

static void
deactive_misc_wdg(panel::widget **wdg)
{
    tms_debugf("DEACTIVATE");
    if (*wdg) {
        tms_debugf("DEACTIVATE WDG %p %p", wdg, *wdg);
        if ((*wdg)->is_slider()) {
            (*wdg)->render = tms_wdg_slider_render;
        } else if ((*wdg)->is_vslider()) {
            (*wdg)->render = tms_wdg_vslider_render;
        } else if ((*wdg)->is_radial()) {
            (*wdg)->render = tms_wdg_radial_render;
        } else if ((*wdg)->is_field()) {
            (*wdg)->render = tms_wdg_field_render;
        }

        tms_wdg_set_active(*wdg, 0);
        *wdg = 0;
    }
}

static void
active_slider_render(struct tms_wdg *w, struct tms_surface *s)
{
    float px = w->pos.x, py = w->pos.y;
    float sx = 1.f, sy = 0.f;
    float r = 0.f;

    if (_tms.emulating_portrait) {
        int xx = (int)px, yy = (int)py;
        tms_convert_to_portrait(&xx, &yy);
        px = (float)xx;
        py = (float)yy;

        sx = 0.f; sy = 1.f;
        r = -90.f;
    }

    /* Save old color */
    tvec4 old = s->ddraw->color;

    /* Set new color to the active greenish tint color */
    tms_ddraw_set_color(s->ddraw, ACTIVE_MISC_WIDGET_COLOR);

    /* Render base sprite */
    tms_ddraw_sprite_r(s->ddraw, w->s[0], px, py, w->size.x, w->size.y, r);

    {
        float pulse = cos((double)_tms.last_time/(2000 * 100.));
        r = pulse*2.f;
        pulse += 1.f;
        pulse /= 2.f;

        float gray  = 1.f - (pulse/8.f);
        float ngray = 1.f + (pulse/8.f);
        tms_ddraw_set_color(s->ddraw, gray, gray, ngray, 1.f);

        struct tms_sprite *spr = gui_spritesheet::get_sprite(S_MOUSE);

        float wmod = spr->width / spr->height;

        tms_ddraw_sprite_r(s->ddraw, spr,
                px + ((w->value[0]-.5f) * 2.f)*w->size.x/2.f*sx,
                py + ((w->value[0]-.5f) * 2.f)*w->size.x/2.f*sy,
                .6f * wmod * _tms.xppcm, .6f * _tms.yppcm, r);
    }

    /* Revert back to old color */
    tms_ddraw_set_color(s->ddraw, TVEC4_INLINE(old));
}

static void
active_vslider_render(struct tms_wdg *w, struct tms_surface *s)
{
    float px = w->pos.x, py = w->pos.y;
    float sx = 0.f, sy = 1.f;
    float r = -90.f; // base rotation of vertical slider is -90

    if (_tms.emulating_portrait) {
        int xx = (int)px, yy = (int)py;
        tms_convert_to_portrait(&xx, &yy);
        px = (float)xx;
        py = (float)yy;

        sx = -1.f; sy = 0.f;
        r = 0.f;
    }

    /* Save old color */
    tvec4 old = s->ddraw->color;

    /* Set new color to the active greenish tint color */
    tms_ddraw_set_color(s->ddraw, ACTIVE_MISC_WIDGET_COLOR);

    /* Render base sprite */
    tms_ddraw_sprite_r(s->ddraw, w->s[0], px, py, w->size.y, w->size.x, r);

    {
        float pulse = cos((double)_tms.last_time/(2000 * 100.));
        r = pulse*2.f;
        pulse += 1.f;
        pulse /= 2.f;

        float gray  = 1.f - (pulse/8.f);
        float ngray = 1.f + (pulse/8.f);
        tms_ddraw_set_color(s->ddraw, gray, gray, ngray, 1.f);

        struct tms_sprite *spr = gui_spritesheet::get_sprite(S_MOUSE);

        float wmod = spr->width / spr->height;

        tms_ddraw_sprite_r(s->ddraw, spr,
                px + ((w->value[0]-.5f) * 2.f)*w->size.y/2.f*sx,
                py + ((w->value[0]-.5f) * 2.f)*w->size.y/2.f*sy,
                .6f * wmod * _tms.xppcm, .6f * _tms.yppcm, r);
    }

    /* Revert back to old color */
    tms_ddraw_set_color(s->ddraw, TVEC4_INLINE(old));
}

static void
active_radial_render(struct tms_wdg *w, struct tms_surface *s)
{
    float px = w->pos.x, py = w->pos.y;
    float r = 0.f;

    if (_tms.emulating_portrait) {
        int xx = (int)px, yy = (int)py;
        tms_convert_to_portrait(&xx, &yy);
        px = (float)xx;
        py = (float)yy;

        r = -90.f;
    }

    /* Save old color */
    tvec4 old = s->ddraw->color;

    /* Set new color to the active greenish tint color */
    tms_ddraw_set_color(s->ddraw, ACTIVE_MISC_WIDGET_COLOR);

    /* Render base sprite */
    tms_ddraw_sprite_r(s->ddraw, w->s[0], px, py, w->size.x, w->size.y, r);

    /* Revert back to old color */
    tms_ddraw_set_color(s->ddraw, TVEC4_INLINE(old));

    float a =  w->value[0] * 2.f * M_PI + (_tms.emulating_portrait ? M_PI/2.f : 0.f);
    float cs = cosf(a);
    float sn = sinf(a);

    float knob_w = w->size.x/5.f;
    float knob_h = w->size.y/5.f;

    if (w->s[1]) {
        tms_ddraw_sprite_r(s->ddraw, w->s[1],
                px + w->size.x/2.1f * cs,
                py + w->size.y/2.1f * sn,
                knob_w,
                knob_h, r);

        if (w->enable_ghost) {
            a =  w->ghost[0] * 2.f * M_PI + (_tms.emulating_portrait ? M_PI/2.f : 0.f);
            cs = cosf(a);
            sn = sinf(a);

            tvec4 col = s->ddraw->color;
            tms_ddraw_set_color(s->ddraw, 1.f, 1.f, 1.f, 0.5f);
            tms_ddraw_sprite_r(s->ddraw, w->s[1],
                    w->pos.x + w->size.x/2.1f * cs,
                    w->pos.y + w->size.y/2.1f * sn,
                    knob_w,
                    knob_h,r);
            tms_ddraw_set_color(s->ddraw, col.x, col.y, col.z, col.w);
        }
    }
}

static void
active_field_render(struct tms_wdg *w, struct tms_surface *s)
{
    float px = w->pos.x, py = w->pos.y;
    float sx = 1.f, sy = 1.f;
    float r = 0.f;

    if (_tms.emulating_portrait) {
        int xx = (int)px, yy = (int)py;
        tms_convert_to_portrait(&xx, &yy);
        px = (float)xx;
        py = (float)yy;

        sx = 1.f; sy = 1.f;
        r = -90.f;
    }

    /* Save old color */
    tvec4 old = s->ddraw->color;

    /* Set new color to the active greenish tint color */
    tms_ddraw_set_color(s->ddraw, ACTIVE_MISC_WIDGET_COLOR);

    /* Render base sprite */
    tms_ddraw_sprite_r(s->ddraw, w->s[0], px, py, w->size.x, w->size.y, r);

    /* Revert back to old color */
    tms_ddraw_set_color(s->ddraw, TVEC4_INLINE(old));

    float knob_w = w->size.x/5.f;
    float knob_h = w->size.y/5.f;

    if (w->s[1]) {
        tms_ddraw_sprite_r(s->ddraw, w->s[1],
                px + ((w->value[0]-.5f)  * 1.8f)*w->size.x/2.f*sx,
                py + ((w->value[1]-.5f) * 1.8f)*w->size.y/2.f*sy,
                knob_w,
                knob_h,
                r);

        if (w->enable_ghost) {
            tvec4 col = s->ddraw->color;
            tms_ddraw_set_color(s->ddraw, 1.f, 1.f, 1.f, 0.5f);
            tms_ddraw_sprite_r(s->ddraw, w->s[1],
                    px + ((w->ghost[0]-.5f)  * 1.8f)*w->size.x/2.f*sx,
                    py + ((w->ghost[1]-.5f) * 1.8f)*w->size.y/2.f*sy,
                    knob_w,
                    knob_h,
                    r);
            tms_ddraw_set_color(s->ddraw, col.x, col.y, col.z, col.w);
        }
    }
}

static bool
set_active_double(panel::widget *wdg)
{
    bool is_self = (G->active_hori_wdg == wdg && G->active_vert_wdg == wdg);

    if (G->active_hori_wdg || G->active_vert_wdg) {
        deactive_misc_wdg(&G->active_hori_wdg);
        deactive_misc_wdg(&G->active_vert_wdg);

        if (is_self) {
            return false;
        }
    }

    if (wdg->is_radial()) {
        wdg->render = active_radial_render;
    } else if (wdg->is_field()) {
        wdg->render = active_field_render;
    }

    G->active_hori_wdg = wdg;
    G->active_vert_wdg = wdg;

    return true;
}

static bool
set_active_slider(panel::widget **active_wdg, panel::widget *wdg)
{
    bool is_self = (*active_wdg == wdg);

    if (*active_wdg) {
        if ((*active_wdg)->type == TMS_WDG_RADIAL || (*active_wdg)->type == TMS_WDG_FIELD) {
            deactive_misc_wdg(&G->active_hori_wdg);
            deactive_misc_wdg(&G->active_vert_wdg);
        } else {
            deactive_misc_wdg(active_wdg);
        }

        if (is_self) {
            return false;
        }
    }

    if (wdg->is_slider()) {
        wdg->render = active_slider_render;
    } else if (wdg->is_vslider()) {
        wdg->render = active_vslider_render;
    }

    *active_wdg = wdg;

    return true;
}

static bool
try_activate_slider(int slot)
{
    if (wdg_misc[slot]) {
        bool ret = true;
        bool warp = false;

        switch (wdg_misc[slot]->type) {
            case TMS_WDG_FIELD:
                warp = true;
            case TMS_WDG_RADIAL:
                ret = set_active_double(wdg_misc[slot]);
                break;
            case TMS_WDG_SLIDER:
                warp = true;
                ret = set_active_slider(&G->active_hori_wdg, wdg_misc[slot]);
                break;
            case TMS_WDG_VSLIDER:
                warp = true;
                ret = set_active_slider(&G->active_vert_wdg, wdg_misc[slot]);
                break;
            default:
                ret = false;
                break;
        }

        if (ret) {
            if (warp) {
                G->wdg_base_x = _tms.window_width/2;
                G->wdg_base_y = _tms.window_height/2;
                SDL_WarpMouseInWindow((SDL_Window*)_tms._window, G->wdg_base_x, G->wdg_base_y);
            } else {
                float mx, my;
                SDL_GetMouseState(&mx, &my);
                G->wdg_base_x = (int)mx;
                G->wdg_base_y = _tms.window_height - (int)my;
            }

            tms_wdg_set_active(wdg_misc[slot], 1);
        }

        return ret;
    }

    return false;
}

int
render_foreground(struct tms_rstate *state, void *value)
{
    int val = VOID_TO_INT(value);

    if (val == 3) {
        glColorMask(1,1,1,1);
        G->tmp_ao_mask = (tvec3){.0f, 0.f, 0.0f};
        return T_OK;
    }

    return 1;
}

int
render_next_prio(struct tms_rstate *state, void *value)
{
    int val = VOID_TO_INT(value);

    switch (val) {
        case 2:
            G->tmp_ao_mask = (tvec3){.0f, 0.f, 0.0f};
            glColorMask(1,1,1,1);
            if (W->is_adventure() && adventure::player && G->caveview_size > 0.f) {
                return 1;
            }
            break;

        case 1:
            G->tmp_ao_mask = (tvec3){.0f, 0.f, 1.0f};
            glColorMask(1,1,1,1);
            break;

        case 0:
            G->tmp_ao_mask = (tvec3){.0f, 1.f, 0.0f};
            glColorMask(1,1,1,1);
            break;

        default: return 1;
    }

    if (!((1 << val) & G->layer_vis)) {
        return 1;
    }

    return T_OK;
}

int
render_hidden_prio(struct tms_rstate *rstate, void *value)
{
    int val = VOID_TO_INT(value);

    switch (val) {
        case 2: G->tmp_ao_mask = (tvec3){.0f, 0.f, 0.0f}; break;
        case 1: G->tmp_ao_mask = (tvec3){.0f, 0.f, 1.0f}; break;
        case 0: G->tmp_ao_mask = (tvec3){.0f, 1.f, 0.0f}; break;

        default: return 1;
    }

    if ((!((1 << val) & G->layer_vis))
        || (W->is_adventure() && adventure::player && G->caveview_size > 0.f  && G->caveview_size < 1.f && val == 2)) {
        glEnable(GL_BLEND);
        /*glDepthMask(true);
        glEnable(GL_DEPTH_TEST);*/
        glBlendColor(1.f, 1.f, 1.f, G->caveview_size > 0.f ? 1.f-(tclampf(G->caveview_size, 0.0f, 1.f)) : .125f);
        glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
    } else {
        return 1;
    }

    return T_OK;
}

void
post_fn(struct tms_rstate *state)
{
    if (W->is_adventure() && adventure::player && G->caveview_size > 0.f) {
        glDepthMask(0);
        struct tms_entity *e = G->caveview;
        glDisable(GL_DEPTH_TEST);
        /* render the caveview fade stuff */
        struct tms_program *prog = m_bg_fixed.pipeline[0].program;
        tms_entity_apply_uniforms(e, state->p);
        tms_pipeline_apply_combined_uniforms(state->p, state, prog, e);
        tms_program_bind(prog);
        tmat4_load_identity(G->caveview->M);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, W->cwindow->caveview->gl_texture);
        tmat4_translate(G->caveview->M,
                //roundf(adventure::player->get_position().x*2.)/2.f- .25f,
                //roundf((adventure::player->get_position().y+.5f)*2.)/2.f- .25f,
                G->caveview_pos.x,
                G->caveview_pos.y,
                //(adventure::player->get_position().x*2.)/2.f- .25f,
                //(adventure::player->get_position().y+.5f)*2./2.f- .25f,
                2.0);
        tmat4_scale(G->caveview->M, CAVEVIEW_SIZE/2, CAVEVIEW_SIZE/2, 0.f);
        tmat4_copy(state->modelview, state->view);
        tmat4_multiply(state->modelview, e->M);
        tms_pipeline_apply_local_uniforms(state->p, state, prog, e);
        glEnable(GL_BLEND);
        glBlendFunc(GL_ZERO, GL_SRC_COLOR);
        tms_mesh_render(const_cast<tms_mesh*>(tms_meshfactory_get_square()), prog);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glDisable(GL_BLEND);
        glBindTexture(GL_TEXTURE_2D, 0);
        glDepthFunc(GL_LESS);
        glDepthMask(0xff);
    }

    /* TODO: copy blending stuff from material/escript */
    bool base_initialized = false;
    struct tms_program *prog = m_spritebuf.pipeline[0].program;

    for (std::set<escript*>::iterator it = W->escripts.begin(); it != W->escripts.end(); ++it) {
        escript *es = static_cast<escript*>(*it);
        draw_data *draw = es->normal_draw;
        if (!draw || es->coordinate_mode != ESCRIPT_SCREEN) continue;

        if (!base_initialized) {
            tmat4_load_identity(state->view);
            //tmat4_load_identity(state->projection);
            tmat4_set_ortho(state->projection, 0, 100, 0, 100, 1, -1);
            tmat4_load_identity(state->modelview);
            glDisable(GL_DEPTH_TEST);
            tms_program_bind(prog);
            tms_pipeline_apply_global_uniforms(state->p, state, prog);

            base_initialized = true;
        }

        struct tms_entity *e = draw->sprite_ent;
        tms_program_bind(prog);
        tms_entity_apply_uniforms(e, state->p);
        tms_pipeline_apply_combined_uniforms(state->p, state, prog, e);
        tms_pipeline_apply_local_uniforms(state->p, state, prog, e);

        tmat4_load_identity(draw->sprite_ent->M);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, draw->texture->gl_texture);

        if (es->blending_mode == 1) {
            glEnable(GL_BLEND);
            glDepthMask(false);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        } else if (es->blending_mode == 2) {
            glEnable(GL_BLEND);
            glDepthMask(false);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE);
        } else {
            glDisable(GL_BLEND);
            //glDepthMask(true); do we need to enable depth mask here?
        }

        tms_mesh_render(draw->sprite_ent->mesh, prog);

        glBindTexture(GL_TEXTURE_2D, 0);
    }

    if (base_initialized) {
        glDisable(GL_BLEND);
        glDepthMask(true);
    }

    /* TODO: disable blending, re-enable depth testing */
}

void
ao_post_fn(struct tms_rstate *state)
{
    /// XXX: what?
    return;
    if (W->level.type == LCAT_ADVENTURE && adventure::player) {
        glColorMask(0,0,1,0);
        float caves = G->caveview_size;

        struct tms_entity *e = G->caveview;
        struct tms_mesh *m = e->mesh;
        struct tms_program *prog = m_cavemask.pipeline[state->p].program;

        tms_program_bind(prog);
        tms_entity_apply_uniforms(e, state->p);
        tms_pipeline_apply_combined_uniforms(state->p, state, prog, e);

        tmat4_load_identity(G->caveview->M);
        tmat4_translate(G->caveview->M, adventure::player->get_position().x, adventure::player->get_position().y, 2.05f);
        tmat4_scale(G->caveview->M, caves, caves, 0.0f);
        tmat4_copy(state->modelview, state->view);
        tmat4_multiply(state->modelview, e->M);
        tms_pipeline_apply_local_uniforms(state->p, state, prog, e);
        tms_mesh_render(G->caveview->mesh, prog);
        //tms_varray_unbind_attributes(m->vertex_array, prog->last_locations);
    }
}

int ao_mask_color(struct tms_rstate *state, void *value)
{
    int val = VOID_TO_INT(value);

    switch (val) {
        case 2:
            glColorMask(0,0,1,0);
            break;
        case 1:
            glColorMask(0,1,0,0);
            break;
        case 0:
            glColorMask(1,0,0,0); break;
        default: return 1;
    }

    return T_OK;
}

int sort_blending(struct tms_rstate *rstate, void *value)
{
    int val = VOID_TO_INT(value);

    rstate->graph->sort_reverse_prio = 0;

    if (P.best_variable_in_the_world == 1337) {
        if (val != TMS_BLENDMODE_OFF) {
            return 1;
        }
    } else {
        switch (val) {
            default:
            case TMS_BLENDMODE_OFF:

                if (P.best_variable_in_the_world3 == 1) {
                    rstate->graph->sort_reverse_prio = 1;
                    glEnable(GL_BLEND);
                    glDepthMask(false);
                    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                } else {
                    rstate->graph->sort_reverse_prio = 0;
                    glDisable(GL_BLEND);
                    glDepthMask(true);
                }
                break;

            case TMS_BLENDMODE__SRC_ALPHA__ONE_MINUS_SRC_ALPHA:
                rstate->graph->sort_reverse_prio = 1;
                glEnable(GL_BLEND);
                glDepthMask(false);
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                break;

            case TMS_BLENDMODE__SRC_ALPHA__ONE:
                rstate->graph->sort_reverse_prio = 1;
                glEnable(GL_BLEND);
                glDepthMask(false);
                glBlendFunc(GL_SRC_ALPHA, GL_ONE);
                break;

            case TMS_BLENDMODE__ONE_MINUS_DST_COLOR__ONE_MINUS_SRC_ALPHA:
                rstate->graph->sort_reverse_prio = 1;
                glEnable(GL_BLEND);
                glDepthMask(false);
                glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA);
                break;
        }
    }

    return T_OK;
}

void
on_panel_change(tms_wdg *w, float value)
{
    if (value == 1.f) {
        if (W->is_adventure()) {
            G->set_control_panel(adventure::player);
        } else {
            G->set_control_panel(0);
        }
    }
}

static std::map<uint32_t, float> hp_offsets;


static const int NUM_ACTIVATOR_BINDINGS = 3;

static const char *activator_strings[NUM_ACTIVATOR_BINDINGS] = {
    "E",
    "S+E",
    "C+E",
};

static p_text *activator_texts[NUM_ACTIVATOR_BINDINGS];

game *G = 0;

game::game()
    : active_hori_wdg(0)
    , active_vert_wdg(0)
    , wdg_base_x(0)
    , wdg_base_y(0)
    , text_small(0)
    , info_label(0)
    , help_dragpanel(0)
    , panel_edit_need_scroll(false)
    , render_controls(false)
    , tex_controls(0)
{
    G = this;

    this->layer_vis = 7;

    this->layer_vis_saved = 7;

    this->current_keymod = 0;
    this->previous_keymod = 0;
    this->follow_options.linear = false;
    this->follow_options.offset_mode = 0;
    this->follow_options.offset.x = 0.f;
    this->follow_options.offset.y = 0.f;

    this->hov_ent = 0;

    this->caveview_size = 0.f;
    this->caveview_zoom = 0.f;
    this->inventory_highest_y = 0.f;
    this->inventory_scroll_offset = 0.f;
    this->dropping = -1;
    this->drop_step = 0;
    this->drop_amount = 0;
    this->drop_speed = 1.f;
    for (int x=0; x<NUM_PROMPT_SLOTS; ++x) {
        prompt_slot[x] = 0;
    }
    for (int x=0; x<MAX_RECENT; ++x) {
        this->recent[x] = -1;
    }

    this->force_static_update = 1;
    this->do_static_update = false;
    this->last_static_update = (tvec3){0.f,0.f,0.f};

    this->do_drop_interacting = false;
    this->current_prompt = 0;
    this->opened_special_level = 0;
    this->_lock = SDL_CreateMutex();
    this->previous_level = 0;
    this->screen_back = 0;
    this->state.waiting = false;
    this->score_highlight = 0.f;
    this->numfeed_timer = 0.f;
    this->score_text = new p_text(font::large, ALIGN_RIGHT, ALIGN_TOP);
    this->score_text->set_position(_tms.window_width, _tms.window_height);
    this->numfeed_text = new p_text(font::small);
    this->numfeed_text->set_position(_tms.window_width/2.f, _tms.window_height-_tms.yppcm/2.f/2.f);
    this->tmp_ao_layer = 1;
    this->tmp_ao_mask = (tvec3){.0f, 0.f, 0.f};
    this->cam_vel = (tvec3){0,0,0};

    for (unsigned x=0; x<NUM_ACTIVATOR_BINDINGS; ++x) {
        activator_texts[x] = new p_text(font::medium);
        activator_texts[x]->set_text(activator_strings[x]);
    }

    this->tmp_ambientdiffuse.x = P.default_ambient;
    this->tmp_ambientdiffuse.y = P.default_diffuse;

    this->sel_p_ent = 0;
    this->sel_p_body = 0;
    this->sel_p_frame = 0;

    this->current_panel = 0;
    this->follow_object = 0;

    this->cs_conn = 0;
    this->cs_timer = 0;

    this->state.abo_architect_mode = false;
    this->state.sandbox = true;
    this->state.edev_labels = false;
    this->state.gridsize = .25f;
    this->state.finished = false;
    this->set_score(0);

    this->_mode = GAME_MODE_DEFAULT;
    this->set_mode(GAME_MODE_DEFAULT);
    this->ss_edev = 0;
    this->ss_plug = 0;
    this->ss_asker = 0;
    this->ss_anim = .0f;
    this->ss_num_socks = 0;
    this->main_fb = 0;
    this->bloom_fb = 0;

    for (int x=0; x<NUM_CA; x++) {
        this->ca[x].life = -2.f;
    }

    //this->main_fb = tms_fb_alloc(_tms.window_width, _tms.window_height, 0);
    //this->main_fb = tms_fb_alloc(_tms.window_width, _tms.window_height, 1);

    this->icon_fb = 0;

    this->dd = tms_ddraw_alloc();

    this->set_scene(new tms::scene());

    this->cam_iterator = W->cam_markers.end();

    this->light = P.get_light_normal();

    this->bgent = new simplebg();
    this->grident = new grid();

    this->caveview = tms_entity_alloc();
    this->caveview->prio = 3;
    tms_entity_set_mesh(caveview, tms_ddraw_circle_mesh);
    tms_entity_set_material(caveview, &m_cavemask);
    tmat4_load_identity(caveview->M);
    tmat3_load_identity(caveview->N);

    //this->reset();
    //

    adventure::init();

    this->cam_rel_pos = b2Vec2(0,0);
    this->adv_rel_pos = b2Vec2(0,0);

    this->init_framebuffers();
    this->init_shaders();
    this->init_graphs();
    this->init_camera();
}

game::~game()
{
    delete this->wm;
    if (this->score_text) {
        delete this->score_text;
    }

    if (this->numfeed_text) {
        delete this->numfeed_text;
    }
    if (this->text_small) {
        delete this->text_small;
    }
    if (this->info_label) {
        delete this->info_label;
    }
    if (this->help_dragpanel) {
        delete this->help_dragpanel;
    }
    gui_spritesheet::cleanup();
    tms_atlas_free(this->texts);

    for (std::vector<struct menu_obj>::iterator it = menu_objects.begin();
            it != menu_objects.end(); ++it) {
        struct menu_obj &mo = *it;

        free(mo.name);
        delete mo.e;
    }
}

bool
game::occupy_prompt_slot()
{
    uint64_t cur_time = _tms.last_time;

    for (int x=0; x<NUM_PROMPT_SLOTS; ++x) {
        uint64_t dt = cur_time - prompt_slot[x];
        if (dt > PROMPT_TIME) {
            prompt_slot[x] = cur_time;
            tms_infof("Took prompt slot %d", x);
            return true;
        }
    }

    return false;
}

void
game::init_framebuffers()
{
    tms_infof("Initializing game framebuffers");

    if (!this->icon_fb) {
        this->icon_fb = tms_fb_alloc(512, 512, 0);
        tms_fb_add_texture(this->icon_fb, GL_RGB, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_NEAREST, GL_NEAREST);
        tms_fb_enable_depth(this->icon_fb, GL_DEPTH_COMPONENT16);
    }

    if (this->main_fb) {
        tms_fb_free(this->main_fb);
        this->main_fb = 0;
    }

    if (this->bloom_fb) {
        tms_fb_free(this->bloom_fb);
        this->bloom_fb = 0;
    }

    if (!_tms.use_gles) {
        if (settings["postprocess"]->v.b) {
            tms_infof("Postprocess time");
            this->main_fb = tms_fb_alloc(_tms.window_width, _tms.window_height, 0);
            tms_fb_add_texture(this->main_fb, GL_RGBA, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_NEAREST, GL_NEAREST);
            tms_fb_enable_depth(this->main_fb, GL_DEPTH_COMPONENT16);

            this->bloom_fb = tms_fb_alloc(_tms.window_width, _tms.window_height, 1);
            tms_fb_add_texture(this->bloom_fb, GL_RGBA, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_LINEAR, GL_LINEAR);
        } else {
            this->main_fb = 0;
            this->bloom_fb = 0;
        }
    }
}

void
game::init_shaders()
{
    tms_infof("Compiling shaders...");

    struct tms_shader *sh;

    sh = tms_shader_alloc();
    tms_shader_compile(sh, GL_VERTEX_SHADER, trans_sources[0]);
    tms_shader_compile(sh, GL_FRAGMENT_SHADER, trans_sources[1]);
    trans_program = (tms_shader_get_program(sh, TMS_NO_PIPELINE));
    trans_program_shift_loc = tms_program_get_uniform(trans_program, "texcoord_trans");
    trans_program_pos_loc = tms_program_get_uniform(trans_program, "position_trans");
    trans_program_poslower_loc = tms_program_get_uniform(trans_program, "position_trans_lower");

    sh = tms_shader_alloc();
    tms_shader_compile(sh, GL_VERTEX_SHADER, src_output[0]);
    tms_shader_compile(sh, GL_FRAGMENT_SHADER, src_output[1]);
    prg_output = tms_shader_get_program(sh, TMS_NO_PIPELINE);

    sh = tms_shader_alloc();
    tms_shader_compile(sh, GL_VERTEX_SHADER, src_brightpass[0]);
    tms_shader_compile(sh, GL_FRAGMENT_SHADER, src_brightpass[1]);
    prg_brightpass = tms_shader_get_program(sh, TMS_NO_PIPELINE);
}

void
game::init_graphs()
{
    tms_infof("Loading graphs...");

    this->graph = this->get_scene()->create_graph(0);
    this->graph->sorting[1] = TMS_SORT_PRIO;
    this->graph->sorting[0] = TMS_SORT_BLENDING;
    this->graph->sorting[2] = TMS_SORT_TEXTURE0;
    this->graph->sorting[3] = TMS_SORT_TEXTURE1;
    this->graph->sorting[4] = TMS_SORT_SHADER;
    this->graph->sorting[5] = TMS_SORT_VARRAY;
    this->graph->sorting[6] = TMS_SORT_MESH;
    this->graph->sort_depth = 7;
    this->graph->post_fn = post_fn;
    tms_graph_set_sort_callback(this->graph, TMS_SORT_BLENDING, sort_blending);
    tms_graph_enable_culling(this->graph, enable_culling);

    this->gi_graph = this->get_scene()->create_graph(1);
    this->gi_graph->sorting[0] = TMS_SORT_PRIO;
    this->gi_graph->sorting[1] = TMS_SORT_SHADER;
    this->gi_graph->sorting[2] = TMS_SORT_VARRAY;
    this->gi_graph->sorting[3] = TMS_SORT_MESH;
    this->gi_graph->sort_depth = 4;
    tms_graph_enable_culling(this->gi_graph, enable_culling);

    this->ao_graph = this->get_scene()->create_graph(3);
    this->ao_graph->sorting[0] = TMS_SORT_PRIO_BIASED;
    this->ao_graph->sorting[1] = TMS_SORT_SHADER;
    this->ao_graph->sorting[2] = TMS_SORT_VARRAY;
    this->ao_graph->sorting[3] = TMS_SORT_MESH;
    this->ao_graph->sort_depth = 4;
    this->ao_graph->post_fn = ao_post_fn;
    tms_graph_set_sort_callback(this->ao_graph, TMS_SORT_PRIO_BIASED, ao_mask_color);
    tms_graph_enable_culling(this->ao_graph, enable_culling);

    this->outline_graph = new tms::graph(0);
    this->outline_graph->scene_pos = 3;
    tms_graph_init(this->outline_graph, 0, 0);
    this->outline_graph->sorting[0] = TMS_SORT_SHADER;
    this->outline_graph->sorting[1] = TMS_SORT_TEXTURE0;
    this->outline_graph->sorting[2] = TMS_SORT_VARRAY;
    this->outline_graph->sorting[3] = TMS_SORT_MESH;
    this->outline_graph->sort_depth = 4;
    this->outline_graph->full_pipeline = 0;
    tms_graph_enable_culling(this->outline_graph, 0);
}

void
game::init_camera()
{
    tms_infof("Loading camera...");

    this->cam = new tms::camera();

    this->cam->enable(TMS_CAMERA_PERSPECTIVE);
    this->cam->set_direction(0, .0f, -1);
    this->cam->set_position(0, 5, 14);
    this->cam->up = (tvec3){0.f, 1.f, 0.f};
    this->cam->fov = 50;

    this->cam->set_direction(0, 0, -1);
    this->cam->calculate();

    this->ao_cam = new tms::camera();
    this->ao_cam->width = 1024.f/50.f;
    this->ao_cam->height = 1024.f/50.f;

    this->ao_cam->near = 0.0f - 2.0f;
    this->ao_cam->far = LAYER_DEPTH*3 + .5f;// + .75f;

    this->gi_cam = new tms::camera();
    this->gi_cam->width = 1024.f/50.f;
    this->gi_cam->height = 1024.f/50.f;
    this->gi_cam->near = 0.f - 2.f;
    this->gi_cam->far = LAYER_DEPTH*3 + .5f;// + .75f;
}

void
game::reset_touch(bool hard/*=true*/)
{
    hover_cursorfield = 0;
    current_interacting = -1;

    for (int x=0; x<MAX_P; x++) {
        this->mining[x] = false;
        down[x] = false;
        snap[x] = false;
        moving[x] = false;
        dragging[x] = false;
        resizing[x] = false;

        if (!hard) {
            if (in_cursorfield[x]) {
                in_cursorfield[x]->pressed --;
            }
        }
        in_cursorfield[x] = 0;

        if (!hard) {
            if (drag_cursorfield[x]) {
                drag_cursorfield[x]->dragged --;
            }
        }
        drag_cursorfield[x] = 0;

        rotating[x] = 0;
        touch_time[x] = _tms.last_time;
        layer[x] = -1;
    }

    if (hard) {
        for (int x=0; x<MAX_INTERACTING; x++) {
            /* XXX: release objects properly? */
            interacting[x] = 0;
            mover_joint[x] = 0;
        }
    } else {
        this->drop_interacting();
    }

    if (!settings["touch_controls"]->v.b)
        move_time = _tms.last_time;

    cam_move_x[0] = 0.f;
    cam_move_x[1] = 0.f;
    cam_move_y[0] = 0.f;
    cam_move_y[1] = 0.f;
    zooming = false;

    this->reset_touch_gui();
}

int
game::pause()
{
    tms_debugf("game::pause");
    sm::stop_all();
    ui::open_dialog(CLOSE_ABSOLUTELY_ALL_DIALOGS);

    if (!settings["touch_controls"]->v.b)
        SDL_SetWindowMouseGrab(_tms._window, false);

    return T_OK;
}

int
game::resume(void)
{
    this->render_controls = false;

    if (this->resume_action == GAME_START_NEW_ADVENTURE) {
        tms_infof("Resume action: Start new adventure");
        this->create_level(LCAT_ADVENTURE, false, true);
        this->state.test_playing = false;
        this->state.new_adventure = true;

        if (settings["first_adventure"]->is_true()) {
            this->render_controls = true;
            settings["first_adventure"]->set(false);
        }

        this->begin_play();
    } else if (this->resume_action == GAME_RESUME_NEW || this->resume_action == GAME_RESUME_NEW_EMPTY) {
        tms_infof("Resume action: New");

        uint32_t level_type = resume_level_type;

        this->create_level(level_type, this->resume_action == GAME_RESUME_NEW_EMPTY, false);

        if (!settings["hide_tips"]->v.b) {
            ui::open_sandbox_tips();
        }
    }

    if (this->resume_action == GAME_RESUME_OPEN) {
        /* do not do anything, the level was loaded before game was resumed */
        tms_infof("OPEN");
    }

#ifndef SCREENSHOT_BUILD
    this->state.fade = 1.f;
#endif
    this->resume_action = GAME_RESUME_CONTINUE;

    this->refresh_widgets();
    reset_touch();

    return T_OK;
}

void
game::back()
{
    tms_debugf("BACK PRESSED");
    if (this->state.test_playing) {
        this->state.test_playing = false;
        this->state.sandbox = true;
        this->state.puzzle_state = 0;
        this->open_sandbox(LEVEL_LOCAL, W->level.local_id);
    } else {
        if (this->screen_back == 0) {
            tms_infof("Returning to main menu.");

            if (W->level_id_type == LEVEL_DB) {
                ui::open_dialog(DIALOG_COMMUNITY);
            } else {
                if (this->state.sandbox && W->is_paused() && !this->state.test_playing && this->state.modified) {
                    tms_infof("Autosaving.");
                    W->save(SAVE_TYPE_AUTOSAVE);
                }
                sm::stop_all();
                tms::set_screen(P.s_menu_main);
            }

        } else {
            tms_infof("Returning to %p", this->screen_back);
            sm::stop_all();
            tms::set_screen(this->screen_back);
        }
    }

}

b2Joint*
game::create_joint(b2JointDef *jd)
{
    return W->b2->CreateJoint(jd);
}

static float saved_z;

void
game::set_caveview_zoom_limits(bool update)
{
    saved_z = this->cam->_position.z;

    if (caveview_size >= 2.f) {
        if (adventure::player && this->state.new_adventure) {
            if (adventure::player->inventory[RESOURCE_WOOD] > 0) {
                if (!(settings["tutorial"]->v.u32 & TUTORIAL_BUILD_LADDERS)) {
                    this->add_tt(TUTORIAL_TEXT_BUILD_LADDERS, 0, b2Vec2(0.f, 2.f), 15.f);
                    this->finished_tt(TUTORIAL_BUILD_LADDERS);
                }
            } else {
                if (!(settings["tutorial"]->v.u32 & TUTORIAL_CAVE_FIRST_TIME)) {
                    this->add_tt(TUTORIAL_TEXT_CAVE_FIRST_TIME, 0, b2Vec2(0.f, 2.f), 15.f);
                    this->finished_tt(TUTORIAL_CAVE_FIRST_TIME);
                }
            }
        }
    }

#ifdef DEBUG
    if (G->shift_down()) {
        return;
    }
#endif

#define CAVEVIEW_MAX_ZOOM 15.f

    if (update) {
        if (this->caveview_size > 0.f) {
            this->caveview_zoom = std::min(std::max(this->caveview_size, this->caveview_zoom), 1.f);
        } else {
            this->caveview_zoom -= _tms.dt*3.f;
        }
    }

    if (this->caveview_zoom > 0.f && this->cam->_position.z > CAVEVIEW_MAX_ZOOM) {
        /* force the zoom to a lower maximum if we're in a cave */
        float b = 1.f-powf(tclampf(this->caveview_zoom, 0.f, 1.f), 1.f);
        this->cam->_position.z = CAVEVIEW_MAX_ZOOM*(1.f-b) + this->cam->_position.z*b;
    }

    //tms_debugf("cam pos: %f", this->cam->_position.z);
}

void
game::unset_caveview_zoom_limits()
{
    this->cam->_position.z = saved_z;
}

void game::step_tooltip() {
    uint64_t diff = _tms.last_time - move_time;
    if (((this->hov_text->active && diff > HOVER_TIME_ACTIVE) || (this->hov_text->active == false && diff > HOVER_TIME)) && !move_queried) {
        move_queried = true;

        b2Body *_b;
        tvec2 _o;
        uint8_t _f;

        W->query(this->cam, (int)move_pos.x, (int)move_pos.y, &this->hov_ent, &_b, &_o, &_f, this->layer_vis, false, 0, true);

        hov_fadeout = true;
        if (this->hov_ent && this->hov_ent->g_id != O_CHUNK) {
            char tooltip_text[512];
            tooltip_text[0] = '\0';
            this->hov_ent->write_tooltip(tooltip_text);
            if (strlen(tooltip_text)) {
                hov_fadeout = false;
                if (!this->hov_text->active) {
                    hov_time = _tms.last_time;
                }
                this->hov_text->active = true;
                this->hov_text->set_text(tooltip_text);
            }
        } else {
            this->hov_ent = 0;

            if (this->hov_text->active && !hov_fadeout) {
                hov_fadeout = true;
                hov_fadeout_time = _tms.last_time + 175000;
            }
        }
    }

    if (this->hov_text->active) {
        float alpha = 0.f;
        float h = this->hov_text->get_num_lines() * this->hov_text->get_max_height();

        if (hov_fadeout) {
            const float x = 1.f-(float)((int64_t)_tms.last_time - (int64_t)hov_fadeout_time) / 175000.f;
            alpha = tclampf(x, 0.f, 1.f);
            this->hov_text->color.a = alpha;
            this->hov_text->outline_color.a = alpha;

            if (alpha <= 0.05f) {
                this->hov_text->active = false;
                tms_debugf("remove entirely");
                hov_fadeout = false;
            }
        } else {
            if (this->hov_ent) {
                float x = this->hov_ent->get_position().x;
                float y = this->hov_ent->get_position().y + (this->hov_ent->height * 1.2f);

                tvec3 dd = tms_camera_project(this->cam, x, y, this->hov_ent->get_layer()*LAYER_DEPTH);
                this->hov_text->set_position(dd.x, dd.y + this->wm->get_margin_y());
            }

            alpha = tclampf((float)(_tms.last_time - hov_time) / 175000.f, 0.f, 1.f);
            this->hov_text->color.a = alpha;
            this->hov_text->outline_color.a = alpha;
        }

        this->add_rounded_square(
                this->hov_text->get_x(),
                this->hov_text->get_y() + (this->hov_text->get_height() / 2.f),
                this->hov_text->get_width(),
                h * 1.05f,
                tvec4f(.2f, .2f, .2f, alpha*0.65f),
                2.f);
    }
}

int
game::step(double dt)
{
#ifndef SCREENSHOT_BUILD
    if (!P.focused) {
        _tms.time_accum = 0;
        return T_OK;
    }
#endif

    ++ this->state.step_num;

    if (this->state.waiting) {
        _tms.time_accum = 0;
    }

    if (!settings["touch_controls"]->v.b && settings["rc_lock_cursor"]->v.b) {
        if ((this->active_hori_wdg && !this->active_hori_wdg->is_radial())
                || (this->active_vert_wdg && !this->active_vert_wdg->is_radial())) {
            SDL_ShowCursor();
        } else {
            SDL_HideCursor();
        }
    }

    this->wm->step();

    /*
    if (this->state.time_mul > 0.f) {
        dt *= 1.f-this->state.time_mul;
        _tms.dt *= 1.f -this->state.time_mul;
        tms_infof("time mulling");
        tms_infof("dt == %f", _tms.dt);
    }
    */

    if (!settings["touch_controls"]->v.b)
        step_tooltip();

    if (this->state.sandbox && W->is_paused() && !this->state.test_playing) {
        /* do autosave */
        if (_tms.last_time > this->state.last_autosave_try+GAME_AUTOSAVE_INTERVAL && !down[0] && !down[1]) {
            this->state.last_autosave_try = _tms.last_time;

            if (this->state.modified) {
                tms_infof("autosaving");

                if (W->save(SAVE_TYPE_AUTOSAVE)) {
                    this->state.modified = false;
                }
            } else {
                //tms_infof("autosave: nothing modified");
            }
        }
    }

#ifdef PROFILING
    Uint32 ss = SDL_GetTicks();
#endif

    while (W->step()) {
        /* step never returns true here if we're paused */

        if (this->do_drop_interacting) {
            this->drop_interacting();
            this->do_drop_interacting = false;
        }

        if (W->is_adventure()) {
            adventure::step();
        }

        for(int x=0; x<MAX_INTERACTING; ++x) {
            if (interacting[x] && (W->level.type == LCAT_ADVENTURE || interacting[x]->in_dragfield || W->level.flag_active(LVL_DO_NOT_REQUIRE_DRAGFIELD))) {
                edevice *ed = interacting[x]->get_edevice();
                if (mover_joint[x] && ed && current_interacting != -1 && dragging[current_interacting]) ed->recreate_all_cable_joints();

                tmat4_copy(interacting_M[x][(interacting_p[x]+1)%INTERACT_TRAIL_LEN], interacting[x]->M);
                tmat3_copy(interacting_N[x][(interacting_p[x]+1)%INTERACT_TRAIL_LEN], interacting[x]->N);

                //interacting_M[x][(interacting_p[x]+1)%INTERACT_TRAIL_LEN][14] += .01f;
                tmat4_scale(interacting_M[x][(interacting_p[x]+1)%INTERACT_TRAIL_LEN], 1.05f, 1.05f, 1.05f);

                /*
                tmat4_copy(interacting_M[x][(interacting_p[x]+1)%INTERACT_TRAIL_LEN], interacting[x]->M);
                tmat3_copy(interacting_N[x][(interacting_p[x]+1)%INTERACT_TRAIL_LEN], interacting[x]->N);
                */

                tmat4_lerp(
                        interacting_M[x][interacting_p[x]%INTERACT_TRAIL_LEN],
                        interacting_M[x][(interacting_p[x]-1)%INTERACT_TRAIL_LEN],
                        interacting_M[x][(interacting_p[x]+1)%INTERACT_TRAIL_LEN],
                        .5f
                        );

                interacting_p[x] += 1;

                if (adventure::player && interacting_discharge[x]) {
                    interacting_discharge[x]->set_points(
                                        adventure::player->get_position(),
                                        interacting[x]->get_position(),
                                        //interacting[x]->local_to_world(interacting_discharge_lp[x], 0),
                                        adventure::player->get_layer() * LAYER_DEPTH,
                                        interacting[x]->get_layer() * LAYER_DEPTH);
                }

                //b2Vec2 p1 = mover_joint[x]->GetBodyA()->GetWorldPoint(mover_joint[x]->GetAnchorA());
                //b2Vec2 p2 = mover_joint[x]->GetBodyB()->GetWorldPoint(mover_joint[x]->GetAnchorB());

                if (mover_joint[x] && ((mover_joint[x]->GetBodyB()->GetPosition() - mover_joint[x]->GetBodyA()->GetPosition()) - mover_joint[x]->GetLinearOffset()).Length() > 2.f) {
                    tms_debugf("NO!");
                    this->drop_interacting();
                    continue;
                }

                tvec3 tproj;
                W->get_layer_point(this->cam, last_cursor_pos_x, last_cursor_pos_y, 0, &tproj);

                if (interacting[x]->g_id == O_LADDER_STEP) {
                    sel_p_offs.x = 0;
                    sel_p_offs.y = 0;
                    tproj.x *= 2.f;
                    tproj.y *= 2.f;
                    tproj.x = roundf(tproj.x+.5f);
                    tproj.y = roundf(tproj.y);
                    tproj.x /= 2.f;
                    tproj.y /= 2.f;
                    tproj.x -= .25f;
                }

                if (mover_joint[x] && x == current_interacting) {
                    mover_joint[x]->SetLinearOffset(b2Vec2(tproj.x-sel_p_offs.x, tproj.y-sel_p_offs.y));
                }

                if (mover_joint[x] && x != current_interacting) {
                    mover_joint[x]->SetLinearOffset(interacting[x]->get_body(0)->GetPosition());
                }

                if (W->level.type == LCAT_ADVENTURE && adventure::player) {
                    b2Vec2 p1 = adventure::player->get_position();
                    b2Vec2 p2 = interacting[x]->get_position();
                    if ((p2-p1).LengthSquared() > INTERACT_REACH_SQUARED || (x != current_interacting && (adventure::player->is_moving_left() || adventure::player->is_moving_right()))) {
                        this->destroy_mover(x);
                    }
                }

                if (interacting[x] && interacting[x]->is_creature()) {
                    creature *c = static_cast<creature*>(interacting[x]);
                    tms_infof("We are interacting with a creature---");
                    if (W->level.flag_active(LVL_ABSORB_DEAD_ENEMIES)) {
                        /* XXX FIXME TODO: Is the creature in the list for timed absorbs? */
                        //G->timed_absorb(this, W->level.dead_enemy_absorb_time);
                    }
                }
            } else {
                this->destroy_mover(x);
            }
        }

        if (!W->is_paused() && W->is_adventure()) {
            if (this->dropping != -1 && adventure::player->get_num_resources(this->dropping)) {
                int step_diff = W->step_count - this->drop_step;

                float speedup = std::min(this->drop_speed, 5.f);
                if (step_diff % (int)(DROP_FREQUENCY / speedup) == 0) {
                    adventure::player->drop_resource(this->dropping, this->drop_amount, b2Vec2(adventure::player->look_dir*1.25f, .75f));
                    if (!adventure::player->get_num_resources(this->dropping)) this->refresh_inventory_widgets();
                    this->drop_speed += DROP_SPEEDUP;
                }
            }
        }

        if (!W->is_paused() && this->follow_object) {
#ifndef SCREENSHOT_BUILD
            if (this->follow_options.linear) {
                b2Vec2 p = this->follow_object->get_position();
                b2Vec2 offset(0,0);
                if (this->follow_options.offset_mode == 0) { // global
                    offset.Set(this->follow_options.offset.x, this->follow_options.offset.y);
                } else if (this->follow_options.offset_mode == 1) {
                    float cs, sn;
                    tmath_sincos(this->follow_object->get_angle(), &sn, &cs);

                    offset.x = this->follow_options.offset.x*cs - this->follow_options.offset.y*sn;
                    offset.y = this->follow_options.offset.x*sn + this->follow_options.offset.y*cs;
                }

                p += offset;

                this->cam->_position.x = p.x;
                this->cam->_position.y = p.y;
            } else {
                b2Vec2 p = b2Vec2(this->cam->_position.x, this->cam->_position.y);

                p -= this->follow_object->get_position();

                b2Vec2 offset;
                if (this->follow_options.offset_mode == 0) { // global
                    offset.Set(this->follow_options.offset.x, this->follow_options.offset.y);
                } else { // relative
                    float cs, sn;
                    tmath_sincos(this->follow_object->get_angle(), &sn, &cs);

                    offset.x = this->follow_options.offset.x*cs - this->follow_options.offset.y*sn;
                    offset.y = this->follow_options.offset.x*sn + this->follow_options.offset.y*cs;
                }

                p -= offset;

                if (!W->level.flag_active(LVL_DISABLE_CAM_MOVEMENT)) {
                    p -= this->cam_rel_pos;
                }

                if (W->is_adventure() && this->follow_object == adventure::player) {
                    p -= this->adv_rel_pos;
                }

                double dist = p.Length() / 2.f;
                double rdist = dist;
                if (dist > 0.) {
                    dist*=dist*dist*dist*dist;
                    double s = WORLD_STEP / 10000. * G->get_time_mul();

                    if (dist > rdist) dist = rdist;

                    dist *= s;

                    p *= 1./(double)p.Length();
                    p *= dist;
                } else {
                    p.x = 0.f;
                    p.y = 0.f;
                }

                this->cam->_position.x -= p.x;
                this->cam->_position.y -= p.y;
            }
#endif
        }
    }

#ifdef PROFILING
    tms_infof("box2d ms: %d (num bodies: %d, num joints: %d)", SDL_GetTicks() - ss, W->b2->GetBodyCount(), W->b2->GetJointCount());
    ss = SDL_GetTicks();
#endif

    if (W->is_paused() && this->_mode == GAME_MODE_DRAW && drawing) {
        this->handle_draw(0, touch_pos[0].x, touch_pos[0].y);
    }

#define BASE_BORDER_SCROLL_SPEED 1.f

    if (settings["border_scroll_enabled"]->v.b) {
        double fps_mod = (_tms.fps_mean < 60. ? 60. : _tms.fps_mean) / 60.;

        if (cam_move_x[0]) {
            this->cam->_position.x -= BASE_BORDER_SCROLL_SPEED * settings["border_scroll_speed"]->v.f * cam_move_x[0] * 0.25f / fps_mod;
        } else if (cam_move_x[1]) {
            this->cam->_position.x += BASE_BORDER_SCROLL_SPEED * settings["border_scroll_speed"]->v.f * cam_move_x[1] * 0.25f / fps_mod;
        }
        if (cam_move_y[0]) {
            this->cam->_position.y -= BASE_BORDER_SCROLL_SPEED * settings["border_scroll_speed"]->v.f * cam_move_y[0] * 0.25f / fps_mod;
        } else if (cam_move_y[1]) {
            this->cam->_position.y += BASE_BORDER_SCROLL_SPEED * settings["border_scroll_speed"]->v.f * cam_move_y[1] * 0.25f / fps_mod;
        }
    }

#undef BASE_BORDER_SCROLL_SPEED

    float damping = powf(.025f, dt);
    this->cam_vel.x *= damping;
    this->cam_vel.y *= damping;
    this->cam_vel.z *= damping;

#ifndef SCREENSHOT_BUILD
    if (!W->level.flag_active(LVL_DISABLE_CAM_MOVEMENT) || (this->state.sandbox && W->is_paused())) {
        this->cam->_position.x += this->cam_vel.x * dt;
        this->cam->_position.y += this->cam_vel.y * dt;
    }
#endif

    if (!W->level.flag_active(LVL_DISABLE_ZOOM) || this->state.sandbox) {
        this->cam->_position.z += this->cam_vel.z * dt;
    }

    //this->cam_rel_pos.x += this->cam_vel.x * dt;
    //this->cam_rel_pos.y += this->cam_vel.y * dt;

    if (!settings["smooth_cam"]->v.b) {
        this->cam_vel.x = 0.f;
        this->cam_vel.y = 0.f;
    }

    if (!settings["smooth_zoom"]->v.b) {
        this->cam_vel.z = 0.f;
    }

#ifdef DEBUG
    float max_z = 200.f;
#else
    float max_z = 60.f;
#endif
    float min_z = 4.f;

#ifndef SCREENSHOT_BUILD
    if (!W->level.flag_active(LVL_DISABLE_ADVENTURE_MAX_ZOOM) && !W->is_paused() && W->is_adventure() && this->follow_object == adventure::player) {
        max_z = 20.f;
    }
#endif

    if (this->cam->_position.z > max_z) {
        this->cam->_position.z = max_z;
    } else if (this->cam->_position.z < min_z) {
        this->cam->_position.z = min_z;
    }

#ifndef SCREENSHOT_BUILD
    /* Apply camera movement constraints */
    if (W->is_playing() && W->is_adventure() && adventure::player && this->follow_object == adventure::player) {
        if (this->cam_rel_pos.Length() > 0.1f) {
            float adventure_max_cam_dist = 7.f;

            if (this->caveview_size > 0.f) {
                adventure_max_cam_dist = 4.5f;
            }

            const b2Vec2 player_pos = adventure::player->get_position();

            const float x_diff = this->cam->_position.x-adventure::player->get_position().x;
            const float y_diff = this->cam->_position.y-adventure::player->get_position().y;

#ifdef DEBUG
            if (!this->shift_down()) {
#endif

            if (x_diff > adventure_max_cam_dist) {
                float diff = adventure_max_cam_dist-x_diff;
                this->cam->_position.x += diff;
                this->cam_rel_pos.x += diff;
            } else if (x_diff < -adventure_max_cam_dist) {
                float diff = x_diff + adventure_max_cam_dist;
                this->cam->_position.x -= diff;
                this->cam_rel_pos.x -= diff;
            }

            if (y_diff > adventure_max_cam_dist) {
                float diff = adventure_max_cam_dist-y_diff;
                this->cam->_position.y += diff;
                this->cam_rel_pos.y += diff;
            } else if (y_diff < -adventure_max_cam_dist) {
                float diff = y_diff + adventure_max_cam_dist;
                this->cam->_position.y -= diff;
                this->cam_rel_pos.y -= diff;
            }

#ifdef DEBUG /* shift_down */
            }
#endif
        }
    }
#endif

    this->set_caveview_zoom_limits(true);

    this->cam->far = this->cam->_position.z+1.f;
    this->cam->near = this->cam->_position.z-3*LAYER_DEPTH;

    tvec3 l = this->light;

    tvec3 l_to_c = (tvec3){ l.x, l.y, 1.f};

    tvec3_mul(&l, LAYER_DEPTH*3);

    this->ao_cam->set_direction(0,0,-1.f);//TVEC3_INLINE_N(l));
    this->gi_cam->set_direction(0,0,-1.f);//TVEC3_INLINE_N(l));

    l.x += roundf(this->cam->_position.x);
    l.y += roundf(this->cam->_position.y);
    /*l.x += this->cam->_position.x;
    l.y += this->cam->_position.y;*/

    this->gi_cam->width = 1024.f/50.f * fmaxf(this->cam->_position.z/11.f, .1f);
    this->gi_cam->height = 1024.f/50.f * fmaxf(this->cam->_position.z/11.f, .1f);

    //this->ao_cam->width = 1024.f/50.f * this->cam->_position.z/11.f;
    //this->ao_cam->height = 1024.f/50.f * this->cam->_position.z/11.f;

    this->ao_cam->width = this->gi_cam->width;
    this->ao_cam->height = this->gi_cam->height;

    this->gi_cam->set_position(l.x, l.y, l.z);
    this->ao_cam->set_position(l.x, l.y, l.z);
    //this->ao_cam->set_position(l.x-l_to_c.x*3.f, l.y-l_to_c.y*3.f, l.z);
    //this->gi_cam->set_position(0, 0, l.z);
    this->gi_cam->up = (tvec3){0.f, 1.f, 0.f};
    this->ao_cam->up = (tvec3){0.f, 1.f, 0.f};
    //tvec3_normalize(&this->gi_cam->up);

    if (this->state.abo_architect_mode && W->is_paused()) {

        this->cam->enable(TMS_CAMERA_PERSPECTIVE);
        this->cam->width = _tms.window_width;
        this->cam->height = _tms.window_height;
        this->cam->calculate();

        tvec3 dd = tms_camera_project(this->cam, this->cam->_position.x, this->cam->_position.y, LAYER_DEPTH*1.f);

        tvec3 top = tms_camera_unproject(this->cam, 0.f, _tms.window_height, dd.z);

        this->cam->disable(TMS_CAMERA_PERSPECTIVE);
        /*
        this->cam->width = 20.f;
        this->cam->height = 20.f;
        this->cam->owidth = 20.f;
        this->cam->oheight = 20.f;
        */

        this->cam->width = fabsf(top.x - this->cam->_position.x)*2.f;
        this->cam->height = fabsf(top.y - this->cam->_position.y)*2.f;
        this->cam->owidth = _tms.window_width;
        this->cam->oheight = _tms.window_height;
        //this->cam->owidth = _tms.window_width/50.f;
        //this->cam->oheight = _tms.window_height/50.f;
    } else {
        this->cam->enable(TMS_CAMERA_PERSPECTIVE);
        this->cam->width = _tms.window_width;
        this->cam->height = _tms.window_height;
    }

    this->cam->calculate();
    //this->cam->view[10] = .5f;

    tmat4_copy(this->cam->combined, this->cam->projection);
    tmat4_multiply(this->cam->combined, this->cam->view);

    this->gi_cam->calculate();
    this->ao_cam->calculate();

    if (settings["shadow_ao_combine"]->v.b) {
        /* GI */
        float skew[16];
        tmat4_load_identity(skew);
        //tvec3_normalize(&l_to_c);
        skew[8] = -l_to_c.x / this->light.z;
        skew[9] = -l_to_c.y / this->light.z;
        tmat4_multiply(this->gi_cam->projection, skew);

        tmat4_copy(this->gi_cam->combined, this->gi_cam->projection);
        tmat4_multiply(this->gi_cam->combined, this->gi_cam->view);

        /* AO */

        skew[8] = -l_to_c.x / this->light.z;
        skew[9] = -l_to_c.y / this->light.z;
        tmat4_multiply(this->ao_cam->projection, skew);

        tmat4_load_identity(skew);
        //skew[10] = 0;
        skew[14] = -LAYER_DEPTH;
        tmat4_multiply(this->ao_cam->view, skew);

        tmat4_copy(this->ao_cam->combined, this->ao_cam->projection);
        /*this->ao_cam->view[11] = 0.f;
        this->ao_cam->view[10] = 0.f;
        this->ao_cam->view[9] = 0.f;*/
        //this->ao_cam->view[10] = 0.0f;
        //this->ao_cam->view[12] = -1.0f;
        //this->ao_cam->view[14] = -1.f;
        tmat4_multiply(this->ao_cam->combined, this->ao_cam->view);

        /* create the shadow matrix */
        static float shadow_bias[] = {
            .5f, 0, 0, 0,
            0, .5f, 0, 0,
            0, 0, .5f, 0,
            .5f, .5f, .5f, 1.f
        };

        tmat4_copy(this->SMVP, shadow_bias);
        tmat4_multiply(this->SMVP, this->gi_cam->combined);

        float tmp[16];
        tmat4_copy(tmp, this->cam->combined);
        tmat4_invert(tmp);

        tmat4_multiply(this->SMVP, tmp);
    } else {
        /* create SMVP and AOMVP separately */

        /* create the ao matrix */
        static float bias[] = {
            .5f, 0, 0, 0,
            0, .5f, 0, 0,
            0, 0, .5f, 0,
            .5f, .5f, .5f, 1.f
        };

        float inv[16];
        tmat4_copy(inv, this->cam->combined);
        tmat4_invert(inv);

        tmat4_copy(this->AOMVP, bias);
        tmat4_multiply(this->AOMVP, this->ao_cam->combined);
        tmat4_multiply(this->AOMVP, inv);

        /* shadow matrix */
        float skew[16];
        tmat4_load_identity(skew);
        //tvec3_normalize(&l_to_c);
        skew[8] = -l_to_c.x / this->light.z;
        skew[9] = -l_to_c.y / this->light.z;
        tmat4_multiply(this->gi_cam->projection, skew);

        tmat4_copy(this->gi_cam->combined, this->gi_cam->projection);
        tmat4_multiply(this->gi_cam->combined, this->gi_cam->view);
        tmat4_copy(this->SMVP, bias);
        tmat4_multiply(this->SMVP, this->gi_cam->combined);

        tmat4_multiply(this->SMVP, inv);
    }

    /* XXX place this somewhere else? */
    if (W->is_paused() || W->level.type == LCAT_ADVENTURE) {
        this->update_pairs();
    }

    sm::position.x = this->cam->_position.x;
    sm::position.y = this->cam->_position.y;
    sm::step();

    {
        tvec3 dd;
        tvec3 top;
        tvec3 bottom;
        dd = tms_camera_project(this->cam, this->cam->_position.x, this->cam->_position.y, 0.f);

        if (!_tms.emulating_portrait) {
            top = tms_camera_unproject(this->cam, _tms.window_width, _tms.window_height, dd.z);
            bottom = tms_camera_unproject(this->cam, 0.f, 0.f, dd.z);

#if false && defined DEBUG
            const float CHUNK_EXTRA_CULLING = .4f;
            float ff = CHUNK_EXTRA_CULLING*this->cam->_position.z;
            top.x -= ff;
            top.y -= ff;
            bottom.x += ff;
            bottom.y += ff;
#endif
        } else {
            top = tms_camera_unproject(this->cam, 0, _tms.window_height, dd.z);
            bottom = tms_camera_unproject(this->cam, _tms.window_width, 0.f, dd.z);
        }

        float min_x = std::min(bottom.x, top.x);
        float min_y = std::min(bottom.y, top.y);
        float max_x = std::max(bottom.x, top.x);
        float max_y = std::max(bottom.y, top.y);

        bool update = true;
#ifdef DEBUG
        if (this->shift_down()) {
            update = false;
        }
#endif
        if (update) {
            const b2Vec2 b2min(min_x, min_y);
            const b2Vec2 b2max(max_x, max_y);

            /*
            tms_infof("Setting chunk window min: %.2f/%.2f max: %.2f/%.2f", b2min.x, b2min.y, b2max.x, b2max.y);
            tms_infof("top: %.2f/%.2f/%.2f", top.x, top.y, top.z);
            tms_infof("bottom: %.2f/%.2f/%.2f", bottom.x, bottom.y, bottom.z);
            tms_infof("min: %.2f/%.2f", min_x, min_y);
            tms_infof("max: %.2f/%.2f", max_y, max_y);
            tms_infof("cam pos: %.2f/%.2f %.2f", this->cam->_position.x, this->cam->_position.y, this->cam->_position.z);
            tms_infof("dd: %.2f/%.2f/%.2f", dd.x, dd.y, dd.z);

            tms_infof("cam vel: %.2f/%.2f/%.2f",
                    this->cam->_velocity.x,
                    this->cam->_velocity.y,
                    this->cam->_velocity.z
                    );

            tms_infof("cam dir: %.2f/%.2f/%.2f",
                    this->cam->_direction.x,
                    this->cam->_direction.y,
                    this->cam->_direction.z
                    );

            tms_infof("cam lookat: %.2f/%.2f/%.2f",
                    this->cam->_lookat.x,
                    this->cam->_lookat.y,
                    this->cam->_lookat.z
                    );

            tms_infof("cam up: %.2f/%.2f/%.2f",
                    this->cam->up.x,
                    this->cam->up.y,
                    this->cam->up.z
                    );

            tms_infof("view: ");
            tmat4_dump(this->cam->view);

            tms_infof("proj: ");
            tmat4_dump(this->cam->projection);

            tms_infof("combined: ");
            tmat4_dump(this->cam->combined);
            */

            W->cwindow->set(b2min, b2max);
        }
    }

    if (material_factory::background_id == BG_OUTDOOR && this->bgent && this->bgent->scene) {
        tmat4_load_identity(this->bgent->M);
        tmat4_translate(this->bgent->M, roundf(this->cam->_position.x), -100.f, -0.499f);
        tmat4_scale(this->bgent->M, 200, 200, 1.0f);
    } else if (this->grident && this->grident->scene) {
        tmat4_load_identity(this->grident->M);
        tmat4_translate(this->grident->M, roundf(this->cam->_position.x), roundf(this->cam->_position.y), -0.499f);
        tmat4_scale(this->grident->M, 200, 200, .0f);
    }

    this->unset_caveview_zoom_limits();

    //tms_infof("rest of step ms: %d", SDL_GetTicks() - ss);
    return T_OK;
}

void
game::update_ghost_entity(entity *ths)
{
    if (ths->flag_active(ENTITY_CUSTOM_GHOST_UPDATE)) {
        ths->ghost_update();
    } else {
        tmat4_load_identity(ths->M);
        tmat4_translate(ths->M, ths->_pos.x, ths->_pos.y, ths->get_layer()*LAYER_DEPTH);
        tmat4_rotate(ths->M, ths->_angle * (180.f/M_PI), 0, 0, -1);
        tmat3_copy_mat4_sub3x3(ths->N, ths->M);
    }
}

void
game::update_static_entities()
{
    pixel::reset_counter();
    tpixel::reset_counter();

    for (std::set<entity*>::iterator i = this->u_static.begin();
            i != this->u_static.end(); i++) {
        if (enable_culling && tms_graph_is_entity_culled(this->graph, (*i)))
            continue;
        entity *ee = (*i);
        b2Vec2 p = ee->get_position();
        float a = ee->get_angle();

        float c,s;
        tmath_sincos(a, &s, &c);

        ee->M[0] = c;
        ee->M[1] = s;
        ee->M[4] = -s;
        ee->M[5] = c;
        ee->M[12] = p.x;
        ee->M[13] = p.y;
        ee->M[14] = ee->prio * LAYER_DEPTH;

        tmat3_copy_mat4_sub3x3(ee->N, ee->M);
    }

    for (std::set<entity*>::iterator i = this->u_static_custom.begin();
            i != this->u_static_custom.end(); i++) {
        if (enable_culling && tms_graph_is_entity_culled(this->graph, (*i)))
            continue;
        entity *ths = (*i);
        ths->update();
    }
}

void
game::update_entities()
{
#ifdef PROFILING
    Uint32 ss = SDL_GetTicks();
#endif

    for (std::set<entity*>::iterator i = this->u_ghost.begin();
            i != this->u_ghost.end(); i++)
        game::update_ghost_entity(*i);

#ifdef PROFILING
    tms_infof("counts: fastbody=%d, grouped=%d, custom=%d, joints=%d, pjoints=%d, stepable=%d, mstepable=%d", u_fastbody.size(), u_grouped.size(), u_custom.size(), u_joint.size(), u_joint_pivot.size(), W->stepable.size(), W->mstepable.size());
#endif
    for (std::set<entity*>::iterator i = this->u_fastbody.begin();
            i != this->u_fastbody.end(); i++) {
        entity *ths = (*i);
        if (enable_culling && tms_graph_is_entity_culled(this->graph, ths)) {
            continue;
        }

        ths->fastbody_update();
    }

#ifdef PROFILING
    tms_infof("fastbody: %d", SDL_GetTicks() - ss);
    ss = SDL_GetTicks();
#endif

    if (this->do_static_update || W->is_paused()) {
        this->update_static_entities();
    }

    //tms_infof("num grouped: %d", this->u_grouped.size());
    for (std::set<entity*>::iterator i = this->u_grouped.begin();
            i != this->u_grouped.end(); i++) {
        composable *ee = static_cast<composable*>(*i);

        if (enable_culling && tms_graph_is_entity_culled(this->graph, ee))
            continue;

        ee->grouped_update();
    }

#ifdef PROFILING
    tms_infof("grouped: %d", SDL_GetTicks() - ss);
    ss = SDL_GetTicks();
#endif

    /* update all pivot joints */
    for (std::set<entity*>::iterator i = this->u_joint_pivot.begin();
            i != this->u_joint_pivot.end(); i++) {
        connection_entity *e = static_cast<connection_entity*>(*i);
        connection *conn = e->conn;
        b2Vec2 p = conn->e->local_to_world(conn->p, conn->f[0]);
        e->M[12] = p.x;
        e->M[13] = p.y;
        e->M[14] = conn->layer*LAYER_DEPTH + LAYER_DEPTH*.85f;
    }

#ifdef PROFILING
    tms_infof("pivot joints: %d", SDL_GetTicks() - ss);
    ss = SDL_GetTicks();
#endif

    /* update all other joints */
    for (std::set<entity*>::iterator i = this->u_joint.begin();
            i != this->u_joint.end(); i++) {
        connection_entity *e = static_cast<connection_entity*>(*i);
        connection *conn = e->conn;
        b2Vec2 p = conn->e->local_to_world(conn->p, conn->f[0]);
        tmat4_load_identity(e->M);
        tmat4_translate(e->M, p.x, p.y, conn->layer*LAYER_DEPTH + (conn->multilayer ? LAYER_DEPTH*.85f:0));

        tmat4_rotate(e->M, (conn->e->get_angle(conn->f[0])+conn->angle)*(180.f/M_PI)+90.f, 0, 0, -1);

        if (conn->render_type == CONN_RENDER_NAIL) {
            tmat4_rotate(e->M, 90, 1, 0, 0);
        }

        tmat3_copy_mat4_sub3x3(e->N, e->M);

        if (conn->multilayer && conn->type == CONN_WELD) {
            tmat4_scale(e->M, .10f, .10f, 1.0f);
        } else if (conn->render_type == CONN_RENDER_NAIL) {
            tmat4_scale(e->M, 1.f, 1.f, 0.5f);
        } else if (conn->multilayer && conn->render_type == CONN_RENDER_SMALL) {
            tmat4_scale(e->M, 1.f, 1.f, .75f);
        } else if (!conn->multilayer && conn->render_type == CONN_RENDER_SMALL) {
            tmat4_scale(e->M, .5f, .5f, 1.f);
        }
    }

#ifdef PROFILING
    tms_infof("joints: %d", SDL_GetTicks() - ss);
    ss = SDL_GetTicks();
#endif

    if (w_is_enabled()) {
        w_updatec_set = &this->u_custom;
        w_updatec_graph = enable_culling ? this->graph : 0;

        std::set<entity*>::iterator i = this->u_custom.begin();
        for (int x=0; x<w_get_num_workers() && i != this->u_custom.end(); x++) {
            struct wdata_updatec data;
            data.i = x;
            w_run(W_RUN_UPDATEC, &data);
            i++;
        }

        w_wait(-1);
    } else {
        for (std::set<entity*>::iterator i = this->u_custom.begin();
                i != this->u_custom.end(); i++) {
            if (enable_culling && tms_graph_is_entity_culled(this->graph, *i))
                continue;
            tms_entity_update(static_cast<struct tms_entity*>(*i));
        }
    }

#ifdef PROFILING
    tms_infof("custom: %d", SDL_GetTicks() - ss);
    ss = SDL_GetTicks();
#endif
}

static inline void _uncull(struct tms_entity *e)
{
    if (e->scene && tms_scene_is_entity_culled(G->get_scene(), (struct tms_entity*)e)) {
        tms_scene_uncull_entity(G->get_scene(), (struct tms_entity*)e);
        tms_graph_uncull_entity(G->graph, (struct tms_entity*)e);
        tms_graph_uncull_entity(G->gi_graph, (struct tms_entity*)e);
        tms_graph_uncull_entity(G->ao_graph, (struct tms_entity*)e);
    }
}

static inline void
_uncull_full(entity *e)
{
    _uncull(e);

    edevice *ee;
    if ((ee = e->get_edevice())) {
        for (int x=0; x<ee->num_s_in; x++) {
            if (ee->s_in[x].p) {
                _uncull(ee->s_in[x].p);
                if (ee->s_in[x].p->c)
                    _uncull(ee->s_in[x].p->c);
            }
        }
        for (int x=0; x<ee->num_s_out; x++) {
            if (ee->s_out[x].p) {
                _uncull(ee->s_out[x].p);
                if (ee->s_out[x].p->c)
                    _uncull(ee->s_out[x].p->c);
            }
        }
    }

    connection *c = e->conn_ll;
    while (c) {
        if (c->self_ent) _uncull(c->self_ent);
        connection *next = c->get_next(e);

        c = next;
    }

    if (e->gr) _uncull(e->gr);
}

bool
_uncull_handler::ReportFixture(b2Fixture *f)
{
    entity *e;

    if ((e = static_cast<entity*>(f->GetUserData()))) {
        if (e->g_id == O_CHUNK) {
            return true;
        }
        if (W->is_paused() && e->type == ENTITY_PLUG && e->flag_active(ENTITY_IS_OWNED)) {
            _uncull_full(static_cast<entity*>(e->parent));
            _uncull(e);
        } else {
#if 0
            if (f->GetShape()->GetType() == b2Shape::e_circle) {
                tms_infof("unculling circle");
            }
            if (f->GetShape()->GetType() == b2Shape::e_polygon) {
                tms_infof("unculling poly");
            }
#endif
            _uncull_full(e);
        }
    }
    return true;
}

bool
_box_select_handler::ReportFixture(b2Fixture *f)
{
    entity *e;

    if ((e = static_cast<entity*>(f->GetUserData()))) {
        if (e->g_id == O_CHUNK) return true;
        if (e->is_static()) return true;
        if (e->flag_active(ENTITY_IS_PLUG)) {
            e = e->get_property_entity();
        }

        box_select_entities.insert(e);
    }

    return true;
}

int
game::render()
{
    // only delay on android (XXX: why?)
#ifdef SDL_PLATFORM_ANDROID
    if (!P.focused)
        SDL_Delay(500);
#endif

    int ierr;

    if ((ierr = glGetError()) != 0)
        tms_errorf("gl error %d in game::render begin", ierr);

#ifdef PROFILING
    Uint32 ss = SDL_GetTicks();
#endif

    this->set_caveview_zoom_limits();

    display::reset();
    ledbuffer::reset();
    spritebuffer::reset();
    fluidbuffer::reset();
    linebuffer::reset();
    textbuffer::reset();
    cable::reset_counter();
    rope::reset_counter();
    plant::reset_counter();

    if (gui_spritesheet::tmp_atlas_modified) {
        tms_texture_upload(&gui_spritesheet::tmp_atlas->texture);
        gui_spritesheet::tmp_atlas_modified = false;
    }

#ifdef PROFILING
    tms_infof("texture upload: %d", SDL_GetTicks() - ss);
    ss = SDL_GetTicks();
#endif

    tms_graph_enable_culling(this->graph, enable_culling);
    tms_graph_enable_culling(this->gi_graph, enable_culling);
    tms_graph_enable_culling(this->ao_graph, enable_culling);

    tvec3 vdist = this->cam->_position;
    vdist.x -= this->last_static_update.x;
    vdist.y -= this->last_static_update.y;
    vdist.z -= this->last_static_update.z;

    float dist = tvec3_magnitude(&vdist);

    if (force_static_update == 1 || dist > 5.f) {
        this->force_static_update = 0;
        this->do_static_update = true;
        this->last_static_update = this->cam->_position;
    } else {
        this->do_static_update = false;
    }

    b2AABB aabb;

    if (enable_culling) {
        tms_scene_cull_all(this->get_scene());
        tms_graph_cull_all(this->graph);
        tms_graph_cull_all(this->gi_graph);
        tms_graph_cull_all(this->ao_graph);

        if (W->is_paused()) {
            if (this->selection.e)
                _uncull_full(this->selection.e);
            entity *e = this->get_pending_ent();
            if (e) _uncull(e);
        }

        tms_graph_uncull_entity(this->graph, display::get_full_entity());
        tms_graph_uncull_entity(this->graph, ledbuffer::get_entity());
        tms_graph_uncull_entity(this->graph, linebuffer::get_entity());
        tms_graph_uncull_entity(this->graph, linebuffer::get_entity2());
        tms_graph_uncull_entity(this->graph, textbuffer::get_entity());
        tms_graph_uncull_entity(this->graph, textbuffer::get_entity2());
        tms_graph_uncull_entity(this->graph, fluidbuffer::get_entity());
        tms_graph_uncull_entity(this->graph, spritebuffer::get_entity());
        tms_graph_uncull_entity(this->graph, spritebuffer::get_entity2());

        _uncull(W->cwindow);
        _uncull(pixel::get_entity(0));
        _uncull(pixel::get_entity(1));
        _uncull(pixel::get_entity(2));
        _uncull(tpixel::get_entity(0));
        _uncull(tpixel::get_entity(1));
        _uncull(tpixel::get_entity(2));

        _uncull(cable::get_entity());
        _uncull(rope::get_entity());

        if (this->grident->scene) {
            _uncull(this->grident);
        }

        if (this->bgent->scene) {
            if (!this->state.abo_architect_mode)
                _uncull(this->bgent);
            else {
                /* only uncull the borders */
                for (int x=0; x<this->bgent->num_children; x++) {
                    tms_graph_uncull_entity(this->graph, this->bgent->children[x]);
                }
            }
        }

        tvec3 dd = tms_camera_project(this->cam, this->cam->_position.x, this->cam->_position.y, 0.f);
        tvec3 projs[4];
        projs[0] = tms_camera_unproject(this->cam, _tms.window_width, _tms.window_height, dd.z);
        projs[1] = tms_camera_unproject(this->cam, _tms.window_width, 0,                  dd.z);
        projs[2] = tms_camera_unproject(this->cam, 0,                 _tms.window_height, dd.z);
        projs[3] = tms_camera_unproject(this->cam, 0,                 0,                  dd.z);

        float minx = projs[0].x;
        float maxx = projs[0].x;
        float miny = projs[0].y;
        float maxy = projs[0].y;

        for (int x=1; x<4; ++x) {
            if (projs[x].x <= minx) {
                minx = projs[x].x;
            } else if (projs[x].x >= maxx) {
                maxx = projs[x].x;
            }

            if (projs[x].y <= miny) {
                miny = projs[x].y;
            } else if (projs[x].y >= maxy) {
                maxy = projs[x].y;
            }
        }

        /*
        float s = 10.f;
        spritebuffer::add(minx, miny, 1.4f, 1.f, 0.f, 1.f, 1.f, s, s, 0, 0.f);
        spritebuffer::add(maxx, maxy, 1.4f, 0.f, 1.f, 0.f, 1.f, s, s, 0, 0.f);
        */

        float ddd = do_static_update ? 8.f : 3.f;
        maxx += ddd; maxy += ddd;
        minx -= ddd; miny -= ddd;

        aabb.lowerBound.Set(minx, miny);
        aabb.upperBound.Set(maxx, maxy);

        W->b2->QueryAABB(&uncull_handler, aabb);

        if (this->caveview_size > 0.f && adventure::player) {
            this->caveview_pos = (tvec2){
                        roundf(adventure::player->get_position().x*2.f)/2.f - .25f,
                        roundf((adventure::player->get_position().y+.25f)*2.f)/2.f
                        //adventure::player->get_position().x*2.f/2.f,
                        //(adventure::player->get_position().y+.5f)*2.f/2.f
            };
            W->cwindow->recreate_caveview_texture(
                        roundf(adventure::player->get_position().x*2.f)/2.f,
                        roundf((adventure::player->get_position().y+.25f)*2.f)/2.f,
                        adventure::player->get_position().x,
                        (adventure::player->get_position().y+.25f)
                    );
        }
    }

#ifdef PROFILING
    tms_infof("culling: %d", SDL_GetTicks() - ss);
    ss = SDL_GetTicks();
#endif

    {
        int num_p = W->b2->GetParticleCount();
        b2Vec2 *p = W->b2->GetParticlePositionBuffer();
        float32 *a = W->b2->GetParticleAccumulationBuffer();
        void **u = W->b2->GetParticleUserDataBuffer();

#if 0
        b2ParticleContact *cc = W->b2->m_particleSystem.m_contactBuffer;
        int32 num_contacts = W->b2->m_particleSystem.m_contactCount;

        for (int x=0; x<num_contacts; x++) {
            int32 a = cc[x].indexA;
            int32 b = cc[x].indexB;
            float z = (float)(int)((uintptr_t)u[a]) * LAYER_DEPTH;

            linebuffer::add(p[a].x, p[a].y, z, p[b].x, p[b].y, z,
                    1.0f, 1.f, 1.f, cc[x].weight*2.f,
                    1.0f, 1.f, 1.f, cc[x].weight*2.f,
                    0.1f, 0.1f
                    );
        }
#endif

        for (int x=0; x<num_p; x++) {
            float pressure = fabsf((1.0f-a[x]*.55f) - .2f)*2.75f;
            float z = (float)(int)((uintptr_t)u[x]) * LAYER_DEPTH;

            //pressure = tclampf(1.f-pressure, .10f, .5f);
            pressure = .25f - pressure*.125f;
            pressure = tclampf(pressure, .05, .25f);
            float size = fmaxf(.05f+fminf(a[x]*.22f, .4f), .075f);
            /*fluidbuffer::add(p[x].x,p[x].y, z,
                    pressure*1.05f, pressure*1.02f, pressure, .33f, size, size);*/
            fluidbuffer::add(p[x].x,p[x].y, z,
                    pressure,
                    size, size);
        }
    }

#ifdef PROFILING
    tms_infof("particle sprite generation: %d", SDL_GetTicks() - ss);
    ss = SDL_GetTicks();
#endif

    this->update_entities();

#ifdef PROFILING
    tms_infof("update: %d", SDL_GetTicks() - ss);
    ss = SDL_GetTicks();
#endif

    for (std::set<entity*>::iterator i = this->u_effects.begin();
            i != this->u_effects.end(); i++) {
        if (enable_culling) {
            if ((*i)->cull_effects_method == CULL_EFFECTS_BY_POSITION) {
                b2Vec2 p = (*i)->get_position();

                if (!(p.x > aabb.lowerBound.x && p.x < aabb.upperBound.x
                            && p.y > aabb.lowerBound.y && p.y < aabb.upperBound.y)) {
                    continue;
                }
            } else if ((*i)->cull_effects_method == CULL_EFFECTS_DISABLE) {
            } else {
                if (tms_graph_is_entity_culled(this->graph, *i))
                    continue;
            }
        }

        (*i)->update_effects();
    }

    if (W->is_playing() && W->is_adventure()) {
        entity *last_host = 0;
        float y_offset = 0.f;
        tvec3 color;

        if (G->caveview_size >= 1.f) {
            color = (tvec3){1.f, 1.f, 1.f};
        } else {
            color = (tvec3){0.f, 0.f, 0.f};
        }

        for (std::multimap<entity*, struct loot>::iterator it = this->loots.begin();
                it != this->loots.end();) {
            entity *host = it->first;
            struct loot &l = it->second;

            if (!host) {
                continue;
            }

            if (host != last_host) {
                y_offset = 0.f;
            }

            l.scale += _tms.dt * 4.f;
            l.life -= _tms.dt * 2.f;

            if (l.scale >= 1.f) {
                l.scale = 1.f;
            }

            b2Vec2 pos = host->get_position();
            pos.y += y_offset + host->height + 0.25f;

            char tmp[64];
            snprintf(tmp, 63, "+%d %s", l.num, l.name);

            textbuffer::add_text(tmp, font::xlarge,
                    pos.x,
                    pos.y,
                    host->get_layer()*LAYER_DEPTH + LAYER_DEPTH/2.f+0.01,
                    color.r, color.g, color.b, fminf(l.scale, l.life),
                    0.0045 * powf(.0001f+fminf(l.scale, l.life), .125f)); // LOOT_SCALE

            last_host = host;

            y_offset += (0.3f * fminf(l.scale, l.life));

            if (l.life <= 0.f) {
                this->loots.erase(it++);
            } else {
                ++ it;
            }
        }
    }

    this->render_tt();

#ifdef PROFILING
    tms_infof("effects: %d", SDL_GetTicks() - ss);
    ss = SDL_GetTicks();
#endif

    tms_assertf((ierr = glGetError()) == 0, "gl error %d in game::render before upload", ierr);
    display::upload();
    ledbuffer::upload();
    spritebuffer::upload();
    fluidbuffer::upload();
    linebuffer::upload();
    textbuffer::upload();
    rope::upload_buffers();
    cable::upload_buffers();
    pixel::upload_buffers();
    tpixel::upload_buffers();
    polygon::upload_buffers();
    plant::upload_buffers();
    tms_assertf((ierr = glGetError()) == 0, "gl error %d in game::render after upload", ierr);

    GLenum err;
    do {
        err = glGetError();
    } while (err != GL_NO_ERROR);

#ifdef PROFILING
    tms_infof("upload: %d", SDL_GetTicks() - ss);
    ss = SDL_GetTicks();
#endif

    glViewport(0,0,_tms.opengl_width, _tms.opengl_height);

    /* create shadow map */
    glDisable(GL_BLEND);

    tms_assertf((ierr = glGetError()) == 0, "gl error %d in game::render before shadow render", ierr);
    if (settings["enable_shadows"]->v.b) {
        if (!this->state.abo_architect_mode || !W->is_paused()) {
            P.best_variable_in_the_world2 = 0;
            this->gi_graph->render(this->gi_cam, this);
        } else {
            tms_fb_bind(tms_pipeline_get_framebuffer(1));
            glClearColor(1.f, 1.f, 1.f, 1.f);
            glClear(GL_COLOR_BUFFER_BIT);
            tms_fb_unbind(tms_pipeline_get_framebuffer(1));
        }
    }
    tms_assertf((ierr = glGetError()) == 0, "gl error %d in game::render after shadow render", ierr);

    //tms_debugf("cam before render ao %f", this->cam->_position.z);
    if (settings["enable_ao"]->v.b) {
        glDisable(GL_BLEND);
        this->ao_graph->render(this->ao_cam, this);

        if (tms_pipeline_get_framebuffer(3)->width == 512)
            tms_fb_swap_blur5x5(tms_pipeline_get_framebuffer(3));
        else
            tms_fb_swap_blur3x3(tms_pipeline_get_framebuffer(3));
    }

    if (!_tms.use_gles && settings["postprocess"]->v.b)
        tms_fb_bind(this->main_fb);

    tms_assertf((ierr = glGetError()) == 0, "gl error %d in game::render after shadow/ao", ierr);
    glDisable(GL_BLEND);

    //ss = SDL_GetTicks();

    tms_assertf((ierr = glGetError()) == 0, "gl error %d in game::render before bg", ierr);

    if (!_tms.use_gles && settings["gamma_correct"]->v.b && !settings["postprocess"]->v.b)
        glEnable(GL_FRAMEBUFFER_SRGB);

    if (this->state.abo_architect_mode) {
        glClearColor(.25f, .25f, .25f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    } else {
        if (material_factory::background_id == BG_COLORED_SPACE) {
            glClearColor(this->state.bg_color.r, this->state.bg_color.g, this->state.bg_color.b, 1.f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        } else if (material_factory::background_id == BG_SPACE || material_factory::background_id == BG_OUTDOOR) {
            tms_assertf((ierr = glGetError()) == 0, "gl error %d in game::render before space bg", ierr);
            glClearColor(4.f/255.f, 11.f/255.f, 19/255.f, 1.f);
            glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
            glDisable(GL_CULL_FACE);
            glDisable(GL_DEPTH_TEST);

            if (material_factory::background_id == BG_OUTDOOR) {
                /* responsible for rendering the horizon background */
                struct tms_fb fb;
                fb.num_textures = 1;
                fb.toggle = 0;
                fb.fb_texture[0][0] = tex_bg->gl_texture;

                float pp = 0.f;

                float ff = this->cam->far;
                float nn = this->cam->near;

#define HORIZON_DIST 2500.f

                this->cam->far = HORIZON_DIST+1.f;
                this->cam->near = -10.f;
                this->cam->calculate();

                tvec3 v1 = tms_camera_project(this->cam, this->cam->_position.x, 0.f, -HORIZON_DIST);
                pp = (v1.y / this->cam->height) * 2.f;

                /* reset camera */
                this->cam->far = ff;
                this->cam->near = nn;
                this->cam->calculate();

                tms_program_bind(trans_program);
                glUniform2f(trans_program_shift_loc, 0.f,0* (this->cam->_position.y > 0? .01f : .075f) * this->cam->_position.y);
                glUniform2f(trans_program_pos_loc, 0.f, pp);
                glUniform2f(trans_program_poslower_loc, 0.f, pp);
                tms_fb_render(&fb, trans_program);

                //fb.fb_texture[0][0] = tex_bedrock->gl_texture;

                v1 = tms_camera_project(this->cam, this->cam->_position.x, -1.f, -.5f);
                float pp2 = (v1.y / this->cam->height) * 2.f;
                tms_program_bind(trans_program);
                //glUniform2f(trans_program_shift_loc, 0.f, 0.f);
                glUniform2f(trans_program_shift_loc, 0.f,0* (this->cam->_position.y > 0? .01f : .075f) * this->cam->_position.y);
                glUniform2f(trans_program_pos_loc, 0.f, pp2-2.f);
                glUniform2f(trans_program_poslower_loc, 0.f, pp);
                tms_fb_render(&fb, trans_program);
            } else {
                tms_texture_render(tex_bg);
            }
        } else {
            glClearColor(.05f, .05f, .05f, 1.f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        }
    }

    tms_assertf((ierr = glGetError()) == 0, "gl error %d in game::render after bg", ierr);

    {
        /* render primary scene */
        P.best_variable_in_the_world = 0;
        tms_graph_set_sort_callback(this->graph, TMS_SORT_PRIO, render_next_prio);
        this->graph->post_fn = post_fn;

        //tms_debugf("cam before render %f", this->cam->_position.z);
        this->graph->render(this->cam, this);
    }

    glDepthMask(true);

    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

    if (W->is_paused() || W->level.type == LCAT_ADVENTURE) {
        this->render_selected_entity();
    }

    this->render_highlighted();
    this->render_trails();

    glEnable(GL_DEPTH_TEST);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    if (!W->is_paused() && W->level.type == LCAT_ADVENTURE) {
        adventure::render();
    }

    glDisable(GL_BLEND);
    glEnable(GL_CULL_FACE);

    {
        /* render half-hidden layers */
        P.best_variable_in_the_world = 1337;
        tms_graph_set_sort_callback(this->graph, TMS_SORT_PRIO, render_hidden_prio);
        this->graph->post_fn = 0;
        this->graph->render(this->cam, this);
        tms_assertf((ierr = glGetError()) == 0, "gl error %d after graph render cam?", ierr);
    }

    {
        /* render foreground */
        P.best_variable_in_the_world = 0;
        P.best_variable_in_the_world3 = 1;
        tms_graph_set_sort_callback(this->graph, TMS_SORT_PRIO, render_foreground);
        this->graph->render(this->cam, this);
        P.best_variable_in_the_world = 1337;
        P.best_variable_in_the_world3 = 0;
        tms_assertf((ierr = glGetError()) == 0, "gl error %d after render foreground", ierr);
    }

    if (!_tms.use_gles && settings["gamma_correct"]->v.b && !settings["postprocess"]->v.b) {
        glDisable(GL_FRAMEBUFFER_SRGB);
    }

    glDepthMask(true);
    glDisable(GL_BLEND);
    glDisable(GL_DEPTH_TEST);

    tms_ddraw_set_matrices(this->dd, this->cam->view, this->cam->projection);
    //tms_ddraw_line3d(this->dd, 0, 0, 0, this->light.x*2.f, this->light.y*2.f, this->light.z*2.f);

    if (!_tms.use_gles && settings["postprocess"]->v.b) {
        tms_fb_unbind(this->main_fb);
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_BLEND);

#if 0
        if (settings["gamma_correct"]->v.b)
            tms_fb_render(this->main_fb, prg_output);
        else
            tms_fb_render(this->main_fb, _tms_fb_copy_program);
#endif

        if (settings["gamma_correct"]->v.b)
            glEnable(GL_FRAMEBUFFER_SRGB);

        tms_fb_render(this->main_fb, _tms_fb_copy_program);

        if (settings["gamma_correct"]->v.b)
            glDisable(GL_FRAMEBUFFER_SRGB);

        glBindTexture(GL_TEXTURE_2D, this->main_fb->fb_texture[0][0]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        if (glad_glGenerateMipmap)
            glGenerateMipmap(GL_TEXTURE_2D);
        else
            glGenerateMipmapEXT(GL_TEXTURE_2D);

        glDisable(GL_BLEND);
        glDisable(GL_DEPTH_TEST);
        tms_fb_render_to(this->main_fb, this->bloom_fb, prg_brightpass);
        tms_fb_swap_blur5x5(this->bloom_fb);
        glEnable(GL_BLEND);
        glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE);
        glDisable(GL_DEPTH_TEST);

        glBindTexture(GL_TEXTURE_2D, this->bloom_fb->fb_texture[this->bloom_fb->toggle][0]);
        if (glad_glGenerateMipmap)
            glGenerateMipmap(GL_TEXTURE_2D);
        else
            glGenerateMipmapEXT(GL_TEXTURE_2D);

        for (int x=0; x<7; x++) {
            glBlendColor(1.f, 1.f, 1.f, .05f);
            glBindTexture(GL_TEXTURE_2D, this->bloom_fb->fb_texture[this->bloom_fb->toggle][0]);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, x);
            tms_fb_render(this->bloom_fb, _tms_fb_copy_program);
        }
        glBlendColor(1.f, 1.f, 1.f, 1.f);
        glBindTexture(GL_TEXTURE_2D, this->bloom_fb->fb_texture[this->bloom_fb->toggle][0]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        glDisable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable(GL_DEPTH_TEST);
        tms_assertf((ierr = glGetError()) == 0, "gl error %d after postprocess", ierr);
    }

    glDisable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    this->render_controls_help();

    this->render_gui();

    tms_assertf((ierr = glGetError()) == 0, "gl error %d after gui", ierr);
    if (this->state.sandbox && W->is_paused()) {
        glScissor(0, 0, _tms.window_width - this->get_menu_width(), _tms.window_height);
        glEnable(GL_SCISSOR_TEST);
    }

    glEnable(GL_BLEND);
    tms_ddraw_set_matrices(this->dd, this->cam->view, this->cam->projection);

    if (W->is_paused() || W->level.type == LCAT_ADVENTURE) {
        if (this->get_mode() == GAME_MODE_SELECT_SOCKET) {
            // FIXME
            this->render_socksel();
        }
    }

    glBindTexture(GL_TEXTURE_2D, gui_spritesheet::atlas->texture.gl_texture);

    glDisable(GL_DEPTH_TEST);

    if (W->is_paused() || W->level.type == LCAT_ADVENTURE) {
        if (this->get_mode() == GAME_MODE_SELECT_CONN_TYPE)
            this->render_conn_types();
        else
            this->render_connections();
    }

    if (W->is_paused()) {
        if (this->get_mode() == GAME_MODE_CONN_EDIT)
            this->render_existing_connections();

        this->render_selected_connection();
    }

    if (W->is_playing() && W->is_adventure()) {
        this->pending_activators.clear();

        if (adventure::player) {
            for (std::set<activator*>::iterator it = W->activators.begin();
                    it != W->activators.end(); it++) {
                activator *act = *it;
                if (act->active
                        && adventure::player->activators.find(act) != adventure::player->activators.end()
                        && adventure::player->cur_activator != act) {
                    this->pending_activators.push_back(act);
                }
            }

            std::sort(this->pending_activators.begin(), this->pending_activators.end(), game_sorter::distance_to_creature(adventure::player));
        }

        this->render_activators();
    }

    if (this->get_mode() != GAME_MODE_CONN_EDIT && W->is_paused() && this->state.sandbox) {
        tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.0f, 1.0f);
        for (std::set<entity*>::iterator it = this->locked.begin();
                it != this->locked.end(); ++it) {
            b2Vec2 p = (*it)->get_position();

            tms_ddraw_sprite_r(this->dd, gui_spritesheet::get_sprite(S_LOCK),
                    p.x, p.y,
                    .375f, .375f,
                    cos((double)_tms.last_time/100000.) * 8.f
                    );
        }
    }

    if (this->get_mode() == GAME_MODE_DEFAULT && W->is_adventure() && adventure::player) {
        if (!(settings["tutorial"]->v.u32 & TUTORIAL_REPAIR_STATION)) {
            this->render_help_icon(W->repair_stations, OFFS_REPAIR_STATION);
        }
    }

#ifndef SCREENSHOT_BUILD
    if (this->get_mode() != GAME_MODE_CONN_EDIT && ((W->is_puzzle() && W->is_paused()) || (!W->is_puzzle() && !W->is_paused()))) {
        this->render_starred();
    }
#endif

    if (!W->is_paused() && this->state.sandbox && !this->state.test_playing) {
        for (std::set<er*>::iterator i = this->errors.begin();
                i != this->errors.end(); i++) {
            er *error = static_cast<er*>(*i);

            if (error->e && error->alpha > 0.1f) {
                tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.0f, error->alpha);

                error->alpha -= _tms.dt * this->get_time_mul();
                entity *e = error->e;

                b2Vec2 p = e->get_position();

                tms_ddraw_sprite_r(this->dd, gui_spritesheet::get_sprite(S_ERROR),
                        p.x,
                        p.y,
                        .35f, .35f, cos((double)_tms.last_time/100000.) * 4.f * this->get_time_mul());
            }
        }
    }

    for (int x=0; x<NUM_CA; x++) {
        if (this->ca[x].life >= 0.f && this->ca[x].life <= 1.f) {
            tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.f, 1.f);
            tms_ddraw_lcircle(this->dd, this->ca[x].p.x, this->ca[x].p.y, .75f*this->ca[x].life, .75f*this->ca[x].life);
            tms_ddraw_set_color(this->dd, 0.0f, 0.0f, 0.0f, 1.f);
            tms_ddraw_lcircle(this->dd, this->ca[x].p.x, this->ca[x].p.y, .72f*this->ca[x].life, .72f*this->ca[x].life);
            this->ca[x].life += _tms.dt*6.f * this->ca[x].dir;
        }
    }
    tms_assertf((ierr = glGetError()) == 0, "gl error %d after something3", ierr);

    if (this->get_mode() == GAME_MODE_QUICK_PLUG) {
        if (this->selection.e != 0) {
            if (this->selection.e->flag_active(ENTITY_IS_EDEVICE)) {
                tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.0f, 1.f);
                b2Vec2 p1 = this->selection.e->get_position();
                b2Vec2 p2 = this->sel_p_ent ? this->sel_p_ent->get_position() : b2Vec2(touch_quickplug_pos.x, touch_quickplug_pos.y);

                float z = this->sel_p_ent ? this->sel_p_ent->get_layer() : this->selection.e->get_layer();

                tms_ddraw_line3d(this->dd, p1.x, p1.y, this->selection.e->get_layer()*LAYER_DEPTH,
                        p2.x, p2.y, z*LAYER_DEPTH);
            }
        }
    }

    pscreen::render();

    if (W->is_paused()) {
        if (this->get_mode() == GAME_MODE_MULTISEL) {
            if (this->multi.box_select == 0 && !this->selection.m) {
                if (this->multi.import) {
                    tms_ddraw_set_color(this->dd, 0.2f, 0.2f, 0.2f, 0.5f);
                    tms_ddraw_square(this->dd,
                            this->multi.cursor.x,
                            this->multi.cursor.y,
                            this->multi.cursor_size.x, this->multi.cursor_size.y);

                    tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.0f, 0.5f);
                    tms_ddraw_lsquare(this->dd,
                            this->multi.cursor.x,
                            this->multi.cursor.y,
                            this->multi.cursor_size.x, this->multi.cursor_size.y);
                }
            } else if (this->multi.box_select >= 3) {
                tms_ddraw_set_color(this->dd, 0.0f, 1.0f, 1.0f, 1.0f);

                tms_ddraw_line(this->dd,
                        begin_box_select.x, begin_box_select.y,
                        begin_box_select.x, end_box_select.y);
                tms_ddraw_line(this->dd,
                        begin_box_select.x, begin_box_select.y,
                        end_box_select.x, begin_box_select.y);
                tms_ddraw_line(this->dd,
                        end_box_select.x, end_box_select.y,
                        begin_box_select.x, end_box_select.y);
                tms_ddraw_line(this->dd,
                        end_box_select.x, end_box_select.y,
                        end_box_select.x, begin_box_select.y);

                tms_ddraw_circle(this->dd, begin_box_select.x, begin_box_select.y, .1f, .1f);
                tms_ddraw_circle(this->dd, end_box_select.x, end_box_select.y, .1f, .1f);
            }
        }

        if (this->selection.e != 0) {
            if (this->selection.e->flag_active(ENTITY_IS_EDEVICE)) {
                edevice *ed = this->selection.e->get_edevice();

                if (this->get_mode() == GAME_MODE_QUICK_PLUG) {
                    tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.0f, 1.f);
                    b2Vec2 p1 = this->selection.e->get_position();
                    b2Vec2 p2 = this->sel_p_ent ? this->sel_p_ent->get_position() : b2Vec2(touch_quickplug_pos.x, touch_quickplug_pos.y);

                    float z = this->sel_p_ent ? this->sel_p_ent->get_layer() : this->selection.e->get_layer();

                    tms_ddraw_line3d(this->dd, p1.x, p1.y, this->selection.e->get_layer()*LAYER_DEPTH,
                            p2.x, p2.y, z*LAYER_DEPTH);
                }

                for (int x=0; x<ed->num_s_in; ++x) {
                    if (ed->s_in[x].p) {
                        plug_base *p = ed->s_in[x].p->get_other();

                        if (p) {
                            tms_ddraw_set_color(this->dd, 1.0f, 0.7f, 0.7f, 0.95f);
                            b2Vec2 p1 = ed->s_in[x].p->get_position();
                            b2Vec2 p2 = p->get_position();
                            tms_ddraw_line3d(this->dd, p1.x, p1.y, CABLE_Z + this->selection.e->get_layer()*LAYER_DEPTH, p2.x, p2.y, CABLE_Z + this->selection.e->get_layer()*LAYER_DEPTH);
                        }
                    }
                }
                for (int x=0; x<ed->num_s_out; ++x) {
                    if (ed->s_out[x].p) {
                        plug_base *p = ed->s_out[x].p->get_other();

                        if (p) {
                            tms_ddraw_set_color(this->dd, 0.7f, 1.0f, 0.7f, 0.95f);
                            b2Vec2 p1 = ed->s_out[x].p->get_position();
                            b2Vec2 p2 = p->get_position();
                            tms_ddraw_line3d(this->dd, p1.x, p1.y, CABLE_Z + this->selection.e->get_layer()*LAYER_DEPTH, p2.x, p2.y, CABLE_Z + this->selection.e->get_layer()*LAYER_DEPTH);
                        }
                    }
                }
            }

#ifdef DEBUG
            if (settings["debug"]->v.b && this->selection.e->gr) {
                /* draw the group centroid */
                tms_ddraw_set_color(this->dd, 1.0f, 0.0f, 0.0f, 1.0f);
                tms_ddraw_circle(this->dd, this->selection.e->gr->get_position().x, this->selection.e->gr->get_position().y, .25f, .25f);
            }
#endif

            switch (this->selection.e->g_id) {
                case O_CURSOR_FIELD: {
                    // Draw click area for Cursor field object

                    cursorfield *g = static_cast<cursorfield*>(this->selection.e);
                    tms_ddraw_set_color(this->dd, 0.0f, 0.0f, 1.0f, 1.0f);

                    b2PolygonShape sh;

                    b2Vec2 vertices[4] = {
                        g->local_to_world(b2Vec2(g->properties[0].v.f, g->properties[1].v.f), 0),
                        g->local_to_world(b2Vec2((g->properties[2].v.f), g->properties[1].v.f), 0),
                        g->local_to_world(b2Vec2((g->properties[2].v.f), (g->properties[3].v.f)), 0),
                        g->local_to_world(b2Vec2(g->properties[0].v.f, (g->properties[3].v.f)), 0)
                    };
                    for (int x=0; x<4; ++x) {
                        tms_ddraw_line3d(this->dd,
                                vertices[x].x, vertices[x].y, this->selection.e->get_layer()*LAYER_DEPTH,
                                vertices[(x+1)%4].x, vertices[(x+1)%4].y, this->selection.e->get_layer()*LAYER_DEPTH
                            );
                    }
                } break;
                case O_FLUID: {
                    // Draw bounding box for fluid particles to spawn

                    fluid *g = static_cast<fluid*>(this->selection.e);
                    tms_ddraw_set_color(this->dd, 0.0f, 0.0f, 1.0f, 1.0f);
                    b2Vec2 vertices[4] = {
                        g->local_to_world(b2Vec2(g->properties[0].v.f, g->properties[1].v.f), 0),
                        g->local_to_world(b2Vec2(-(g->properties[0].v.f), g->properties[1].v.f), 0),
                        g->local_to_world(b2Vec2(-(g->properties[0].v.f), -(g->properties[1].v.f)), 0),
                        g->local_to_world(b2Vec2(g->properties[0].v.f, -(g->properties[1].v.f)), 0)
                    };
                    for (int x=0; x<4; ++x) {
                        tms_ddraw_line3d(this->dd,
                                vertices[x].x, vertices[x].y, this->selection.e->get_layer()*LAYER_DEPTH,
                                vertices[(x+1)%4].x, vertices[(x+1)%4].y, this->selection.e->get_layer()*LAYER_DEPTH
                            );
                    }
                } break;
                case O_SHAPE_EXTRUDER: {
                    // Draw bounding box of Shape extruder

                    shape_extruder *g = static_cast<shape_extruder*>(this->selection.e);
                    if (g->conn_ll) {
                        tms_ddraw_set_color(this->dd, 0.0f, 0.0f, 1.0f, 1.0f);
                        composable *other = static_cast<composable*>(g->c.o);

                        if (other) {
                            float w = other->get_width();
                            float h = other->height;

                            b2PolygonShape sh;

                            b2Vec2 vertices[4] = {
                                other->local_to_world(b2Vec2(w+g->properties[0].v.f, h+g->properties[1].v.f), 0),
                                other->local_to_world(b2Vec2(-(w+g->properties[2].v.f), h+g->properties[1].v.f), 0),
                                other->local_to_world(b2Vec2(-(w+g->properties[2].v.f), -(h+g->properties[3].v.f)), 0),
                                other->local_to_world(b2Vec2(w+g->properties[0].v.f, -(h+g->properties[3].v.f)), 0)
                            };
                            for (int x=0; x<4; ++x) {
                                tms_ddraw_line3d(this->dd,
                                        vertices[x].x, vertices[x].y, this->selection.e->get_layer()*LAYER_DEPTH,
                                        vertices[(x+1)%4].x, vertices[(x+1)%4].y, this->selection.e->get_layer()*LAYER_DEPTH
                                    );
                            }
                        }
                    }
                } break;
                case O_PROXIMITY_SENSOR: {
                    // Draw range of proximity sensor

                    proximitysensor *sensor = static_cast<proximitysensor*>(this->selection.e);
                    static const int32 num_v = 4;
                    tms_ddraw_set_color(this->dd, 1.0f, 0.0f, 0.0f, 0.8f);
                    b2Vec2 vertices[num_v];

                    for (int x=0; x<num_v; ++x) {
                        vertices[x] = this->selection.e->local_to_world(sensor->get_sensor_shape().GetVertex(x), 0);
                    }

                    for (int x=0; x<num_v; ++x) {
                        tms_ddraw_line3d(this->dd,
                                vertices[x].x, vertices[x].y, this->selection.e->get_layer()*LAYER_DEPTH,
                                vertices[(x+1)%num_v].x, vertices[(x+1)%num_v].y, this->selection.e->get_layer()*LAYER_DEPTH
                            );
                    }
                } break;
                case O_ID_FIELD:
                case O_OBJECT_FIELD:
                case O_TARGET_SETTER: {
                    objectfield *of = static_cast<objectfield*>(this->selection.e);

                    tms_ddraw_set_color(this->dd, 1.0f, 0.0f, 0.0f, 0.8f);
                    static const int32 num_v = 4;
                    b2Vec2 vertices[num_v];

                    for (int x=0; x<num_v; ++x) {
                        vertices[x] = this->selection.e->local_to_world(of->sensor_shape.GetVertex(x), 0);
                    }

                    for (int x=0; x<num_v; ++x) {
                        tms_ddraw_line3d(this->dd,
                                vertices[x].x, vertices[x].y, this->selection.e->get_layer()*LAYER_DEPTH,
                                vertices[(x+1)%num_v].x, vertices[(x+1)%num_v].y, this->selection.e->get_layer()*LAYER_DEPTH
                            );
                    }
                } break;

                case O_DRAGFIELD: {
                    // Draw

                    dragfield *df = static_cast<dragfield*>(this->selection.e);

                    tms_ddraw_set_color(this->dd, 1.0f, 0.0f, 0.0f, 0.8f);

                    tms_ddraw_lcircle(this->dd,
                            df->get_position().x, df->get_position().y,
                            df->sensor_shape.m_radius, df->sensor_shape.m_radius);
                } break;
                case O_FAN: { // "Oh fan!"
                    // Draw lines showing the exhaust of selected Fan object

                    fan *f = static_cast<fan*>(this->selection.e);

                    #define NUM_RAYS    5
                    #define RAY_LENGTH  10.f
                    #define FAN_WIDTH   1.f
                    #define FAN_LINE_OFFSET (((x / ((float)NUM_RAYS - 1.f)) * FAN_WIDTH) - (FAN_WIDTH / 2.f))

                    tms_ddraw_set_color(this->dd, 1.0f, 0.0f, 0.0f, 0.4f);
                    for (int x=0; x<NUM_RAYS; x++) {
                        float a = f->get_angle();
                        b2Vec2 angle;
                        tmath_sincos(a, &angle.y, &angle.x);

                        b2Vec2 r = f->get_position();
                        b2Vec2 dir = f->local_to_world(b2Vec2(0.f,  RAY_LENGTH), 0);

                        r.x   += angle.x * FAN_LINE_OFFSET;
                        r.y   += angle.y * FAN_LINE_OFFSET;
                        dir.x += angle.x * FAN_LINE_OFFSET;
                        dir.y += angle.y * FAN_LINE_OFFSET;

                        float l = f->get_layer()*LAYER_DEPTH;

                        tms_ddraw_line3d(this->dd, r.x, r.y, l, dir.x, dir.y, l);
                    }
                } break;
            }
        }
    }
    tms_assertf((ierr = glGetError()) == 0, "gl error %d after something2", ierr);

    if (this->selection.e != 0) {
        entity *e = this->selection.e;
        if (W->is_paused() && e->flag_active(ENTITY_IS_RESIZABLE)) {
            this->render_shape_resize();
        }
    }

    if (this->state.sandbox && this->state.edev_labels && settings["render_edev_labels"]->v.b) {
        this->render_edev_labels();
        tms_assertf((ierr = glGetError()) == 0, "gl error %d after edev labels", ierr);
    }

    glBindTexture(GL_TEXTURE_2D, 0);

    std::vector<entity*> hp_occurences;

    for (int x=0; x<NUM_HP; x++) {
        struct hp *h = &this->hps[x];
        if (h->time <= 0.f || !h->e) {
            continue;
        } else if (h->time <= 0.25f && h->regen && h->percent < 1.f) {
            h->time = 0.25f;
            h->e->entity_health += 0.25f;
            h->percent = tclampf(h->e->entity_health / ENTITY_MAX_HEALTH, 0.f, 1.f);
        }

        h->time -= _tms.dt*.2f;

        float y_offset = h->e->height*2.f + (BAR_Y_OFFSET * std::count(hp_occurences.begin(), hp_occurences.end(), h->e));
        float alpha = tclampf(h->time / .25f, 0.f, 1.f) * .75f;

        this->draw_entity_bar(h->e, h->percent, y_offset, h->color, alpha);

        hp_occurences.push_back(h->e);

        if (h->e->flag_active(ENTITY_IS_CREATURE)) {
            if (static_cast<creature*>(h->e)->creature_flag_active(CREATURE_IS_ZOMBIE)) {
                h->color = (tvec3){0.4f, 0.4f, .4f};
            }
        }
    }

//#define SHOW_MOOD_DATA
#define SHOW_RAYCASTS

#if defined(SHOW_RAYCASTS) && defined(DEBUG)
    if (settings["debug"]->v.b) {
        std::set<struct game_debug_line*>::iterator it = this->debug_lines.begin();
        while (it != this->debug_lines.end()) {
            struct game_debug_line *gdl = *it;

            gdl->life -= G->timemul(8);
            float a = tclampf((gdl->life/1000.f)*.9f, 0.f, 1.f);

            tms_ddraw_set_color(this->dd, gdl->r, gdl->g, gdl->b, a);
            tms_ddraw_line(this->dd, gdl->x1, gdl->y1, gdl->x2, gdl->y2);

            if (gdl->life < 0) {
                this->debug_lines.erase(it++);
            } else {
                ++it;
            }
        }
    } else {
        this->debug_lines.clear();
    }
#endif

#ifdef SHOW_MOOD_DATA
    if (!W->is_paused()) {
        for (std::map<uint32_t, entity*>::iterator it = W->all_entities.begin();
                it != W->all_entities.end(); ++it) {
            if (it->second->flag_active(ENTITY_IS_ROBOT)) {
                robot_base *rob = static_cast<robot_base*>(it->second);

                float y = 1.65f;
                tvec3 color;
                for (int x=0; x<NUM_MOODS; ++x) {
                    switch (x) {
                        case MOOD_ANGER:
                            color = tvec3f(.95f, .33f, .33f);
                            break;

                        case MOOD_BRAVERY:
                            color = tvec3f(.33f, .33f, .95f);
                            break;

                        case MOOD_FEAR:
                            color = tvec3f(.95f, .95f, .33f);
                            break;

                        default:
                            color = tvec3f(.3f, .3f, .3f);
                            break;
                    }

                    this->draw_entity_bar(rob, rob->mood.get(x), y, color);
                    y += BAR_Y_OFFSET;
                }

                {
                    if (rob->roam) {
                        float v = tclampf((float)rob->logic_timer/rob->logic_timer_max, 0.f, 1.f);
                        color = tvec3f(.33f, .95f, .95f);
                        this->draw_entity_bar(rob, v, y, color);
                        y += BAR_Y_OFFSET;
                    }
                }
            }
        }
    }
#endif

    tms_ddraw_set_matrices(this->dd, this->cam->view, this->cam->projection);
    tms_assertf((ierr = glGetError()) == 0, "gl error %d after hp rendering", ierr);

#if 0
    if (this->state.waiting && !this->state.finished) {
        glEnable(GL_BLEND);
        tms_ddraw_set_color(this->get_surface()->ddraw, 0.f, 0.f, 0.f, .25f);
        tms_ddraw_square(this->get_surface()->ddraw, _tms.window_width/2.f, _tms.window_height/2.f, _tms.window_width, _tms.window_height);
    }
#endif

    if (!this->state.ending) {
        if (this->state.fade > 0.f) {
            glEnable(GL_BLEND);
            tms_ddraw_set_color(this->get_surface()->ddraw, 0.f, 0.f, 0.f, this->state.fade);
            tms_ddraw_square(this->get_surface()->ddraw, _tms.window_width/2.f, _tms.window_height/2.f, _tms.window_width, _tms.window_height);
            this->state.fade -= _tms.dt * GAME_FADE_SPEED/2.f;
        }
    } else {
        if (this->state.fade >= 1.f) {
            switch (this->state.end_action) {
                default: case GAME_END_PROCEED:
                    this->proceed();
                    break;

                case GAME_END_WARP:
                    P.add_action(ACTION_WARP, (void*)(uintptr_t)this->state.end_warp);
                    break;
            }
        }
        glEnable(GL_BLEND);
        tms_ddraw_set_color(this->get_surface()->ddraw, 0.f, 0.f, 0.f, this->state.fade);
        tms_ddraw_square(this->get_surface()->ddraw, _tms.window_width/2.f, _tms.window_height/2.f, _tms.window_width, _tms.window_height);
        this->state.fade += _tms.dt * GAME_FADE_SPEED;
    }
    tms_assertf((ierr = glGetError()) == 0, "gl error %d after fade stuff", ierr);

    glDisable(GL_BLEND);

    if (this->state.sandbox && W->is_paused()) {
        glDisable(GL_SCISSOR_TEST);
        glScissor(0, 0, _tms.window_width, _tms.window_height);
    }
    tms_assertf((ierr = glGetError()) == 0, "gl error %d after something", ierr);

#ifdef DEBUG
    if (settings["debug"]->v.b) {
        W->draw_debug(this->cam);
    }
#endif

    if (settings["enable_shadows"]->v.b && settings["swap_shadow_map"]->v.b
            && tms_pipeline_get_framebuffer(1)) {
        tms_fb_swap(tms_pipeline_get_framebuffer(1), 0);
        tms_assertf((ierr = glGetError()) == 0, "gl error %d after fb swap shadows", ierr);
    }

    /*
    if (settings["enable_ao"]->v.b && settings["swap_ao_map"]->v.b
            && tms_pipeline_get_framebuffer(3))
        tms_fb_swap(tms_pipeline_get_framebuffer(3), 0);
        */

    if (this->main_fb) {
        tms_fb_swap(this->main_fb, 0);
        tms_assertf((ierr = glGetError()) == 0, "gl error %d after fb swap main", ierr);
    }

#ifdef PROFILING
    tms_infof("render: %d", SDL_GetTicks() - ss);
#endif

    tms_assertf((ierr = glGetError()) == 0, "gl error %d in game::render end", ierr);

    this->unset_caveview_zoom_limits();

    return T_OK;
}

void
game::add_error(entity *e, uint8_t error_type/*=ERROR_NONE*/, const char *message/*=0*/)
{
    std::set<er*>::iterator it = this->errors.begin();
    for (; it != this->errors.end(); ++it) {
        er *error = static_cast<er*>(*it);
        if (error->e == e) {
            error->alpha = 0.9f;
            return;
        }
    }

    er *error = new er();
    error->e = e;
    error->type = error_type;
    if (message) error->message = strdup(message);
    this->errors.insert(error);

    if (!this->wdg_error->surface && settings["render_gui"]->is_true()) {
        this->wdg_error->add();

        this->wm->rearrange();
    }
}

void
game::clear_errors()
{
    std::set<er*>::iterator it = this->errors.begin();
    for (; it != this->errors.end(); ++it) {
        delete *it;
    }

    this->errors.clear();
}

void
game::reselect()
{
    this->selection.select(this->selection.e, this->selection.b, this->selection.offs, this->selection.frame, true);
}

void
game::render_tt()
{
    for (int x=0; x<MAX_TUTORIAL_TEXTS; x++) {
        if (this->tt[x].life <= 0.f)
            return;

        b2Vec2 p;
        if (this->tt[x].e)
            p = this->tt[x].e->get_position() + this->tt[x].pos;
        else
            p = b2Vec2(this->cam->_position.x, this->cam->_position.y) + this->tt[x].pos;


        const char *tutorial_text = tutorial_texts[this->tt[x].what];
        if (this->tt[x].what == 4) {
            // Special case for this tutorial text, different phrasing depending on control scheme
            if (settings["touch_controls"]->v.b)
                tutorial_text = tutorial_text_4th_mobile;
            else
                tutorial_text = tutorial_text_4th_pc;
        }

        textbuffer::add_text(tutorial_text, font::medium,
                p.x,p.y,
                2.9f,
                1.f, 1.f, 1.f, this->tt[x].life < .25f ? (this->tt[x].life/.25f) : 1.f,
                .015,
                ALIGN_CENTER,
                ALIGN_CENTER,
                true
                );
        this->tt[x].life -= _tms.dt;
    }
}

/**
 * Render any pending activators.
 * The pending activators will be cleared just before the pending activators
 * are refreshed.
 *
 * This currently happens in game::render()
 **/
void
game::render_activators(void)
{
    if (!this->pending_activators.empty()) {
        float mv[16];
        float p[16];
        tmat4_copy(p, this->cam->projection);

        int layer = 0;
        if (adventure::player) {
            layer = adventure::player->get_layer();
        }

        int x = 0;

        for (std::deque<activator*>::iterator it = this->pending_activators.begin();
                it != this->pending_activators.end(); ++it) {
            activator* act = *it;

            tmat4_copy(mv, this->cam->view);
            tmat4_translate(mv, 0, 0, layer*LAYER_DEPTH+((LAYER_DEPTH/2.f)));
            tms_ddraw_set_matrices(this->dd, mv, p);

            b2Vec2 activator_pos = act->get_activator_pos();
            float radius = act->get_activator_radius();

            tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.0f, .5f + .35f * cos((double)_tms.last_time/200000.));
            tms_ddraw_circle(this->dd,
                    activator_pos.x, activator_pos.y,
                    radius * 0.2f, radius * 0.2f);

            tms_ddraw_set_color(this->dd, 0.2f, 0.2f, 0.2f, .5f + .35f * cos((double)_tms.last_time/200000.));
            tms_ddraw_circle(this->dd,
                    activator_pos.x, activator_pos.y,
                    radius * 0.15f, radius * 0.15f);

            if (!settings["touch_controls"]->v.b) {
                if (x < NUM_ACTIVATOR_BINDINGS && !adventure::player->cur_activator) {
                    tvec3 proj;
                    proj = tms_camera_project(this->cam, activator_pos.x, activator_pos.y, layer*LAYER_DEPTH+LAYER_DEPTH/2.f);
                    this->add_text(activator_texts[x], proj.x, proj.y);
                }

                x++;
            }
        }
    }
}

void
game::render_starred(void)
{
    if (settings["render_gui"]->is_false()) {
        return;
    }

    tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.0f, .75f+cos((double)_tms.last_time/100000.) * .25f);
    for (std::set<entity*>::iterator i = this->starred.begin();
    i != this->starred.end(); i++) {

        b2Vec2 p = (*i)->get_position();

        if ((*i)->type == ENTITY_CABLE) {
            p = ((cable*)(*i))->p[0]->get_position() + ((cable*)(*i))->p[1]->get_position();
            p *= .5f;
        } else if ((*i)->g_id == O_COMMAND_PAD) {
            p.x += .25f;
        }

        float s = _tms.xppcm * 0.01f;

        float max_z = 60.f;

        if (!W->level.flag_active(LVL_DISABLE_ADVENTURE_MAX_ZOOM) && !W->is_paused() && W->is_adventure() && this->follow_object == adventure::player) {
            max_z = 20.f;
        }

        s *= tclampf((this->cam->_position.z*2.f) / max_z, 1.f, 2.f);

        tms_ddraw_sprite_r(this->dd, gui_spritesheet::get_sprite(S_STAR),
        p.x,
        p.y,
        s, s,
        cos((double)_tms.last_time/100000.) * 16.f);
    }
}

void
game::render_controls_help()
{
    if (settings["touch_controls"]->v.b)
        return;

    if (!this->render_controls) {
        if (this->tex_controls) {
            delete this->tex_controls;
            this->tex_controls = 0;
        }

        return;
    }

    if (!this->tex_controls) {
        this->tex_controls = new tms::texture();
        this->tex_controls->format = GL_RGBA;
        this->tex_controls->load("data/textures/controls.png");
        this->tex_controls->upload();
    }

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    struct tms_sprite tmp;
    tmp.bl = tvec2f(0.f, 0.f);
    tmp.tr = tvec2f(1.f, 1.f);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, this->tex_controls->gl_texture);

    int width = this->tex_controls->width;
    int height = this->tex_controls->height;

    if (this->tex_controls->width*1.25f >= _tms.window_width) {
        float mod = _tms.window_width / (this->tex_controls->width*1.25f);
        width *= mod;
        height *= mod;
    }

    tms_ddraw_sprite(this->get_surface()->ddraw, &tmp,
            _tms.window_width/2.f, _tms.window_height/2.f,
            width, height);

    this->add_text("Press <ESCAPE> or click anywhere on the screen to close this window.",
            font::xmedium,
            _tms.window_width/2.f, _tms.window_height/2.f + height/2.f + _tms.xppcm*0.1f,
            TV_WHITE,
            true,
            ALIGN_CENTER, ALIGN_BOTTOM);
}

void
game::render_existing_connections(void)
{
    float mv[16];
    float p[16];
    tmat4_copy(p, this->cam->projection);

    tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.0f, .95f);

    for (std::set<connection*>::iterator i = W->connections.begin();
            i != W->connections.end(); i++) {
        connection *c = *i;
        if (this->state.sandbox || (c->e->is_moveable() || c->o->is_moveable())) {
            b2Vec2 base = c->e->local_to_world(b2Vec2(c->p.x, c->p.y), c->f[0]);

            tmat4_copy(mv, this->cam->view);
            tmat4_translate(mv, base.x, base.y, c->layer*LAYER_DEPTH+.5f+((LAYER_DEPTH/2.f)*c->multilayer));
            tms_ddraw_set_matrices(this->dd, mv, p);
            tms_ddraw_sprite(this->dd, c->type == CONN_PIVOT ? gui_spritesheet::get_sprite(S_ATTACH_ROTARY): gui_spritesheet::get_sprite(S_ATTACH_RIGID), 0, 0, .75f, .75f);
        }
    }
}

void
game::render_connections(void)
{
    c_map::iterator i = this->pairs.begin();

    if (this->get_mode() == GAME_MODE_SELECT_SOCKET
        || this->get_mode() == GAME_MODE_QUICK_PLUG) {
        return;
    }

    float mv[16];
    float p[16];
    tmat4_copy(p, this->cam->projection);

    tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.0f, .75f + .15f * cos((double)_tms.last_time/50000.));
    int n=0;
    for (;i != this->pairs.end(); i++, n++) {
        connection *c = i->second;

        tvec3 dd = tms_camera_project(this->cam, this->cam->_position.x, this->cam->_position.y,c->layer*LAYER_DEPTH+((LAYER_DEPTH/2.f)*c->multilayer));
        tvec3 v1 = tms_camera_unproject(this->cam, 0.f, 0.f, dd.z);
        tvec3 v2 = tms_camera_unproject(this->cam, _tms.xppcm*.5f, 0.f, dd.z);

        float w = v2.x-v1.x;

        tmat4_copy(mv, this->cam->view);
        tmat4_translate(mv, 0, 0, c->layer*LAYER_DEPTH+((LAYER_DEPTH/2.f)*c->multilayer));
        tms_ddraw_set_matrices(this->dd, mv, p);

        //tms_ddraw_circle(this->dd, c->p.x, c->p.y, .25f, .25f);
        tms_ddraw_sprite_r(this->dd, gui_spritesheet::get_sprite(S_ATTACH), c->p.x, c->p.y, w, w, cos((double)_tms.last_time/100000.) * 16.f);

        if (!settings["touch_controls"]->v.b && n < 6) {
            tvec3 proj;
            proj = tms_camera_project(this->cam, c->p.x, c->p.y, c->layer*LAYER_DEPTH+((LAYER_DEPTH/2.f)*c->multilayer));
            this->add_glyph(font::medium->get_glyph('F'+n), proj.x, proj.y);
        }
    }
}

/**
 * Render the connection type selection menu
 **/
void
game::render_conn_types()
{
    float mv[16];
    float p[16];

    if (this->cs_timer < 1.5f) {
        this->cs_timer += _tms.dt*15.f;
        if (this->cs_timer > 1.5f) this->cs_timer = 1.5f;
    }

    tmat4_copy(p, this->cam->projection);
    tmat4_copy(mv, this->cam->view);
    tmat4_translate(mv, 0, 0, this->cs_conn->layer*LAYER_DEPTH+((LAYER_DEPTH/2.f)*this->cs_conn->multilayer));
    tms_ddraw_set_matrices(this->dd, mv, p);

    tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.0f, 1.f*this->cs_timer);
    //tms_ddraw_set_color(this->dd, 0.0f, 0.0f, 0.0f, 1.f*this->cs_timer);
    //

    tvec3 dd = tms_camera_project(this->cam, this->cam->_position.x, this->cam->_position.y,this->cs_conn->layer*LAYER_DEPTH+((LAYER_DEPTH/2.f)*this->cs_conn->multilayer));
    tvec3 v1 = tms_camera_unproject(this->cam, 0.f, 0.f, dd.z);
    tvec3 v2 = tms_camera_unproject(this->cam, _tms.xppcm*.5f, 0.f, dd.z);

    float w = v2.x-v1.x;

    tms_ddraw_line(this->dd,
            this->cs_conn->p.x, this->cs_conn->p.y,
            this->cs_conn->p.x - CSCONN_OFFSX*w * this->cs_timer, this->cs_conn->p.y + CSCONN_OFFSY*w * this->cs_timer
            );
    tms_ddraw_line(this->dd,
            this->cs_conn->p.x, this->cs_conn->p.y,
            this->cs_conn->p.x + CSCONN_OFFSX*w * this->cs_timer, this->cs_conn->p.y + CSCONN_OFFSY*w * this->cs_timer
            );

    tms_ddraw_sprite(this->dd, gui_spritesheet::get_sprite(S_ATTACH_RIGID),
            this->cs_conn->p.x - CSCONN_OFFSX*w * this->cs_timer, this->cs_conn->p.y + CSCONN_OFFSY*w * this->cs_timer,
            w * this->cs_timer, w * this->cs_timer
            );
    tms_ddraw_sprite(this->dd, gui_spritesheet::get_sprite(S_ATTACH_ROTARY),
            this->cs_conn->p.x + CSCONN_OFFSX*w * this->cs_timer, this->cs_conn->p.y + CSCONN_OFFSY*w * this->cs_timer,
            w * this->cs_timer, w * this->cs_timer
            );

    float ss = (1.f - cs_timer);
    if (ss < .25f) ss = .25f;
    tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.0f, ss);
    tms_ddraw_sprite(this->dd, gui_spritesheet::get_sprite(S_ATTACH),
            this->cs_conn->p.x, this->cs_conn->p.y,
            w * ss, w * ss
            );
    tms_ddraw_set_color(this->dd, 1.0f, 1.0f, 1.0f, 1.f);
}

void
game::select_socksel(int x)
{
    if (!this->ss_plug && !this->ss_asker) {
        /* ss_plug is 0 if we're unplugging */
        this->perform_socket_action(x);

    } else {
        cable *cc = 0;
        int ctype = this->ss_socks[x]->ctype;

        if (this->ss_asker) {
            /* quickplug */
            cc = (cable*)of::create(ctype == CABLE_RED ? 34 : (ctype == CABLE_BLACK ? 33 : 35));
            //W->add(cc);
            cc->construct();
            cc->on_pause();
            cc->p[0]->entity::set_layer(this->ss_edev->get_entity()->get_layer());

            /* find the first socket of this type in the asker and connect one of the plugs to it */
#if 0
            isocket *found = 0;
            if (this->ss_edev->get_socket_dir(this->ss_socks[x]) == CABLE_IN) {
                for (int x=0; x<this->ss_asker->num_s_out; x++) {
                    if (this->ss_asker->s_out[x].p == 0 && this->ss_asker->s_out[x].ctype == ctype) {
                        found = &this->ss_asker->s_out[x];
                        break;
                    }
                }
            } else {
                for (int x=0; x<this->ss_asker->num_s_in; x++) {
                    if (this->ss_asker->s_in[x].p == 0 && this->ss_asker->s_in[x].ctype == ctype) {
                        found = &this->ss_asker->s_in[x];
                        break;
                    }
                }
            }
#endif

            this->ss_plug = cc->p[1];

            /*
            if (found) {
                cc->p[1]->entity::set_layer(this->ss_asker->get_entity()->get_layer());
                //cc->p[0]->connect(this->ss_asker, found);
            } else {
                tms_infof("wtf");
                this->remove_entity(cc);
                W->remove(cc);
                delete cc;
                return;
            }
            */
        }

        bool success = false;

        if (this->ss_quickplug_step2) {
            W->add(this->ss_plug->c);
            this->add_entity(this->ss_plug->c);
        }

        int status;
        if (cc && this->ss_asker) {
            cc->connect(static_cast<plug*>(this->ss_plug), this->ss_edev, this->ss_edev->get_socket_index(this->ss_socks[x]));
            status = T_OK;
        } else {
            status = this->ss_plug->connect(this->ss_edev, this->ss_socks[x]);
        }

        switch (status) {
            case T_OK:
                success = true;
                this->add_ca(-1, this->ss_edev->get_entity()->local_to_world(this->ss_socks[x]->lpos, 0));
                this->selection.disable();
                /*if (W->is_paused()) {
                    this->selection.select(this->ss_edev->get_entity(), 0, (tvec2){0,0}, 0, true);
                }*/
                if (this->ss_asker) {
                    this->open_socket_selector(cc->p[0], this->ss_asker);
                    this->ss_quickplug_step2 = true;
                    return;
                }
                break;

            case 1:
                ui::message("The plugs of a cable cannot be more than one layer apart.");
                break;

            case 2:
                ui::message("Unable to connect the plug to the object.");
                break;

            case 3:
                ui::message("Incompatible cable types.");
                break;
        }

        if (!success && this->ss_asker) {
            /* TODO: remove created cable */
            tms_infof("ASKER MUST BE REMOVED");
        }
    }
    this->ss_quickplug_step2 = false;
    this->set_mode(GAME_MODE_DEFAULT);
    this->state.modified = true;
}

/**
 * Render the socket selection menu, and
 * store the sockets in the game object so we can identify
 * clicks on them later.
 **/
void
game::render_socksel()
{
    if (!this->ss_edev) {
        tms_errorf("object we're trying to render sockets for isn't there anymore, abort");
        this->set_mode(GAME_MODE_DEFAULT);
        return;
    } else if (!this->ss_edev->get_entity()) {
        tms_errorf("entity for the object we're trying to render sockets for isn't there anymore, abort!!");
        return;
    }

    float p[16], mv[16];
    tmat4_copy(p, this->cam->projection);
    tmat4_copy(mv, this->cam->view);
    tmat4_translate(mv, 0, 0, this->ss_edev->get_entity()->get_layer()*LAYER_DEPTH);
    tms_ddraw_set_matrices(this->dd, mv, p);

    glEnable(GL_BLEND);
    this->ss_anim += _tms.dt*10.f;
    if (this->ss_anim > 1.f) this->ss_anim = 1.f;
    this->ss_num_socks = 0;

    if (this->ss_asker) {
        /* quickplug */
        int asker_masks[3];
        for (int t=0; t<3; t++) {
            asker_masks[t] = this->ss_asker->get_inout_mask(t);
        }

        for (int x=0; x<this->ss_edev->num_s_in; x++) {
            //if ((asker_masks[this->ss_edev->s_in[x].ctype] & target_masks[this->ss_edev->s_in[x].ctype])
            if ((asker_masks[this->ss_edev->s_in[x].ctype] & CABLE_OUT)
                    && this->ss_edev->s_in[x].p == 0) {
                this->ss_socks[this->ss_num_socks] = &this->ss_edev->s_in[x];
                this->ss_num_socks ++;
            }
        }

        for (int x=0; x<this->ss_edev->num_s_out; x++) {
            //if ((asker_masks[this->ss_edev->s_out[x].ctype] & target_masks[this->ss_edev->s_out[x].ctype])
            if ((asker_masks[this->ss_edev->s_out[x].ctype] & CABLE_IN)
                    && this->ss_edev->s_out[x].p == 0) {
                this->ss_socks[this->ss_num_socks] = &this->ss_edev->s_out[x];
                this->ss_num_socks ++;
            }
        }
    } else if (this->ss_plug) {
        switch (this->ss_plug->plug_type) {
            case PLUG_PLUG:
                {
                    plug *p = static_cast<plug*>(this->ss_plug);
                    int mask = p->c->get_inout_mask(p->c->ctype);

                    if (mask & CABLE_IN) {
                        for (int x=0; x<this->ss_edev->num_s_in; x++) {
                            /* only add the socket if it's available */
                            if (this->ss_edev->s_in[x].p == 0 && this->ss_edev->s_in[x].ctype == p->c->ctype) {
                                this->ss_socks[this->ss_num_socks] = &this->ss_edev->s_in[x];
                                this->ss_num_socks ++;
                            }
                        }
                    }
                    if (mask & CABLE_OUT) {
                        for (int x=0; x<this->ss_edev->num_s_out; x++) {
                            if (this->ss_edev->s_out[x].p == 0 && this->ss_edev->s_out[x].ctype == p->c->ctype) {
                                this->ss_socks[this->ss_num_socks] = &this->ss_edev->s_out[x];
                                this->ss_num_socks ++;
                            }
                        }
                    }
                }
                break;

            case PLUG_JUMPER:
            case PLUG_RECEIVER:
                for (int x=0; x<this->ss_edev->num_s_in; x++) {
                    /* only add the socket if it's available */
                    if (this->ss_edev->s_in[x].p == 0 && this->ss_edev->s_in[x].ctype == CABLE_RED) {
                        this->ss_socks[this->ss_num_socks] = &this->ss_edev->s_in[x];
                        this->ss_num_socks ++;
                    }
                }
                break;

            case PLUG_MINI_TRANSMITTER:
                for (int x=0; x<this->ss_edev->num_s_out; x++) {
                    if (this->ss_edev->s_out[x].p == 0 && this->ss_edev->s_out[x].ctype == CABLE_RED) {
                        this->ss_socks[this->ss_num_socks] = &this->ss_edev->s_out[x];
                        this->ss_num_socks ++;
                    }
                }
                break;

            default:
                tms_errorf("Unknown plug type: %d", this->ss_plug->plug_type);
                break;
        }
    } else {
        /* disconnect */
        for (int x=0; x<this->ss_edev->num_s_in; x++) {
            if (this->ss_edev->s_in[x].p != 0 && (this->state.sandbox || (W->level.type == LCAT_ADVENTURE && !W->is_paused()) || this->ss_edev->s_in[x].p->is_moveable() || (this->ss_edev->s_in[x].p->c && this->ss_edev->s_in[x].p->c->is_moveable()))) {
                this->ss_socks[this->ss_num_socks] = &this->ss_edev->s_in[x];
                this->ss_num_socks ++;
            }
        }
        for (int x=0; x<this->ss_edev->num_s_out; x++) {
            if (this->ss_edev->s_out[x].p != 0 && (this->state.sandbox || (W->level.type == LCAT_ADVENTURE && !W->is_paused()) || this->ss_edev->s_out[x].p->is_moveable() || (this->ss_edev->s_out[x].p->c && this->ss_edev->s_out[x].p->c->is_moveable()))) {
                this->ss_socks[this->ss_num_socks] = &this->ss_edev->s_out[x];
                this->ss_num_socks ++;
            }
        }
    }

    if (!this->ss_num_socks) {
        tms_infof("no sockets, disabling socksel");
        this->ss_quickplug_step2 = false;
        this->set_mode(GAME_MODE_DEFAULT);
        this->ss_num_socks = 0;
        return;
    } else if (this->ss_num_socks == 1 && (!this->ss_plug || this->ss_quickplug_step2)) {
        /* automatically select the only socket */

        this->state.modified = true;
        if (!this->ss_asker && !this->ss_quickplug_step2) {
            this->perform_socket_action(0);

            this->ss_quickplug_step2 = false;
            this->set_mode(GAME_MODE_DEFAULT);
            this->ss_num_socks = 0;
            this->ss_edev = 0;
        } else {
            this->select_socksel(0);
        }
        return;
    }

    bool call_opengl_stuff = true;
    const float base_scale = 0.005f;

    for (int x=0; x<this->ss_num_socks; ++x) {
        tms_ddraw_set_color(this->dd, MENU_WHITE_FI, 1.f);
        b2Vec2 pos = this->ss_socks[x]->lpos;
        b2Vec2 ipos = pos;

        int i = this->ss_edev->get_socket_index(this->ss_socks[x]);

        //ipos *= 1.f/ipos.Length();
        //
        if (!this->ss_edev->scaleselect) {
            float ia = atan2f(ipos.y, ipos.x);
            ia += this->ss_socks[x]->abias;

            tmath_sincos(ia, &ipos.y, &ipos.x);

            pos = this->ss_edev->get_entity()->local_to_world(pos, 0);

            ipos *= 1.5f * this->ss_anim;
        } else
            ipos *= this->ss_edev->scalemodifier * this->ss_anim;

        ipos = this->ss_edev->get_entity()->local_to_world(ipos, 0);

        tms_ddraw_set_color(this->dd, MENU_WHITE_FI, 1.f);
        tms_ddraw_lcircle(this->dd, ipos.x, ipos.y, .375f * this->ss_anim, .375f * this->ss_anim);

        if (i < 0x80) {
        } else {
            //tms_ddraw_set_color(this->dd, 0.15f, 0.15f, 0.15f, 1.f);
        }

        if (!this->ss_edev->scaleselect) {
            tms_ddraw_line(this->dd, pos.x, pos.y, ipos.x, ipos.y);
        }

        tms_ddraw_circle(this->dd, ipos.x, ipos.y, .375f * this->ss_anim, .375f * this->ss_anim);

        p_text *tx_tag;
        p_text *tx_sid;
        if (this->ss_socks[x]->tag != SOCK_TAG_NONE) {
            tx_tag = gui_spritesheet::tx_sock_tag[this->ss_socks[x]->tag];
        } else {
            tx_tag = gui_spritesheet::tx_sock_tag[1];
        }

        float wh = 2.5;
        tvec3 bgc;

        switch (this->ss_socks[x]->ctype) {
            default: bgc = MENU_WHITE_F; break;
            case CABLE_RED: bgc = (tvec3){.3f, .3f, .3f}; break;
            case CABLE_BLUE: bgc = (tvec3){0.7f, .7f, 1.f}; break;
        }

        if (this->ss_socks[x]->tag != SOCK_TAG_NONE) {
            tms_ddraw_set_color(this->dd, TVEC3_INLINE(bgc), 1.f);
            tms_ddraw_square(this->dd,
                    ipos.x,
                    ipos.y-.275f,
                    .375f * this->ss_anim * 0.55f * wh,
                    .375f * this->ss_anim * .55f);

            tx_tag->set_scale(0.5f * base_scale * this->ss_anim);
            tx_tag->render_at_pos(this->dd, ipos.x, ipos.y-.275f, false, call_opengl_stuff);

            call_opengl_stuff = false;
        }

        if (i >= 0x80) {
            tx_sid = gui_spritesheet::tx_out[i-0x80];
        } else {
            tx_sid = gui_spritesheet::tx_in[i];
        }

        tx_sid->set_scale(base_scale * this->ss_anim);

        tx_sid->render_at_pos(this->dd, ipos.x, ipos.y, false, call_opengl_stuff);

        call_opengl_stuff = false;
    }
}

/* highlight the currently selected entity */
void
game::render_selected_entity()
{
    this->state.edev_labels = false;

    if (!this->selection.enabled() || !this->selection.e)
        return;

    this->state.edev_labels = (this->selection.e->flag_active(ENTITY_IS_EDEVICE) || this->selection.e->type == ENTITY_PLUG || this->selection.e->type == ENTITY_EDEVICE);

    if (this->selection.e->curr_update_method == ENTITY_UPDATE_GROUPED) {
        composable *ee = static_cast<composable*>(this->selection.e);

        b2Vec2 p = ee->gr->body->GetWorldPoint(ee->_pos);
        float a = ee->gr->body->GetAngle()+ee->_angle;

        float c,s;
        tmath_sincos(a, &s, &c);

        ee->M[0] = c;
        ee->M[1] = s;
        ee->M[4] = -s;
        ee->M[5] = c;
        ee->M[12] = p.x;
        ee->M[13] = p.y;
        ee->M[14] = ee->prio * LAYER_DEPTH;

        tmat3_copy_mat4_sub3x3(ee->N, ee->M);
    }

    /* XXX */
    tms_graph_add_entity_with_children(this->outline_graph, this->selection.e);

    glEnable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);
    //glDisable(GL_DEPTH_TEST);
    glBlendFunc(GL_ONE, GL_ONE);
    glBlendEquation(GL_FUNC_ADD);
    glDepthFunc(GL_EQUAL);
    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);
    glColorMask(0,0,1,1);

    tms_graph_render(this->outline_graph, this->cam, this);

    glBlendEquation(GL_FUNC_ADD);
    //glDisable(GL_BLEND);
    glDepthFunc(GL_LESS);

    tms_graph_remove_entity_with_children(this->outline_graph, this->selection.e);

    glColorMask(1,1,1,1);

    /* get the location of the rotation icon */

    glDisable(GL_DEPTH_TEST);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
    glBindTexture(GL_TEXTURE_2D, gui_spritesheet::atlas->texture.gl_texture);

    if ((W->is_paused() || !this->selection.e->conn_ll) && this->selection.e->flag_active(ENTITY_ALLOW_ROTATION) && !this->selection.e->flag_active(ENTITY_CONNECTED_TO_BREADBOARD)) {
        const float width = selection.e->get_width();
        b2Vec2 p = this->selection.e->local_to_world(b2Vec2(width, 0.f), this->selection.frame);
        b2Vec2 r = this->selection.e->local_to_world(b2Vec2(width+.9f, 0.f), this->selection.frame);
        b2Vec2 r2 = this->selection.e->local_to_world(b2Vec2(width+1.f, 0.f), this->selection.frame);
        glDisable(GL_DEPTH_TEST);
        tms_ddraw_set_color(this->dd, 0.f, 0.f, 0.f, 1.f);
        float mv[16];
        tmat4_copy(mv, this->cam->view);
        tmat4_translate(mv, 0, 0, this->selection.e->get_layer()*LAYER_DEPTH);
        tms_ddraw_set_matrices(this->dd, mv, this->cam->projection);
        tms_ddraw_line(this->dd, p.x, p.y, r.x, r.y);
        tms_ddraw_set_color(this->dd, 1.f, 1.f, 1.f, 1.f);
        tms_ddraw_sprite(this->dd, gui_spritesheet::get_sprite(S_ROT), r2.x, r2.y, .5f, .5f);
    }

    if (this->selection.e->g_id == O_SERVO_MOTOR || this->selection.e->g_id == O_DC_MOTOR) {
        motor *s = (motor*)this->selection.e;
        b2Vec2 p = this->selection.e->local_to_world(b2Vec2(0.f, 0.f), this->selection.frame);
        b2Vec2 r = this->selection.e->local_to_world(b2Vec2(cosf(s->properties[1].v.f) *3.f, sinf(s->properties[1].v.f)*3.f), this->selection.frame);

        tms_ddraw_set_color(this->dd, 1.f, 0.f, 1.f, 1.f);
        float mv[16];
        tmat4_copy(mv, this->cam->view);
        tmat4_translate(mv, 0, 0, this->selection.e->get_layer()*LAYER_DEPTH);
        tms_ddraw_set_matrices(this->dd, mv, this->cam->projection);
        tms_ddraw_line(this->dd, p.x, p.y, r.x, r.y);
        tms_ddraw_square(this->dd, r.x, r.y, .25f, .25f);
    }

    if (this->selection.e->g_id == O_GRAVITY_MANAGER) {
        gravityman *g = static_cast<gravityman*>(this->selection.e);

        b2Vec2 p = g->local_to_world(b2Vec2(0.f, 0.f), this->selection.frame);
        b2Vec2 r = g->local_to_world(b2Vec2(cosf(g->properties[0].v.f) *2.f, sinf(g->properties[0].v.f)*2.f), this->selection.frame);

        tms_ddraw_set_color(this->dd, 1.f, 0.f, 1.f, 1.f);
        float mv[16];
        tmat4_copy(mv, this->cam->view);
        tmat4_translate(mv, 0, 0, g->get_layer()*LAYER_DEPTH);
        tms_ddraw_set_matrices(this->dd, mv, this->cam->projection);
        tms_ddraw_line(this->dd, p.x, p.y, r.x, r.y);
        tms_ddraw_square(this->dd, r.x, r.y, .25f, .25f);
    }

    glDisable(GL_BLEND);
}

void
game::render_selected_connection()
{
    if (this->selection.enabled() && this->selection.c) {
        glDisable(GL_DEPTH_TEST);
        connection *c = this->selection.c;
        b2Vec2 base = c->e->local_to_world(b2Vec2(c->p.x, c->p.y), c->f[0]);
        float mv[16];
        float p[16];

        tmat4_copy(p, this->cam->projection);
        tmat4_copy(mv, this->cam->view);

        tmat4_translate(mv, base.x, base.y, c->layer*LAYER_DEPTH+.5f+((LAYER_DEPTH/2.f)*c->multilayer));
        tms_ddraw_set_color(this->dd, 2.f, 2.f, 5.f, 1.f);
        tms_ddraw_set_matrices(this->dd, mv, p);
        tms_ddraw_sprite(this->dd, c->type == CONN_PIVOT ? gui_spritesheet::get_sprite(S_ATTACH_ROTARY): gui_spritesheet::get_sprite(S_ATTACH_RIGID), 0, 0, .75f, .75f);
        glEnable(GL_DEPTH_TEST);
    }

}

void
game::drop_interacting(void)
{
    if (W->level.type == LCAT_ADVENTURE)
        this->selection.disable();

    for (int x=0; x<MAX_INTERACTING; x++) {
        if (interacting[x]) {
            this->destroy_mover(x);
        }
    }

    this->set_mode(GAME_MODE_DEFAULT);
}

void
game::drop_if_interacting(entity *e)
{
    for (int x=0; x<MAX_INTERACTING; x++) {
        if (interacting[x] == e) {
            this->destroy_mover(x);
            break;
        }
    }
}

int
game::interacting_with(entity *e)
{
    for (int x=0; x<MAX_INTERACTING; x++) {
        if (interacting[x] == e) {
            return current_interacting != -1 && dragging[current_interacting] ? 2 : 1;
        }
    }

    return false;
}

int
game::is_mover_joint(b2Joint *j)
{
    for (int x=0; x<MAX_INTERACTING; x++) {
        if (mover_joint[x] == j) {
            return x;
        }
    }

    return -1;
}

void
game::render_trails()
{
    for (int x=0; x<MAX_INTERACTING; x++) {
        if (interacting[x]) {
            int num = interacting_p[x];
            if (num > INTERACT_TRAIL_LEN)
                num = INTERACT_TRAIL_LEN;

            tms_graph_add_entity(this->outline_graph, interacting[x]);

            float M_saved[16];
            float N_saved[9];
            tmat4_copy(M_saved, interacting[x]->M);
            tmat3_copy(N_saved, interacting[x]->N);

            glEnable(GL_BLEND);
            glEnable(GL_DEPTH_TEST);
            glDepthMask(0);
            //glDepthFunc(GL_EQUAL);

            glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE);
            //glBlendFunc(GL_ONE, GL_ONE_MINUS_CONSTANT_ALPHA);
            //glBlendFunc(GL_ONE_MINUS_CONSTANT_ALPHA, GL_CONSTANT_ALPHA);
            glBlendEquation(GL_FUNC_ADD);

            for (int y=0; y<num; y++) {
                int p = (interacting_p[x]-num-1+y)%INTERACT_TRAIL_LEN;
                if (p<0)p=0;
                glBlendColor(1.f, 1.f, 1.f, .05f+(float)y/(float)INTERACT_TRAIL_LEN * .15f);

                tmat4_copy(interacting[x]->M, interacting_M[x][p]);
                tmat3_copy(interacting[x]->N, interacting_N[x][p]);

                tms_graph_render(this->outline_graph, this->cam, this);
            }

            tmat4_copy(interacting[x]->M, M_saved);
            tmat3_copy(interacting[x]->N, N_saved);
            glBlendColor(1.f, 1.f, 1.f, 1.f);
            glDepthFunc(GL_EQUAL);
            tms_graph_render(this->outline_graph, this->cam, this);

            glDepthMask(0xff);
            glBlendEquation(GL_FUNC_ADD);
            glDisable(GL_BLEND);
            glDepthFunc(GL_LESS);

            tms_graph_remove_entity(this->outline_graph, interacting[x]);

            tmat4_copy(interacting[x]->M, M_saved);
            tmat3_copy(interacting[x]->N, N_saved);
        }
    }
}

static void
fadeout_update_matrices(struct tms_entity *e, b2Vec2 velocity, bool scale=false)
{
    for (int c=0; c<e->num_children; c++) {
        fadeout_update_matrices(e->children[c], velocity);
    }

    e->M[12] += velocity.x*_tms.dt;
    e->M[13] += velocity.y*_tms.dt;

    if (scale) {
        float damping = powf(.025f, _tms.dt);
        e->M[0] *= damping;
        e->M[5] *= damping;
        e->M[10] *= damping;
    }
}

void
game::render_highlighted()
{
    for (int x=0; x<NUM_HL; x++) {
        struct hl *hl = &this->hls[x];

        if (hl->type & HL_TYPE_ERROR) {
            if (hl->time <= -1.f) {
                hl->time = 1.f;
            }
        } else {
            if (hl->time <= 0.f) {
                this->clear_hl(hl);

                continue;
            }

        }

        if (!(hl->type & HL_TYPE_PERSISTENT)) {
            hl->time -= _tms.dt * 3.f;
        }

        if (hl->type & HL_TYPE_MULTI) {
            for (entity_set::iterator i = hl->entities->begin();
                    i != hl->entities->end(); i++) {
                (*i)->prepare_fadeout();
                tms_graph_add_entity_with_children(this->outline_graph, *i);
            }
        } else if (hl->e) {
            hl->e->prepare_fadeout();
            tms_graph_add_entity_with_children(this->outline_graph, hl->e);
        } else {
            continue;
        }

        float amod = 1.f;

        glEnable(GL_DEPTH_TEST);

        if (hl->type & HL_TYPE_ERROR) {
            amod = 0.5f;
            glDisable(GL_DEPTH_TEST);
        } else if (hl->type & HL_TYPE_TINT) {
            amod = 0.5f;
        }

        glDepthFunc(GL_LEQUAL);
        glCullFace(GL_BACK);
        glEnable(GL_BLEND);
        glEnable(GL_CULL_FACE);

        glBlendColor(1.f, 1.f, 1.0f, fabsf(hl->time * amod));

        if (hl->type & HL_TYPE_ERROR) {
            glColorMask(0,1,1,1);
            glBlendFunc(GL_ONE, GL_CONSTANT_ALPHA);
            glBlendEquation(GL_FUNC_SUBTRACT);
        } else if (hl->type & HL_TYPE_TINT) {
            glColorMask(1,1,1,1);
            glBlendFunc(GL_ONE, GL_ONE);
            glBlendEquation(GL_FUNC_ADD);
        } else {
            glColorMask(1,1,1,1);
            glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE);
            glBlendEquation(GL_FUNC_ADD);
        }

        tms_graph_render(this->outline_graph, this->cam, this);

        glBlendEquation(GL_FUNC_ADD);
        glDisable(GL_BLEND);
        glDepthFunc(GL_LESS);

        if (hl->type & HL_TYPE_MULTI) {
            for (entity_set::iterator i = hl->entities->begin();
                    i != hl->entities->end(); i++) {
                tms_graph_remove_entity_with_children(this->outline_graph, *i);
            }
        } else {
            tms_graph_remove_entity_with_children(this->outline_graph, hl->e);
        }

        glColorMask(1,1,1,1);
    }

    for (std::set<fadeout_event*>::iterator i = this->fadeouts.begin(); i != this->fadeouts.end(); ) {
        fadeout_event *ev = *i;

        //ev->time -= 100;
        ev->time -= _tms.dt*2.f;

        if (ev->time <= 0.f) {
            this->free_fadeout(ev);
            this->fadeouts.erase(i++);
        } else {
            glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE);
            glBlendEquation(GL_FUNC_ADD);
            glDepthFunc(GL_LESS);
            glCullFace(GL_BACK);
            glEnable(GL_CULL_FACE);
            glEnable(GL_BLEND);

            glBlendColor(1.f, 1.f, 1.f, ev->time);

            for (std::vector<fadeout_entity>::iterator it = ev->entities.begin();
                    it != ev->entities.end(); it++) {
                //(*i).e->M[0] *= ev->time;
                //
                //float m[16];
                //tmat4_load_identity(m);
                //tmat4_scale(m, ev->time, ev->time, 1.f);
                //tmat4_multiply((*it).e->M, m);
                //
                //

                if (ev->absorber) {
                    b2Vec2 velocity = ev->absorber->local_to_world(ev->absorber_point, ev->absorber_frame) - b2Vec2((*it).e->M[12], (*it).e->M[13]);
                    velocity *= 10.f;
                    fadeout_update_matrices((*it).e, velocity, true);
                } else {
                    fadeout_update_matrices((*it).e, (*it).velocity);
                }
                tms_graph_add_entity_with_children(this->outline_graph, (*it).e);
            }

            tms_graph_render(this->outline_graph, this->cam, this);

            for (std::vector<fadeout_entity>::iterator it = ev->entities.begin();
                    it != ev->entities.end(); it++)
                tms_graph_remove_entity_with_children(this->outline_graph, (*it).e);

            glBlendEquation(GL_FUNC_ADD);
            glDepthFunc(GL_LESS);
            glDisable(GL_BLEND);
            i++;
        }
    }

    if (W->level.type == LCAT_ADVENTURE && W->is_paused()) {
        entity *e = W->get_entity_by_id(this->state.adventure_id);
        if (e && e->flag_active(ENTITY_IS_ROBOT)) {
            tms_graph_add_entity_with_children(this->outline_graph, e);

            glEnable(GL_DEPTH_TEST);
            glDepthFunc(GL_EQUAL);
            glCullFace(GL_BACK);
            glEnable(GL_BLEND);
            glEnable(GL_CULL_FACE);

            //glBlendColor(1.f, 1.f, 1.f, fabsf(this->hls[x].time * (this->hls[x].error ? .5f : 1.f)));
            glBlendColor(0.5f, 1.f, 0.5f, 0.2f);
            glColorMask(0,1,0,1);
            glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE);
            glBlendEquation(GL_FUNC_ADD);

            tms_graph_render(this->outline_graph, this->cam, this);

            glBlendEquation(GL_FUNC_ADD);
            glDisable(GL_BLEND);
            glDepthFunc(GL_LESS);

            tms_graph_remove_entity_with_children(this->outline_graph, e);

            glColorMask(1,1,1,1);
        }
    }
}

void
game::free_fadeout(fadeout_event *ev)
{
    /* TODO: cleanup.
     *       ALSO masure we clean up if the game is paused before
     *       a fadeout is completed !!!!! */

    for (std::vector<fadeout_entity>::iterator i = ev->entities.begin();
            i != ev->entities.end(); i++) {
        if ((*i).do_free) {
            delete (*i).e;
        }
    }

    delete ev;
}

/**
 * Open a menu for choosing which socket to attach the
 * given plug to.
 *
 * If p is 0, we open the socket unplug menu for the given edevice
 **/
void
game::open_socket_selector(entity *e, edevice *edev, int action/*=0*/)
{
    tms_infof("open socket selector");
    this->ss_quickplug_step2 = false;
    this->ss_action = action;

    if (G->state.sandbox || !e || e->get_property_entity()->is_moveable() || W->level.type == LCAT_ADVENTURE) {
        this->set_mode(GAME_MODE_SELECT_SOCKET);
        this->ss_edev = edev;

        this->ss_plug = 0;
        if (e && !e->flag_active(ENTITY_IS_PLUG)) {
            this->ss_asker = e->get_edevice();
        } else {
            if (e && e->flag_active(ENTITY_IS_PLUG)) {
                this->ss_plug = static_cast<plug_base*>(e);
            }

            this->ss_asker = 0;
        }

        this->ss_num_socks = 0;
        this->ss_anim = 0.f;

        tms_infof("OK");
    }
}

void
game::set_follow_object(entity *e, bool snap, bool preserve_pos/*=false*/)
{
    if (this->follow_object == e && e != 0)
        return;

    if (e) {
        this->follow_object = e;
    } else {
        if (W->is_adventure()) {
            this->follow_object = adventure::player;
        } else {
            this->follow_object = 0;
        }
    }

    this->cam_vel.x = 0.f;
    this->cam_vel.y = 0.f;

    if (this->follow_object) {
        if (preserve_pos) {
            this->cam_rel_pos = b2Vec2(this->cam->_position.x, this->cam->_position.y) - this->follow_object->get_position();
        } else {
            if (this->follow_object == adventure::player) {
                this->cam_rel_pos = b2Vec2(0.f,0.f);
                this->adv_rel_pos = b2Vec2(0.f,0.f);
            } else {
                this->cam_rel_pos = b2Vec2(0.f,0.f);
            }
        }

        if (snap) {
            this->cam->_position.x = this->follow_object->get_position().x;
            this->cam->_position.y = this->follow_object->get_position().y;
        }
    }
}

void
game::setup_panel(panel *p)
{
    adventure::clear_widgets();
    this->current_panel = p;

    for (int x=0; x<p->num_widgets; x++) {
        if (p->widgets[x].used) {
            switch (p->widgets[x].wtype) {
                case PANEL_LEFT:  if (wdg_left_i < MAX_DIR_BTN) {
                                      wdg_left[wdg_left_i++] = &p->widgets[x];
                                  }
                                  break;
                case PANEL_RIGHT: if (wdg_right_i < MAX_DIR_BTN) {
                                      wdg_right[wdg_right_i++] = &p->widgets[x];
                                  }
                                  break;
                case PANEL_UP:    if (wdg_up_i < MAX_DIR_BTN) {
                                      wdg_up[wdg_up_i++] = &p->widgets[x];
                                  }
                                  break;
                case PANEL_DOWN:  if (wdg_down_i < MAX_DIR_BTN) {
                                      wdg_down[wdg_down_i++] = &p->widgets[x];
                                  }
                                  break;
                case PANEL_BTN:   if (wdg_btn_i < MAX_BTN) {
                                      wdg_btn[wdg_btn_i++] = &p->widgets[x];
                                  }
                                  break;

                case PANEL_RADIAL:
                case PANEL_BIGRADIAL:
                case PANEL_FIELD:
                case PANEL_BIGFIELD:
                    if (wdg_misc_i < MAX_MISC_WIDGETS) {
                        wdg_misc[wdg_misc_i++] = &p->widgets[x];
                    }
                    break;

                case PANEL_SLIDER:
                case PANEL_BIGSLIDER:
                    if (wdg_misc_i < MAX_MISC_WIDGETS) {
                        wdg_misc[wdg_misc_i++] = &p->widgets[x];
                    }
                    break;

                case PANEL_VSLIDER:
                case PANEL_VBIGSLIDER:
                    if (wdg_misc_i < MAX_MISC_WIDGETS) {
                        wdg_misc[wdg_misc_i++] = &p->widgets[x];
                    }
                    break;
            }
            this->get_surface()->add_widget(&p->widgets[x]);
        }
    }
}

/**
 * TODO: fix spaghetti
 **/
void
game::set_control_panel(entity *e)
{
    if (e) {
        /* If the entity is in the list of starred objects, remove it from that list. */
        this->starred.erase(e);
    }
    if (e && e == this->current_panel) {
        /* If the entity sent is the same panel that we're already controlling, stop here. */
        return;
    }

    /* We've recieved an unique RC.
     * Begin by resetting all labels and special events for any previous RCs. */
    for (int x=0; x<MAX_DIR_BTN; ++x) {
        wdg_up[x] = 0;
        wdg_down[x] = 0;
        wdg_left[x] = 0;
        wdg_right[x] = 0;
    }
    for (int x=0; x<MAX_BTN; ++x) {
        wdg_btn[x] = 0;
    }
    for (int x=0; x<MAX_MISC_WIDGETS; ++x) {
        wdg_misc[x] = 0;
    }
    wdg_up_i = 0;
    wdg_down_i = 0;
    wdg_left_i = 0;
    wdg_right_i = 0;
    wdg_btn_i = 0;
    wdg_misc_i = 0;

    if (this->current_panel) {
        if (this->current_panel->is_control_panel()) {
            /* If we are already connected to a panel and it's an RC,
             * call the panel_disconnected for the RC.
             *
             * That function resets any buttons that were pressed to their
             * default values, assuming the RC is an RC Basic or RC Micro.
             *
             * With any other RC we cannot rely on the default values
             * because of the ghost inputs. */
            ((panel*)this->current_panel)->panel_disconnected();

            /* We decativate any "specially activated" widgets */
            deactive_misc_wdg(&G->active_hori_wdg);
            deactive_misc_wdg(&G->active_vert_wdg);
        } else if (this->current_panel->is_creature()) {
            /* If the previous panel was a creature, we will assume it
             * was the adventure robot, and clear any such widgets from
             * the screen. */
            adventure::clear_widgets();
        } else {
            /* If the panel was neither an RC or a creature,
             * something strange must have happened. */
            return;
        }

        this->current_panel = 0;
    }

    this->active_hori_wdg = 0;
    this->active_vert_wdg = 0;

    /* If the input to this function is a null pointer, we stop any further
     * execution. */
    if (!e) {
        //this->set_follow_object(0, false);
        return;
    }

    panel *p = (e && e->is_control_panel() ? static_cast<panel*>(e) : 0);
    creature *c = (e && e->is_creature() ? static_cast<creature*>(e) : 0);

    this->current_panel = e;

    if (p) {
        if (!W->is_adventure()) {
            /* If the level flag "Disable RC camera-snap" is enabled,
             * we will not snap to the given RC. */
            this->set_follow_object(e, false, W->level.flag_active(LVL_DISABLE_RC_CAMERA_SNAP));
        }

        /* If the new panel is an RC, we set up any panel widget labels
         * for that RC. */
        this->setup_panel(static_cast<panel*>(e));
    } else if (c && W->is_adventure() && c->is_player()) {
        /* If the new panel is the adventure robot, we initialize the on-screen
         * adventure widgets. */
        adventure::init_widgets();
    }
}

void
game::reset()
{
    disable_menu = false;
    sm::stop_all();
    this->selection.disable();
    this->reset_touch(true);
    W->cwindow->reset();
    this->clear_entities();
    W->cwindow->preloader.clear_chunks();

    this->state.is_main_puzzle = false;


    this->tmp_ambientdiffuse.x = P.default_ambient;
    this->tmp_ambientdiffuse.y = P.default_diffuse;

    this->multi.box_select = 0;

    this->follow_options.linear = false;
    this->follow_options.offset_mode = 0;
    this->follow_options.offset.x = 0.f;
    this->follow_options.offset.y = 0.f;

    this->_restart_level = false;
    this->_submit_score = false;

    for (std::set<fadeout_event*>::iterator i = this->fadeouts.begin(); i != this->fadeouts.end(); i++) {
        fadeout_event *ev = *i;
        this->free_fadeout(ev);
    }
    this->fadeouts.clear();
    this->clear_errors();
    this->starred.clear();
    adventure::reset();

    _tms.emulating_portrait = false;
    this->cam->up = (tvec3){0.f, 1.f, 0.f};

    this->set_control_panel(0);
    this->follow_object = 0;

    for (int x=0; x<NUM_HL; x++) {
        this->hls[x].e = 0;
        this->hls[x].time = 0.f;
    }

    for (int x=0; x<NUM_HP; x++) {
        this->hps[x].e = 0;
        this->hps[x].time = 0.f;
    }

    for (int x=0; x<MAX_TUTORIAL_TEXTS; x++) {
        this->tt[x].e = 0;
        this->tt[x].what = 0;
        this->tt[x].life = 0.f;
    }

    this->loots.clear();

    this->dropping = -1;
    this->drop_step = 0;
    this->drop_amount = 0;
    this->drop_speed = 1.f;

    for (int x=0; x<MAX_COPY_ENTITIES; ++x) {
        copy_entity[x] = 0;
    }
    for (int x=0; x<NUM_PROMPT_SLOTS; ++x) {
        prompt_slot[x] = 0;
    }

    this->force_static_update = 1;
    this->do_static_update = false;
    this->last_static_update = (tvec3){0.f,0.f,0.f};

    this->current_prompt = 0;
    this->state.time_mul = 0.f;
    this->state.last_autosave_try = _tms.last_time;
    this->state.modified = false;
    this->state.edev_labels = false;
    this->state.test_playing = false;
    this->state.fade = 0.f;
    this->state.waiting = false;
    this->state.ending = false;
    this->state.end_action = GAME_END_PROCEED;
    this->state.finished = false;
    this->state.success = false;
    this->state.edit_layer = 0;
    this->set_score(0);
    this->state.submitted_score = false;
    this->state.new_adventure = false;
    this->brush = 0;
    this->brush_layer_inclusion = true;
    this->brush_material = TERRAIN_GRASS;
    this->get_scene()->add_entity(W->cwindow);
    this->get_scene()->add_entity(display::get_full_entity());
    this->get_scene()->add_entity(ledbuffer::get_entity());
    this->get_scene()->add_entity(linebuffer::get_entity());
    this->get_scene()->add_entity(linebuffer::get_entity2());
    this->get_scene()->add_entity(textbuffer::get_entity());
    this->get_scene()->add_entity(textbuffer::get_entity2());
    this->get_scene()->add_entity(fluidbuffer::get_entity());
    this->get_scene()->add_entity(spritebuffer::get_entity());
    this->get_scene()->add_entity(spritebuffer::get_entity2());
    tms_scene_add_entity(this->super.scene, cable::get_entity());
    tms_scene_add_entity(this->super.scene, rope::get_entity());

    this->get_scene()->add_entity(static_cast<entity*>(pixel::get_entity(0)));
    this->get_scene()->add_entity(static_cast<entity*>(pixel::get_entity(1)));
    this->get_scene()->add_entity(static_cast<entity*>(pixel::get_entity(2)));

    this->get_scene()->add_entity(static_cast<entity*>(tpixel::get_entity(0)));
    this->get_scene()->add_entity(static_cast<entity*>(tpixel::get_entity(1)));
    this->get_scene()->add_entity(static_cast<entity*>(tpixel::get_entity(2)));

    this->selection.reset();
    this->sel_p_body = 0;
    this->sel_p_frame = 0;
    this->sel_p_ent = 0;
    this->current_panel = 0;

    this->cam_vel = (tvec3){0.f, 0.f, 0.f};

#ifdef DEBUG
    this->debug_lines.clear();
#endif

    this->hov_text->active = false;

    _tms.emulating_portrait = false;
    this->cam->up = (tvec3){0.f, 1.f, 0.f};

    G->caveview_size = 0.f;
    G->caveview_zoom = 0.f;
}

/**
 * Apply state directly from world by reading
 **/
void
game::load_state()
{
    tms_debugf("loading game/world state");

    /* copy information about the worlds buffer and set the pointer to point at
     * the location of the state data */
    lvlbuf lb = W->lb;
    lb.rp = W->state_ptr;

    this->state.time_mul = lb.r_float();
    this->state.adventure_id = lb.r_uint32();
    this->set_score(lb.r_uint32());
    this->state.finished = lb.r_uint8();
    this->state.success = lb.r_uint8();
    W->gravity_x = lb.r_float();
    W->gravity_y = lb.r_float();
    W->step_count = lb.r_uint32();
    this->state.finish_step = lb.r_uint32();
    this->cam->_position.x = lb.r_float();
    this->cam->_position.y = lb.r_float();
    this->cam->_position.z = lb.r_float();
    this->cam_vel.x = lb.r_float();
    this->cam_vel.y = lb.r_float();
    this->cam_vel.z = lb.r_float();
    this->cam_rel_pos.x = lb.r_float();
    this->cam_rel_pos.y = lb.r_float();
    this->adv_rel_pos.x = lb.r_float();
    this->adv_rel_pos.y = lb.r_float();
    uint32_t follow = lb.r_uint32();
    uint32_t cp = lb.r_uint32();
    this->follow_options.offset.x = lb.r_float();
    this->follow_options.offset.y = lb.r_float();
    this->follow_options.linear = lb.r_bool();
    this->follow_options.offset_mode = lb.r_uint8();
    this->last_cursor_pos_x = lb.r_int32();
    this->last_cursor_pos_y = lb.r_int32();
    W->electronics_accum = lb.r_uint64();
    this->state.new_adventure = lb.r_bool();

    uint32_t num_events = lb.r_uint32();
    for (int x=0; x<num_events; ++x) {
        W->events[x] = lb.r_int32();
    }

    uint32_t num_timed_absorbs = lb.r_uint32();
    for (int x=0; x<num_timed_absorbs; ++x) {
        uint32_t entity_id = lb.r_uint32();
        int64_t itime = lb.r_uint64();

        W->timed_absorb.insert(std::pair<uint32_t, int64_t>(entity_id, itime));
    }

    /* further apply the read data */
    W->b2->SetGravity(b2Vec2(W->gravity_x, W->gravity_y));
    W->last_gravity = b2Vec2(W->gravity_x, W->gravity_y);

    if (follow) {
        entity *e = W->get_entity_by_id(follow);
        if (e) {
            this->follow_object = e;
        }
    }
    if (cp) {
        entity *e = W->get_entity_by_id(cp);
        if (e) {
            this->set_control_panel(e);
        }
    }
}

void
game::write_state(lvlinfo *lvl, lvlbuf *lb)
{
    lb->ensure(this->get_state_size());

    lb->w_float(this->state.time_mul);
    lb->w_uint32(this->state.adventure_id);
    lb->w_uint32(this->get_real_score());
    lb->w_uint8(this->state.finished);
    lb->w_uint8(this->state.success);
    lb->w_float(W->gravity_x);
    lb->w_float(W->gravity_y);
    lb->w_uint32(W->step_count);
    lb->w_uint32(this->state.finish_step);
    lb->w_float(this->cam->_position.x);
    lb->w_float(this->cam->_position.y);
    lb->w_float(this->cam->_position.z);
    lb->w_float(this->cam_vel.x);
    lb->w_float(this->cam_vel.y);
    lb->w_float(this->cam_vel.z);
    lb->w_float(this->cam_rel_pos.x);
    lb->w_float(this->cam_rel_pos.y);
    lb->w_float(this->adv_rel_pos.x);
    lb->w_float(this->adv_rel_pos.y);
    lb->w_uint32(this->follow_object ? this->follow_object->id : 0);
    lb->w_uint32(this->current_panel ? this->current_panel->id : 0);
    lb->w_float(this->follow_options.offset.x);
    lb->w_float(this->follow_options.offset.y);
    lb->w_bool(this->follow_options.linear);
    lb->w_uint8(this->follow_options.offset_mode);
    lb->w_int32(this->last_cursor_pos_x);
    lb->w_int32(this->last_cursor_pos_y);
    lb->w_uint64(W->electronics_accum);
    lb->w_bool(this->state.new_adventure);

    uint32_t num_events = WORLD_EVENT__NUM;
    lb->w_uint32(num_events);
    for (int x=0; x<num_events; ++x) {
        lb->w_int32(W->events[x]);
    }

    uint32_t num_timed_absorbs = W->timed_absorb.size();
    lb->w_uint32(num_timed_absorbs);

    for (std::map<uint32_t, int64_t>::iterator it = W->timed_absorb.begin();
            it != W->timed_absorb.end(); ++it) {
        lb->w_uint32(it->first);
        lb->w_int64(it->second);
    }
}

size_t
game::get_state_size()
{
    return
        sizeof(float)    /* timemul */
        + sizeof(uint32_t) /* adventure_id */
        + sizeof(uint32_t) /* score */
        + sizeof(uint8_t) /* finished */
        + sizeof(uint8_t) /* success */
        + sizeof(float) /* gravity_x */
        + sizeof(float) /* gravity_y */
        + sizeof(uint32_t) /* step_count */
        + sizeof(uint32_t) /* finish_step */
        + sizeof(float) /* cam x */
        + sizeof(float) /* cam y */
        + sizeof(float) /* cam z */
        + sizeof(float) /* cam vel x */
        + sizeof(float) /* cam vel y */
        + sizeof(float) /* cam vel z */
        + sizeof(float) /* cam rel x */
        + sizeof(float) /* cam rel y */
        + sizeof(float) /* adv rel x */
        + sizeof(float) /* adv rel y */
        + sizeof(uint32_t) /* follow object */
        + sizeof(uint32_t) /* current panel */
        + sizeof(float) /* follow offset x */
        + sizeof(float) /* follow offset y */
        + sizeof(uint8_t) /* follow linear */
        + sizeof(uint8_t) /* follow offset_mode */
        + sizeof(int32_t) /* last cursor pos x */
        + sizeof(int32_t) /* last cursor pos y */
        + sizeof(uint64_t) /* electronics accum */
        + sizeof(uint8_t) /* is new adventure */
        + sizeof(uint32_t) /* num events */
        + (sizeof(int32_t) * WORLD_EVENT__NUM) /* W->events */
        + sizeof(uint32_t) /* num timed absorbs */
        + W->timed_absorb.size() * (sizeof(uint32_t)+sizeof(int64_t))
        ;
}

void
game::apply_level_properties()
{
    W->cwindow->set_seed(W->level.seed);

    this->state.adventure_id = W->level.get_adventure_id();

    if (!adventure::player) {
        entity *player = W->get_entity_by_id(this->state.adventure_id);
        if (player) {
            adventure::player = static_cast<creature*>(player);
        }
    }

    if (!this->caveview->scene) {
        this->get_scene()->add_entity(static_cast<tms::entity*>(this->caveview));
    }

    if (this->state.abo_architect_mode) {
        this->set_architect_mode(false);
    }

#ifdef SDL_PLATFORM_ANDROID
    if (W->level.flag_active(LVL_PORTRAIT_MODE)) {
#else
    if (false) {
#endif
        _tms.emulating_portrait = true;
        this->cam->up = (tvec3){1.f, 0.f, 0.f};
    } else  {
        _tms.emulating_portrait = false;
        this->cam->up = (tvec3){0.f, 1.f, 0.f};
    }

    if (W->level.type == LCAT_ADVENTURE) {
        this->cam->_position.z = 12.f;
    }

    this->cam->_position.x = W->level.sandbox_cam_x;
    this->cam->_position.y = W->level.sandbox_cam_y;
    this->cam->_position.z = W->level.sandbox_cam_zoom;

    this->init_background();

    this->check_all_entities();
}

void
game::init_background()
{
    if (this->bgent->scene) {
        this->get_scene()->remove_entity(this->bgent);
    }

    material_factory::background_id = W->level.bg;
    tms_infof("setting bg to %d", material_factory::background_id);
    material_factory::load_bg_texture(true);

    tms_debugf("SETTING BOTTOM ONLY TO %d", (material_factory::background_id == BG_OUTDOOR));
    ((simplebg*)this->bgent)->bottom_only = (material_factory::background_id == BG_OUTDOOR);
    ((simplebg*)this->bgent)->set_color(W->level.bg_color);

    bool valid = ((simplebg*)this->bgent)->set_level_size(
        W->level.size_x[0],
        W->level.size_x[1],
        W->level.size_y[0],
        W->level.size_y[1]);

    if (!valid) {
        tms_infof("Border sizes invalid, falling back to space background.");
        material_factory::background_id = BG_SPACE;
    }

    switch (material_factory::background_id) {
        case BG_SPACE:
            /* TODO: cool space rendering */
        case BG_COLORED_SPACE:
            break;

        default:
            tms_infof("Background ID %d, adding entity.", material_factory::background_id);
            this->get_scene()->add_entity(this->bgent);
            break;
    }

    float r,g,b,a;
    unpack_rgba(W->level.bg_color, &r, &g, &b, &a);

    this->state.bg_color.r = r;
    this->state.bg_color.g = g;
    this->state.bg_color.b = b;
    this->state.bg_color.a = a;
}

void
game::on_play()
{

}

void
game::on_pause()
{

}

static bool just_paused = false;

void
game::do_pause()
{
    W->save_cache(W->level_id_type, W->level.local_id);

    if (!settings["touch_controls"]->v.b)
        SDL_SetWindowMouseGrab((SDL_Window*)_tms._window, false);

    if (this->state.test_playing && !W->is_puzzle()) {
        tms_infof("returning to sandbox");
        /* When returning from the sandbox, i.e. when we've finished testing our level,
         * we will open the autosave. */
        this->open_sandbox(LEVEL_LOCAL, 0);
    } else {
        if (this->screen_back != 0 && !this->state.is_main_puzzle && !W->is_puzzle()) {
            sm::stop_all();
            tms::set_screen(this->screen_back);
        } else {
            tms_infof("Returning to half-paused state.");
            just_paused = true;
            if (W->is_puzzle())
                G->state.puzzle_state = 1;

            this->open_play(W->level_id_type, W->level.local_id, this->state.pkg, this->state.test_playing, this->state.is_main_puzzle?1:0);
        }
    }

    this->layer_vis = layer_vis_saved;
    this->refresh_widgets();
}

void
game::do_play()
{
    tms_infof("Playing");

    if (!this->state.sandbox && W->level.type == LCAT_PUZZLE) {
        /* loop through all highlights, make sure there are no placement errors */
        bool cancel = false;
        for (int x=0; x<NUM_HL; x++) {
            if (this->hls[x].type & HL_TYPE_ERROR && this->hls[x].e) {
                cancel = true;
                break;
            }
        }

        if (cancel) {
            ui::message("Please ensure no object is blinking red (error) before playing.", false);
            return;
        }
    }

    uint32_t level_id_type = W->level_id_type;
    uint32_t local_id = W->level.local_id;

    if (this->state.sandbox && !W->is_puzzle()) {
        level_id_type = LEVEL_LOCAL;
        local_id = 0;
        this->autosave();
        this->wdg_help->remove();
    } else {
        this->wdg_help->add();
    }

    this->refresh_widgets();

    this->open_play(level_id_type, local_id, this->state.pkg, this->state.sandbox || this->state.test_playing, G->state.is_main_puzzle ? 2 : 0);
}

void
game::clear_entities()
{
    tms_scene_clear_entities(this->get_scene());
    this->u_static.clear();
    this->u_static_custom.clear();
    this->u_ghost.clear();
    this->u_effects.clear();
    this->u_fastbody.clear();
    this->u_grouped.clear();
    this->u_custom.clear();
    this->u_joint.clear();
    this->u_joint_pivot.clear();
}

void
game::add_entity(entity *e, bool soft)
{
    this->get_scene()->add_entity(e);

    switch (e->curr_update_method) {
        case ENTITY_UPDATE_GHOST:
            this->u_ghost.insert(e);
            break;

        case ENTITY_UPDATE_FASTBODY:
            this->u_fastbody.insert(e);
            break;

        case ENTITY_UPDATE_GROUPED:
            this->u_grouped.insert(e);
            break;

        case ENTITY_UPDATE_JOINT_PIVOT:
            this->u_joint_pivot.insert(e);
            break;

        case ENTITY_UPDATE_JOINT:
            this->u_joint.insert(e);
            break;

        case ENTITY_UPDATE_STATIC:
            this->u_static.insert(e);
            if (!soft && !W->is_paused()) e->update();
            break;

        case ENTITY_UPDATE_STATIC_CUSTOM:
            this->u_static_custom.insert(e);
            if (!soft && !W->is_paused()) e->update();
            break;

        case ENTITY_UPDATE_NULL:
            break;

        default:
            this->u_custom.insert(e);
            break;
    }

    if (e->flag_active(ENTITY_DO_UPDATE_EFFECTS)) {
        this->u_effects.insert(e);
    }

    if (e->flag_active(ENTITY_IS_LOCKED)) {
        this->locked.insert(e);
    }
}

void
game::destroy_mover(uint8_t x, bool do_not_deselect)
{
    this->lock();

    if (mover_joint[x]) {
        b2Body *b = mover_joint[x]->GetBodyB();

        b->ResetMassData();
        b->SetAngularDamping(0.f);
        b->SetLinearDamping(0.f);
        b->SetGravityScale(1.f);

        if (adventure::player) {
            b->SetLinearVelocity(adventure::player->get_body(0)
                    ? adventure::player->get_body(0)->GetLinearVelocity() : b2Vec2(0.f, 0.f)
                    );
            b->SetAngularVelocity(0.f);
        }

        W->b2->DestroyJoint(mover_joint[x]);
        mover_joint[x] = 0;
    }

    if (interacting_discharge[x]) {
        this->remove_entity(interacting_discharge[x]);
        delete interacting_discharge[x];
        interacting_discharge[x] = 0;
    }

    if (interacting[x]) {
        if (interacting[x] == this->selection.e && !do_not_deselect) {
            this->selection.disable();
        }
        interacting[x]->on_release_playing();
        edevice *ed = interacting[x]->get_edevice();
        interacting[x] = 0;

        if (ed)
            ed->recreate_all_cable_joints();
    }

    interacting[x] = 0;

    this->unlock();
}

void
game::remove_entity(entity *e)
{
    if (this->hov_ent == e) {
        this->hov_ent = 0;
        this->hov_text->active = false;
    }

    if (W->is_paused()) {
        for (int x=0; x<MAX_COPY_ENTITIES; ++x) {
            if (e == copy_entity[x]) {
                copy_entity[x] = 0;
            }
        }

        if (e->flag_active(ENTITY_IS_EDEVICE)) {
            edevice *ed = e->get_edevice();
            if (ed) {
                if (ed == this->ss_asker) {
                    this->ss_asker = 0;
                }

                if (ed == this->ss_edev) {
                    this->ss_edev = 0;
                }
            }
        }

        if (this->selection.m) {
            this->selection.m->erase(e);
        }
    } else {
        for (int x=0; x<MAX_P; ++x) {
            if (drag_cursorfield[x] == e) {
                drag_cursorfield[x] = 0;
            }
            if (in_cursorfield[x] == e) {
                in_cursorfield[x] = 0;
            }
        }

        if (hover_cursorfield == e) {
            hover_cursorfield = 0;
        }
    }

    /* if this object is highlighted, disable its highlight */
    for (int x=0; x<NUM_HL; x++) {
        if (this->hls[x].type & HL_TYPE_MULTI && this->hls[x].entities) {
            this->hls[x].entities->erase(e);
        }
        if (this->hls[x].e == e) {
            this->hls[x].e = 0;
            this->hls[x].time = 0.f;
        }
    }

    for (int x=0; x<MAX_TUTORIAL_TEXTS; x++) {
        if (this->tt[x].e == e) {
            this->tt[x].e = 0;
            this->tt[x].life = 0.f;
        }
    }

    for (std::set<fadeout_event*>::iterator i = this->fadeouts.begin(); i != this->fadeouts.end();
            i ++ ) {
        fadeout_event *ev = *i;

        if (ev->absorber == e) {
            ev->absorber = 0;
        }
    }

    /* if the object is a robot, remove possible hp stuff */
    for (int x=0; x<NUM_HP; x++) {
        if (this->hps[x].e == e) {
            this->hps[x].time = 0.f;
            this->hps[x].e = 0;
        }
    }

    this->loots.erase(e);

    if (this->selection.e == e) {
        this->selection.disable();
    }
    if (this->sel_p_ent == e) {
        this->sel_p_ent = 0;
    }

    this->u_static.erase(e);
    this->u_static_custom.erase(e);
    this->u_ghost.erase(e);
    this->u_fastbody.erase(e);
    this->u_grouped.erase(e);
    this->u_custom.erase(e);
    this->u_joint_pivot.erase(e);
    this->u_joint.erase(e);

    this->u_effects.erase(e);

    this->locked.erase(e);

    this->get_scene()->remove_entity(e);
}

void
game::set_copy_entity(uint8_t slot, entity *e)
{
    if (slot >= MAX_COPY_ENTITIES) return;

    copy_entity[slot] = e;

    if (e) {
        this->add_highlight(e, HL_PRESET_DEFAULT);
        ui::messagef("Copy entity #%d set to %s[%d]", slot+1, e->get_name(), e->id);
    }
}

void
game::copy_properties(entity *destination, entity *source, bool hl/*=false*/)
{
    if (destination && source && source->compatible_with(destination)) {
        for (int x=0; x<destination->num_properties; ++x) {
            switch (destination->properties[x].type) {
                case P_INT8:
                case P_INT:
                case P_ID:
                case P_FLT:
                    memcpy(&destination->properties[x], &source->properties[x], sizeof(property));
                    break;

                case P_STR:
                    destination->set_property(x, source->properties[x].v.s.buf);
                    break;

                default:
                    tms_warnf("Unhandled property type %d in copy_properties", destination->properties[x].type);
                    break;
            }
        }

        if (destination->g_id == O_PIXEL) {
            ((pixel*)destination)->update_appearance();
        } else if (destination->g_id == O_BOX) {
            destination->on_load(false, false);
        }

        if (hl) {
            P.add_action(ACTION_HIGHLIGHT_SELECTED, 0);
            P.add_action(ACTION_RESELECT, 0);
        }
    } else {
        tms_infof("Unable to copy properties. d: %p. s: %p. g_id %d == %d. np %d == %d",
                destination, source, (destination?destination->g_id:0), (source?source->g_id:0),
                (destination?destination->num_properties:0), (source?source->num_properties:0));
    }
}

void
game::add_entities(
        std::map<uint32_t, entity*> *entities,
        std::map<uint32_t, group*> *groups,
        std::set<connection*> *connections,
        std::set<cable*> *cables
        )
{
    for (std::map<uint32_t, entity*>::iterator i = entities->begin();
            i != entities->end(); i++) {
        this->add_entity(i->second);
    }

    for (std::map<uint32_t, group*>::iterator i = groups->begin();
            i != groups->end(); i++) {
        i->second->create_mesh();
        i->second->finalize();
        this->add_entity(i->second);
    }

    for (std::set<connection *>::iterator i = connections->begin();
            i != connections->end(); i++) {
        if ((*i)->self_ent)
            this->add_entity((*i)->self_ent);
    }

    for (std::set<cable *>::iterator i = cables->begin();
            i != cables->end(); i++) {
        this->add_entity(*i);
    }
}

void
game::check_all_entities()
{
    this->starred.clear();
    this->locked.clear();

    for (std::map<uint32_t, entity*>::iterator i = W->all_entities.begin();
            i != W->all_entities.end(); i++) {
        if (!this->state.sandbox && W->is_puzzle()) {
            if (i->second->is_moveable()) this->starred.insert(i->second);
        } else if (this->state.sandbox && W->is_paused()) {
            if (i->second->is_locked()) this->locked.insert(i->second);
        }

        if (!W->is_adventure() && i->second->is_control_panel() && i->second != this->current_panel) {
            if (((panel*)i->second)->widgets_in_use) this->starred.insert(i->second);
        }
    }

    for (std::set<cable*>::iterator i = W->cables.begin();
            i != W->cables.end(); i++) {
        if (!this->state.sandbox && W->level.type == LCAT_PUZZLE) {
            if ((*i)->is_moveable() && ((*i)->p[0]->s == 0 && (*i)->p[1]->s == 0)) this->starred.insert(*i);
        }
    }
}

void
game::apply_pending_connection(int n)
{
    entity *saved = this->selection.e;
    for (c_map::iterator it = this->pairs.begin(); it != this->pairs.end(); ++it) {
        connection *c = it->second;

        if (n == 0) {
            this->pairs.erase(it);
            this->apply_connection(c, 0);
            break;
        }

        n --;
    }

    this->selection.select(saved);
}

/**
 * Available keybindings.
 *
 * F5     Quicksave
 * F9     Quickload
 * C      Restore camera position
 **/
int
game::handle_input_playing(tms::event *ev, int action)
{
    if (ev->type == TMS_EV_POINTER_DOWN) {
        ui::open_dialog(CLOSE_ALL_DIALOGS);

        this->render_controls = false;
    }
    if (this->state.waiting) {
        if (this->state.finished) {
            if ((ev->type == TMS_EV_KEY_PRESS && ev->data.key.keycode == TMS_KEY_ENTER)
                    || ev->type == TMS_EV_POINTER_UP) {
                if (this->state.sandbox || this->state.test_playing) {
                    this->proceed();
                } else {
                    if (this->state.success && this->state.pkg != 0) {
                        if (this->state.is_main_puzzle) {
                            char filename[1024];
                            uint32_t next = this->state.pkg->get_next_level(W->level.local_id);
                            snprintf(filename, 1023, "%s/7.%d.psol", pkgman::get_level_path(LEVEL_LOCAL), next);

                            open_play_data *opd = new open_play_data(LEVEL_LOCAL, next, this->state.pkg, false, 1);

                            tms_infof("does %s exist?", filename);
                            if (file_exists(filename)) {
                                tms_infof("yep! send ui confirm thing");
                                ui::confirm("Do you want to load your last saved solution?",
                                        "Yes",    principia_action(ACTION_OPEN_MAIN_PUZZLE_SOLUTION, opd),
                                        "No",     principia_action(ACTION_CREATE_MAIN_PUZZLE_SOLUTION, opd),
                                        "Back",   ACTION_BACK);
                            } else {
                                tms_infof("file %s does not exist!", filename);
                                P.add_action(ACTION_CREATE_MAIN_PUZZLE_SOLUTION, opd);
                            }

                            return EVENT_DONE;
                        } else {
                            this->state.end_action = GAME_END_PROCEED;
                            this->state.ending = true;
                        }
                        } else {
                            tms_infof("restarting level");
                            this->do_pause();
                        }
                }
            } else if (ev->type == TMS_EV_KEY_PRESS && (ev->data.key.keycode == TMS_KEY_B ||
                        ev->data.key.keycode == SDL_SCANCODE_AC_BACK)) {
                this->do_pause();
            }

            return T_OK;
        } else {
            if (ev->type == TMS_EV_POINTER_UP
                || ev->type == TMS_EV_KEY_PRESS) {
                if (ev->type == TMS_EV_KEY_PRESS) {
                    switch (ev->data.key.keycode) {
                        case SDL_SCANCODE_AC_BACK:
                        case TMS_KEY_B:
                            this->back();
                            return EVENT_DONE;
                    }
                }

                if (ev->type == TMS_EV_KEY_PRESS) {
                    if (ev->data.key.keycode != TMS_KEY_W
                            && ev->data.key.keycode != TMS_KEY_S
                            && ev->data.key.keycode != TMS_KEY_A
                            && ev->data.key.keycode != TMS_KEY_D
                            && ev->data.key.keycode != TMS_KEY_E
                            && ev->data.key.keycode != TMS_KEY_UP
                            && ev->data.key.keycode != TMS_KEY_DOWN
                            && ev->data.key.keycode != TMS_KEY_LEFT
                            && ev->data.key.keycode != TMS_KEY_RIGHT
                            && ev->data.key.keycode != TMS_KEY_ENTER
                            && ev->data.key.keycode != TMS_KEY_SPACE
                            )
                        return T_OK;
                }

                this->state.waiting = false;
                if (!settings["touch_controls"]->v.b) {
                    if (settings["jail_cursor"]->v.b)
                        SDL_SetWindowMouseGrab(_tms._window, true);
                    else
                        SDL_SetWindowMouseGrab(_tms._window, false);
                }

                if (ev->type == TMS_EV_KEY_PRESS && ev->data.key.keycode == TMS_KEY_SPACE) {
                    return T_OK;
                }
            } else {
                return T_OK;
            }
        }
    }

    if (ev->type == TMS_EV_KEY_PRESS ||
        ev->type == TMS_EV_KEY_UP ||
        ev->type == TMS_EV_POINTER_DOWN ||
        ev->type == TMS_EV_POINTER_UP) {
        this->passthru_input(ev);
    }

    if (ev->type == TMS_EV_KEY_PRESS) {
        if (this->menu_handle_event(ev) == EVENT_DONE) {
            return EVENT_DONE;
        }

        switch (ev->data.key.keycode) {
            case TMS_KEY_W: if (wdg_up[0]) { tms_wdg_set_active(wdg_up[0], 1); return T_OK; } break;
            case TMS_KEY_S: if (wdg_down[0]) { tms_wdg_set_active(wdg_down[0], 1); return T_OK; } break;
            case TMS_KEY_A: if (wdg_left[0]) { tms_wdg_set_active(wdg_left[0], 1); return T_OK; } break;
            case TMS_KEY_D: if (wdg_right[0]) { tms_wdg_set_active(wdg_right[0], 1); return T_OK; } break;

            case TMS_KEY_F: if (wdg_btn[0]) { tms_wdg_set_active(wdg_btn[0], 1); return T_OK; } else { apply_pending_connection(0); } break;
            case TMS_KEY_G: if (wdg_btn[1]) { tms_wdg_set_active(wdg_btn[1], 1); return T_OK; } else { apply_pending_connection(1); } break;
            case TMS_KEY_H: if (wdg_btn[2]) { tms_wdg_set_active(wdg_btn[2], 1); return T_OK; } else { apply_pending_connection(2); } break;
            case TMS_KEY_J: if (wdg_btn[3]) { tms_wdg_set_active(wdg_btn[3], 1); return T_OK; } else { apply_pending_connection(3); } break;
            case TMS_KEY_K: if (wdg_btn[4]) { tms_wdg_set_active(wdg_btn[4], 1); return T_OK; } else { apply_pending_connection(4); } break;
            case TMS_KEY_L: apply_pending_connection(5); break;

            case TMS_KEY_Z: if (try_activate_slider(0)) { return T_OK; } break;
            case TMS_KEY_X: if (try_activate_slider(1)) { return T_OK; } break;
            case TMS_KEY_C: if (try_activate_slider(2)) { return T_OK; } break;
            case TMS_KEY_V: if (try_activate_slider(3)) { return T_OK; } break;

            case TMS_KEY_T:
                if (this->selection.e) {
                    entity *e = this->selection.e;

                    e->disconnect_all();

                    this->post_interact_select(e);
                    this->refresh_widgets();
                }
                break;

            case TMS_KEY_ESC:
                if (this->render_controls) {
                    this->render_controls = false;
                    return EVENT_DONE;
                } else if (G->active_hori_wdg || G->active_vert_wdg) {
                    deactive_misc_wdg(&G->active_hori_wdg);
                    deactive_misc_wdg(&G->active_vert_wdg);
                    return EVENT_DONE;
                }
                break;
        }

        if (W->is_adventure()) {
            if (adventure::handle_input_playing(ev, action) == EVENT_DONE)
                return EVENT_DONE;

            switch (ev->data.key.keycode) {
                case TMS_KEY_ESC:
                    if (G->current_panel && G->current_panel != adventure::player) {
                        if (adventure::player) {
                            adventure::player->detach();
                        }
                    }
                    break;
            }
        }

        switch (ev->data.key.keycode) {
            case TMS_KEY_F5:
                if (W->level.flag_active(LVL_ALLOW_QUICKSAVING)) {
                    this->save_state();
                    ui::message("Saved!");
                } else {
                    ui::message("This level does not support quick saving.");
                }
                break;

            case TMS_KEY_F9:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    disable_menu = true;

                    this->open_latest_state(true);
                }
                break;

            case TMS_KEY_O:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    disable_menu = true;

                    ui::open_dialog(DIALOG_OPEN_STATE);
                }
                break;

            case SDL_SCANCODE_AC_BACK:
            case TMS_KEY_B:
            case TMS_KEY_P:
                if (W->is_adventure()) {
                    if (this->state.sandbox || this->state.test_playing) {
                        if (settings["dna_sandbox_back"]->v.b) {
                            this->do_pause();
                        } else {
                            ui::confirm("Are you sure you want to quit this level?",
                                    "Yes",  ACTION_WORLD_PAUSE,
                                    "No",   ACTION_IGNORE,
                                    0,      ACTION_IGNORE,
                                    confirm_data(CONFIRM_TYPE_BACK_SANDBOX));
                        }
                    } else {
                        ui::confirm("Are you sure you want to quit this level?",
                                "Yes",  ACTION_WORLD_PAUSE,
                                "No",   ACTION_IGNORE);
                    }
                } else {
                    this->do_pause();
                }
                break;

            case TMS_KEY_C:
                this->cam_rel_pos.x = 0.f;
                this->cam_rel_pos.y = 0.f;
                this->adv_rel_pos.x = 0.f;
                this->adv_rel_pos.y = 0.f;
                break;

            case TMS_KEY_Q:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    ui::open_dialog(DIALOG_CONFIRM_QUIT);
                } else {
#ifdef DEBUG
                    this->print_stats();
#endif
                }
                break;

            case SDL_SCANCODE_MENU:
                ui::open_dialog(DIALOG_PLAY_MENU);
                break;
        }
    } else if (ev->type == TMS_EV_KEY_DOWN || ev->type == TMS_EV_KEY_REPEAT) {
        if (this->menu_handle_event(ev) == EVENT_DONE) {
            return EVENT_DONE;
        }
        if (W->is_adventure()) {
            if (adventure::handle_input_playing(ev, action) == EVENT_DONE) {
                return EVENT_DONE;
            }
        }
    } else if (ev->type == TMS_EV_KEY_UP) {
        if (this->menu_handle_event(ev)) {
            return T_OK;
        }
        switch (ev->data.key.keycode) {
            case TMS_KEY_W:
                if (wdg_up[0]) {
                    tms_wdg_set_active(wdg_up[0], 0);
                    return T_OK;
                }
                break;
            case TMS_KEY_S:
                if (wdg_down[0]) {
                    tms_wdg_set_active(wdg_down[0], 0);
                    return T_OK;
                }
                break;
            case TMS_KEY_A:
                if (wdg_left[0]) {
                    tms_wdg_set_active(wdg_left[0], 0);
                    return T_OK;
                }

                break;
            case TMS_KEY_D:
                if (wdg_right[0]) {
                    tms_wdg_set_active(wdg_right[0], 0);
                    return T_OK;
                }
                break;

            case TMS_KEY_F: if (wdg_btn[0]) { tms_wdg_set_active(wdg_btn[0], 0); return T_OK; } break;
            case TMS_KEY_G: if (wdg_btn[1]) { tms_wdg_set_active(wdg_btn[1], 0); return T_OK; } break;
            case TMS_KEY_H: if (wdg_btn[2]) { tms_wdg_set_active(wdg_btn[2], 0); return T_OK; } break;
            case TMS_KEY_J: if (wdg_btn[3]) { tms_wdg_set_active(wdg_btn[3], 0); return T_OK; } break;
            case TMS_KEY_K: if (wdg_btn[4]) { tms_wdg_set_active(wdg_btn[4], 0); return T_OK; } break;
        }

        if (W->is_adventure()) {
            if (adventure::handle_input_playing(ev, action) == EVENT_DONE) {
                return EVENT_DONE;
            }
        }
    } else if (ev->type == TMS_EV_POINTER_DOWN) {
        int pid = ev->data.motion.pointer_id;
        if (pid == 0) current_interacting = -1;

        if (this->menu_handle_event(ev)) {
            down[pid] = false;
            return T_OK;
        }
        if (W->is_adventure()) {
            if (adventure::handle_input_playing(ev, action) == EVENT_DONE)
                return EVENT_DONE;
        }

        tvec2 click_pt = tvec2f(ev->data.motion.x, ev->data.motion.y);
        int64_t diff = (int64_t)_tms.last_time - (int64_t)touch_time[pid];
        float dist = std::abs(tvec2_dist(touch_pos[pid], click_pt));

        this->mining[pid] = false;
        down[pid] = true;
        touch_pos[pid] = click_pt;
        touch_time[pid] = _tms.last_time;
        dragging[pid] = false;
        rotating[pid] = 0;
        resizing[pid] = false;

        tvec3 tproj;
        W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, 0, &tproj);

        if (this->menu_handle_event(ev)) {
            down[pid] = false;
            sm::play(&sm::click, sm::position.x, sm::position.y, rand(), 1.f, false, 0, true);
            return EVENT_DONE;
        }

        if (this->get_mode() == GAME_MODE_DEFAULT && W->is_adventure() && adventure::player
         && !(settings["tutorial"]->v.u32 & TUTORIAL_REPAIR_STATION)) {

            /* Detect clicks on help icons! */
            bool r = false;
            r = this->check_click_help_icon(
                    W->repair_stations,
                    OFFS_REPAIR_STATION,
                    b2Vec2(tproj.x, tproj.y),
                    principia_action(
                        ACTION_OPEN_URL,
                        (void*)strdup("https://principia-web.se/wiki/Repair_Station"))
                    );

            if (r) {
                return EVENT_DONE;
            }
        }

        entity *e = 0;
        b2Body *b;
        tvec2 offs;
        uint8_t frame;

        W->query(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, &e, &b, &offs, &frame, this->layer_vis);

        if (e && e->handle_event(ev->type, pid, tvec2f(ev->data.motion.x, ev->data.motion.y)) == EVENT_DONE)
            return EVENT_DONE;

        if (!settings["touch_controls"]->v.b) {
            if (pid == 0) {
                this->update_last_cursor_pos(ev->data.motion.x, ev->data.motion.y);
                W->events[WORLD_EVENT_CLICK_DOWN] ++;
            }
        } else {
            if (pid == 0) {
                this->update_last_cursor_pos(ev->data.motion.x, ev->data.motion.y);
            }
            W->events[WORLD_EVENT_CLICK_DOWN] ++;
        }

        if (pid == 0 || pid == 1) {
            if (down[0] && down[1] && !W->level.flag_active(LVL_DISABLE_ZOOM)) {
                zoom_dist = tvec2_dist(touch_pos[0], touch_pos[1]);
                zooming = true;
                zoom_stopped = false;
            }
        }

        if (this->selection.e && this->check_click_shape_resize(ev->data.motion.x, ev->data.motion.y)) {
            tms_debugf("enablding shape resize");
            resizing[pid] = 1;
            return T_OK;
        }

        if (this->selection.e && !this->selection.e->conn_ll && this->check_click_rotate(ev->data.motion.x, ev->data.motion.y)) {
            tms_debugf("enabling rotation");
            rotating[pid] = 1;
            return T_OK;
        }
        if (W->level.type == LCAT_ADVENTURE && adventure::player && adventure::is_player_alive()) {
            robot_parts::tool *t = adventure::player->get_tool();
            if (t && (settings["touch_controls"]->v.b || pid == 0)) {
                if (t->action(ev->type, pid, tvec2f(ev->data.motion.x, ev->data.motion.y)) == EVENT_DONE)
                    return EVENT_DONE;
            }
        }

        int lvis = this->layer_vis;
        if (W->is_adventure() && this->caveview_size > 0.f) {
            lvis &= ~4;
        }

        W->query(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, &this->sel_p_ent, &this->sel_p_body, &this->sel_p_offs, &this->sel_p_frame, lvis);

        if (this->sel_p_ent) {
            if (this->player_can_build()) {
                if (this->check_quick_plug(diff, ev->data.motion.x, ev->data.motion.y)) {
                    if (this->interact_select(this->selection.e) == -1) {
                        tms_infof("idsable");
                        this->selection.disable();
                        this->set_mode(GAME_MODE_DEFAULT);
                    } else {
                        tms_debugf("aaaaaaa");
                    }
                }
            }

            if (this->sel_p_ent->g_id == O_CURSOR_FIELD) {
                ((cursorfield*)this->sel_p_ent)->pressed ++;
                ((cursorfield*)this->sel_p_ent)->dragged ++;
                in_cursorfield[pid] = (cursorfield*)this->sel_p_ent;
                drag_cursorfield[pid] = (cursorfield*)this->sel_p_ent;
            }
        }
    } else if (ev->type == TMS_EV_POINTER_DRAG) {
        if (this->menu_handle_event(ev))
            return T_OK;

        if (W->is_adventure()) {
            if (adventure::handle_input_playing(ev, action) == EVENT_DONE) {
                return EVENT_DONE;
            }
        }

        int pid = ev->data.motion.pointer_id;

        if (pid == 0)
            this->update_last_cursor_pos(ev->data.motion.x, ev->data.motion.y);

        tvec2 tdown = (tvec2){ev->data.motion.x, ev->data.motion.y};
        tvec2 td = (tvec2){tdown.x-touch_pos[pid].x, tdown.y-touch_pos[pid].y};

        if (!down[pid]) return T_OK;

        if (W->level.type == LCAT_ADVENTURE && adventure::player && adventure::is_player_alive()) {
            robot_parts::tool *t = adventure::player->get_tool();
            if (t && (settings["touch_controls"]->v.b || pid == 0)) {
                if (t->action(ev->type, pid, tvec2f(ev->data.motion.x, ev->data.motion.y)) == EVENT_DONE)
                    return EVENT_DONE;
            }
        }

        tvec3 tproj;
        W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, 0, &tproj);

        float td_mag = tvec2_magnitude(&td);

        if (zooming && (pid == 0 || pid == 1)) {
            if (!zoom_stopped) {
                touch_pos[pid] = tdown;
                touch_time[pid] = _tms.last_time;
                touch_proj[pid] = (tvec2){tproj.x, tproj.y};

                float dist = tvec2_dist(touch_pos[0], touch_pos[1]);
                float offs = dist - zoom_dist;
                zoom_dist = dist;
                if (settings["smooth_zoom"]->v.b) {
                    this->cam_vel.z -= offs * .25f * settings["zoom_speed"]->v.f;
                } else {
                    this->cam_vel.z -= offs * 2.5f * settings["zoom_speed"]->v.f;
                }
            }
        } else {
            if (in_cursorfield[pid]) {
                W->query(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, &this->sel_p_ent, &this->sel_p_body, &this->sel_p_offs, &this->sel_p_frame, this->layer_vis);

                if (this->sel_p_ent != drag_cursorfield[pid]) {

                    if (drag_cursorfield[pid] != 0)
                        drag_cursorfield[pid]->dragged --;

                    if (this->sel_p_ent != 0 && this->sel_p_ent->g_id == O_CURSOR_FIELD) {
                        drag_cursorfield[pid] = (cursorfield*)this->sel_p_ent;
                        drag_cursorfield[pid]->dragged ++;
                    } else
                        drag_cursorfield[pid] = 0;
                }

                return T_OK;
            }
            if (this->get_mode() == GAME_MODE_QUICK_PLUG) {
                if (this->selection.e) {
                    touch_proj[pid] = (tvec2){tproj.x, tproj.y};
                    touch_pos[pid] = tdown;
                    W->query(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, &this->sel_p_ent, &this->sel_p_body, &this->sel_p_offs, &this->sel_p_frame, this->layer_vis);

                    if (this->sel_p_ent && (!this->sel_p_ent->flag_active(ENTITY_IS_EDEVICE) || this->sel_p_ent == this->selection.e)) {
                        this->sel_p_ent = 0;
                    }
                    W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, this->selection.e->get_layer(), &touch_quickplug_pos);
                    //touch_time[pid] = _tms.last_time;
                    return T_OK;
                } else {
                    tms_debugf("quickplug, but we have no selection! canceling");
                    this->set_mode(GAME_MODE_DEFAULT);
                }
            }

            if (!dragging[pid]
                    && (!settings["touch_controls"]->v.b || td_mag > DRAG_DIST_MIN_EPS)
                    && (_tms.last_time - touch_time[pid] > DRAG_TIME_EPS
                        || td_mag > DRAG_DIST_EPS)) {

                if (!rotating[pid]
                        && (settings["touch_controls"]->v.b || pid == 0)
                        && this->sel_p_ent && (this->sel_p_ent->flag_active(ENTITY_IS_INTERACTIVE) || (W->level.type == LCAT_ADVENTURE && !this->sel_p_ent->flag_active(ENTITY_IS_STATIC)))) {
                    if (this->player_can_build() || (W->level.type != LCAT_ADVENTURE && (this->sel_p_ent->in_dragfield || W->level.flag_active(LVL_DO_NOT_REQUIRE_DRAGFIELD)))) {
                        tms_infof("SELECTED interactive object[%d] with pid %d", this->sel_p_ent->in_dragfield, pid);

                        if (!W->level.flag_active(LVL_DISABLE_INTERACTIVE)
                            && (!W->is_adventure() || adventure::is_player_alive())
                            ) {

                            int f;
                            if ((f = this->interact_select(this->sel_p_ent)) != -1) {
                                this->selection.select(this->sel_p_ent, this->sel_p_body, this->sel_p_offs, this->sel_p_frame, true);
                                this->sel_p_ent->on_grab_playing();

                                moving[pid] = true;
                                if (pid == 0) {
                                    current_interacting = f;
                                }
                            } else {
                            }
                        } else {
                            moving[pid] = true;
                            //down[pid] = false;
                            //moving[pid] = false;
                        }
                    } else {
                        moving[pid] = true;
                        down[pid] = false;
                        tms_debugf("object NOT in dragfield");
                    }
                } else if (rotating[pid] || resizing[pid]) {
                    moving[pid] = true;
                } else {
                    //tms_infof("entity NOT interactive");
                    moving[pid] = false;
                }

                dragging[pid] = true;
                //touch_time[pid] = _tms.last_time;
            }

            if (dragging[pid]) {
                if (!moving[pid]) {
                    tvec3 lastproj;
                    W->get_layer_point(this->cam, touch_pos[pid].x, touch_pos[pid].y, 0, &lastproj);

                    tvec2 diff = (tvec2){tproj.x, tproj.y};
                    diff = tvec2_sub(diff, (tvec2){lastproj.x, lastproj.y});

                    if (settings["smooth_cam"]->v.b) {
                        this->cam_vel.x -= diff.x * settings["cam_speed_modifier"]->v.f * 10.f;
                        this->cam_vel.y -= diff.y * settings["cam_speed_modifier"]->v.f * 10.f;
                    } else {
                        this->cam_move(
                                diff.x * settings["cam_speed_modifier"]->v.f,
                                diff.y * settings["cam_speed_modifier"]->v.f,
                                0);
                    }

                    touch_proj[pid] = (tvec2){tproj.x, tproj.y};
                    touch_pos[pid] = tdown;
                    //touch_time[pid] = _tms.last_time;
                } else {
                    /*
                    tvec3 pt;
                    W->get_layer_point(this->cam,
                            (int)ev->data.motion.x,
                            (int)ev->data.motion.y,
                            this->selection.e->get_layer(), &pt);
                            */
                    if (resizing[pid] && this->selection.e) {
                        tvec3 pos;
                        W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, this->selection.e->get_layer(), &pos);
                        this->handle_shape_resize(pos.x, pos.y);
                    } else if (rotating[pid] && this->selection.e) {
                        tvec3 pos;
                        W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, this->selection.e->get_layer(), &pos);
                        entity *re = this->selection.e;
                        float a = re->get_body(0)->GetAngle();
                        b2Vec2 p = re->get_body(0)->GetPosition();

                        b2Vec2 cs = b2Vec2(pos.x - p.x, pos.y - p.y);
                        cs *= 1.f/cs.Length();

                        float na = atan2f(cs.y, cs.x) + this->rot_offs;

                        /* Architect mode emulates the shift functionality,
                         * holding down shift when architect mode is active neutralizes the effect. */
                        bool shift_down = (this->shift_down() && !this->state.abo_architect_mode) || (!this->shift_down() && this->state.abo_architect_mode);
                        bool ctrl_down = this->ctrl_down();

                        // On touch platforms we include alternate snap-methods (holding a second finger down on the screen)
                        if (settings["touch_controls"]->v.b)
                            shift_down = shift_down || snap[0] || snap[1];

                        /**
                         * Holding down shift and ctrl produces 64-angle snapping
                         * Holding down shift produces 16-angle snapping
                         * Holding down ctrl produces 4-angle snapping
                         **/

                        if (shift_down && ctrl_down) {
                            na = na/(M_PI/32.f);
                            na = roundf(na);
                            na = na*(M_PI/32.f);
                        } else if (shift_down) {
                            na = na/(M_PI/8.f);
                            na = roundf(na);
                            na = na*(M_PI/8.f);
                        } else if (ctrl_down) {
                            na = na/(M_PI/2.f);
                            na = roundf(na);
                            na = na*(M_PI/2.f);
                        }

                        float da = tmath_adist(a, na);

                        for (int x=0; x<MAX_INTERACTING; x++) {
                            if (mover_joint[x] && mover_joint[x]->GetBodyB() == re->get_body(0)) {
                                mover_joint[x]->SetAngularOffset(re->get_body(0)->GetAngle()+da);
                                break;
                            }
                        }
                    } else if (this->selection.e) {
                        touch_pos[pid] = (tvec2){ev->data.motion.x, ev->data.motion.y};

                        if (pid == 0 && current_interacting != -1) {
                            tvec3 tproj;
                            W->get_layer_point(this->cam, touch_pos[pid].x, touch_pos[pid].y, 0, &tproj);

                            if (mover_joint[current_interacting]) {
                                if (interacting[current_interacting]->g_id == O_LADDER_STEP) {
                                    sel_p_offs.x = 0;
                                    sel_p_offs.y = 0;
                                    tproj.x *= 2.f;
                                    tproj.y *= 2.f;
                                    tproj.x = roundf(tproj.x+.5f);
                                    tproj.y = roundf(tproj.y);
                                    tproj.x /= 2.f;
                                    tproj.y /= 2.f;
                                    tproj.x -= .25f;
                                }
                                mover_joint[current_interacting]->SetLinearOffset(b2Vec2(tproj.x-sel_p_offs.x, tproj.y-sel_p_offs.y));
                            }
                        }
                    }
                }
            } else {
                //tms_infof("not dragging");
            }
        }

    } else if (ev->type == TMS_EV_POINTER_UP) {
        int pid = ev->data.motion.pointer_id;
        int curr_was = current_interacting;
        if (pid == 0) {
            current_interacting = -1;

            if (curr_was != -1 && interacting[curr_was] && interacting[curr_was]->g_id == O_LADDER_STEP) {
                ladder_step *ls = static_cast<ladder_step*>(interacting[curr_was]);

                if (ls->emitted_by && adventure::player && ls->emitted_by == adventure::player->id) {
                    ls->find_pairs();
                    if (ls->has_pair) {
                        tms_debugf("has_pair");
                        G->apply_connection(&ls->c_back, -1);
                    } else {
                        tms_debugf("does not have pair ");
                    }

                    ls->emitted_by = 0;
                    G->drop_interacting();
                }
            }
        }

        if (this->menu_handle_event(ev)) {
            return T_OK;
        }

        if (W->is_adventure()) {
            if (adventure::handle_input_playing(ev, action) == EVENT_DONE)
                return EVENT_DONE;
        }

        if (!settings["touch_controls"]->v.b) {
            if (pid == 0) {
                this->update_last_cursor_pos(ev->data.motion.x, ev->data.motion.y);
                W->events[WORLD_EVENT_CLICK_UP] ++;
            }
        } else {
            if (pid == 0) {
                this->update_last_cursor_pos(ev->data.motion.x, ev->data.motion.y);
            }
            W->events[WORLD_EVENT_CLICK_UP] ++;
        }

        if (!down[pid]) return T_OK;

        down[pid] = false;
        //interacting[pid] = 0;
        //

        if (W->level.type == LCAT_ADVENTURE && adventure::player && adventure::is_player_alive()) {
            robot_parts::tool *t = adventure::player->get_tool();
            if (t && (settings["touch_controls"]->v.b || pid == 0)) {
                if (t->action(ev->type, pid, tvec2f(ev->data.motion.x, ev->data.motion.y)) == EVENT_DONE)
                    return EVENT_DONE;
            }
        }

        if (in_cursorfield[pid]) {
            if (drag_cursorfield[pid]) {
                drag_cursorfield[pid]->dragged --;
                drag_cursorfield[pid] = 0;
            }
            in_cursorfield[pid]->pressed --;
            in_cursorfield[pid] = 0;
            return T_OK;
        }

        entity *e = 0;
        b2Body *b;
        tvec2 offs;
        uint8_t frame;

        W->query(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, &e, &b, &offs, &frame, this->layer_vis);

        if (e && e->handle_event(ev->type, pid, tvec2f(ev->data.motion.x, ev->data.motion.y)) == EVENT_DONE)
            return EVENT_DONE;

        if (zooming && (pid == 0 || pid == 1)) {
            zoom_stopped = true;
            if (!down[0] && !down[1])
                zooming = false;
        } else {
            if (this->get_mode() == GAME_MODE_QUICK_PLUG) {
                this->sel_p_ent = e;
                this->sel_p_body = b;
                this->sel_p_offs = offs;
                this->sel_p_frame = frame;

                this->set_mode(GAME_MODE_DEFAULT);

                if (this->sel_p_ent && this->sel_p_ent->flag_active(ENTITY_IS_EDEVICE) && this->sel_p_ent != this->selection.e && this->selection.e) {
                    tms_infof("quickplug detected: %s", this->sel_p_ent->get_name());

                    for (int t=0; t<3; t++) {
                        int m1 = this->selection.e->get_edevice()->get_inout_mask(t);
                        int m2 = this->sel_p_ent->get_edevice()->get_outin_mask(t);

                        if ((m1 & m2)) {
                            this->open_socket_selector(this->selection.e, this->sel_p_ent->get_edevice());
                            tms_infof("compatible");
                            break;
                        }
                    }
                } else
                    tms_infof("nothing detected");

                return T_OK;
            }

            if (!dragging[pid]) {
                W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, 0, &pt[0]);
                W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, 1, &pt[1]);
                W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, 2, &pt[2]);
                W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y,
                         + .5f,
                        &half_pt[0]);
                W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y,
                        1.f * LAYER_DEPTH + .5f,
                        &half_pt[1]);
                W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y,
                        2.f * LAYER_DEPTH + .5f,
                        &half_pt[2]);

                if (W->is_adventure()) {
                    if (adventure::is_player_alive()) {
                        std::set<activator*>::iterator it = adventure::player->activators.begin();
                        b2Vec2 click = b2Vec2(pt[adventure::player->get_layer()].x, pt[adventure::player->get_layer()].y);

                        activator *nearest = 0;
                        float nearest_dist = INFINITY;
                        for (;it != adventure::player->activators.end(); ++it) {
                            activator *act = *it;
                            b2Vec2 ipos = act->get_activator_pos();

                            float dist = (ipos - click).Length();

                            if (dist < .575f) {
                                if (dist < nearest_dist) {
                                    nearest = act;
                                    nearest_dist = dist;
                                }
                            }
                        }

                        if (nearest) {
                            adventure::player->activate_activator(nearest);
                            return T_OK;
                        }
                    }

                    if (this->player_can_build()) {
                        if (this->get_mode() == GAME_MODE_SELECT_SOCKET) {
                            if (this->check_click_socksel()) {
                                return T_OK;
                            }
                        }
                        if (this->get_mode() == GAME_MODE_SELECT_OBJECT) {
                            this->check_select_object(ev->data.motion.x, ev->data.motion.y, pid);
                            return T_OK;
                        }

                        tms_infof("checking");

                        if (this->get_mode() == GAME_MODE_SELECT_CONN_TYPE) {
                            if (this->check_click_conntype(ev->data.motion.x, ev->data.motion.y)) {
                                return T_OK;
                            }
                        }

                        if (this->check_click_conn(ev->data.motion.x, ev->data.motion.y)) {
                            return T_OK;
                        }

                        if (this->interact_select(this->sel_p_ent) == -1) {
                            this->drop_interacting();
                        } else {
                            this->selection.select(this->sel_p_ent, this->sel_p_body, this->sel_p_offs, this->sel_p_frame, false);
                        }
                    }

                } else {
                    this->selection.select(this->sel_p_ent, this->sel_p_body, this->sel_p_offs, this->sel_p_frame, false);
                    if (this->selection.e) {
                        if (this->selection.e->flag_active(ENTITY_IS_CONTROL_PANEL))
                            this->set_control_panel(this->selection.e);
                    }
                }
            } else {
                if (curr_was != -1 && interacting[curr_was]) {
                    if (interacting[curr_was]->get_body(0)->GetLinearVelocity().LengthSquared() > 12.f && (b2Vec2(pt[interacting[curr_was]->get_layer()].x, pt[interacting[curr_was]->get_layer()].y) - interacting[curr_was]->get_position()).Length()>1.5f) {
                        if (interacting[curr_was] == this->selection.e) {
                            this->selection.disable();
                        }
                        this->destroy_mover(curr_was);
                    }
                }
            }
        }
    } else if (ev->type == TMS_EV_POINTER_MOVE) {
        if (this->menu_handle_event(ev) == EVENT_DONE)
            return EVENT_DONE;

        if (W->is_adventure()) {
            if (adventure::handle_input_playing(ev, action) == EVENT_DONE) {
                return EVENT_DONE;
            }
        }

        tvec2 pt = tvec2f(ev->data.motion.x, ev->data.motion.y);

        if (this->active_hori_wdg || this->active_vert_wdg) {
            float x_diff = pt.x - this->wdg_base_x;
            float y_diff = pt.y - this->wdg_base_y;

            float x_value_diff = (x_diff / (float)_tms.opengl_width * 2.f) * settings["widget_control_sensitivity"]->v.f;
            float y_value_diff = (y_diff / (float)_tms.opengl_height * 2.f) * settings["widget_control_sensitivity"]->v.f;

            if (this->active_hori_wdg) {
                panel::widget *wdg = this->active_hori_wdg;
                switch (wdg->type) {
                    case TMS_WDG_SLIDER:
                        //this->wdg_base_x = pt.x;
                        wdg->value[0] = tclampf(wdg->value[0] + x_value_diff, 0.f, 1.f);
                        SDL_WarpMouseInWindow((SDL_Window*)_tms._window, G->wdg_base_x, G->wdg_base_y);
                        break;
                }
            }

            if (this->active_vert_wdg) {
                panel::widget *wdg = this->active_vert_wdg;

                switch (wdg->type) {
                    case TMS_WDG_VSLIDER:
                        //this->wdg_base_y = pt.y;
                        wdg->value[0] = tclampf(wdg->value[0] + y_value_diff, 0.f, 1.f);
                        SDL_WarpMouseInWindow((SDL_Window*)_tms._window, G->wdg_base_x, G->wdg_base_y);
                        break;

                    case TMS_WDG_FIELD:
                        wdg->value[0] = tclampf(wdg->value[0] + x_value_diff, 0.f, 1.f);
                        wdg->value[1] = tclampf(wdg->value[1] + y_value_diff, 0.f, 1.f);

                        SDL_WarpMouseInWindow((SDL_Window*)_tms._window, G->wdg_base_x, G->wdg_base_y);
                        break;

                    case TMS_WDG_RADIAL:
                        {
#define REQUIRE_RADIAL_THRESHOLD

                            pt.x -= this->wdg_base_x;
                            pt.y -= this->wdg_base_y;
#ifdef REQUIRE_RADIAL_THRESHOLD
                            const float ax = std::abs(pt.x);
                            const float ay = std::abs(pt.y);
                            const float len = tmath_sqrt(ax*ax+ay*ay);
                            static const float radial_threshold = 0.200f * _tms.xppcm;
                            if (len > radial_threshold) {
#endif
                                float a = atan2(pt.y, pt.x) + (M_PI*2.f);

                                const bool shift_down = this->shift_down();
                                const bool ctrl_down = this->ctrl_down();

                                if (shift_down && ctrl_down) {
                                    a = a/(M_PI/32.f);
                                    a = roundf(a);
                                    a = a*(M_PI/32.f);
                                } else if (shift_down) {
                                    a = a/(M_PI/8.f);
                                    a = roundf(a);
                                    a = a*(M_PI/8.f);
                                } else if (ctrl_down) {
                                    a = a/(M_PI/2.f);
                                    a = roundf(a);
                                    a = a*(M_PI/2.f);
                                }

                                a = fmodf(a, M_PI*2.);
                                a = fabs(a) / (M_PI*2);

                                wdg->value[0] = a;
#ifdef REQUIRE_RADIAL_THRESHOLD
                            }
#endif
                        }
                        break;
                }
            }
        }
    } else if (ev->type == TMS_EV_POINTER_SCROLL) {
        if (this->menu_handle_event(ev))
            return T_OK;

        float z = ev->data.scroll.y < 0 ? -2.f : 2.f;

        if (settings["smooth_zoom"]->v.b) {
            this->cam_vel.z -= (z*2.f) * settings["zoom_speed"]->v.f;
        } else {
            this->cam_move(
                    0,
                    0,
                    z * settings["zoom_speed"]->v.f);
        }
    }

    return T_OK;
}

void
game::proceed()
{
    tms_infof("Proceeding from %d", W->level.local_id);
    if (this->state.pkg != 0) {
        tms_infof("curr pkg %p", this->state.pkg);
        uint32_t next = this->state.pkg->get_next_level(W->level.local_id);

        this->previous_level = this->state.pkg->get_level_index(W->level.local_id);
        if (next == 0) {
            tms_infof("completed all levels");

            tms::set_screen(P.s_menu_pkg);
        } else  {
            if (this->state.pkg->first_is_menu) {
                /* TODO: the level selector should prevent to main menu,
                 * and if you're in a level you should return to the level selector. */
                //this->screen_back = P.s_menu_pkg;
            }

            this->open_play(this->state.pkg->type, next, this->state.pkg, false);
        }

        if (this->state.pkg->first_is_menu) {
            this->state.waiting = false;
        }

        return;
    } else {
        this->do_pause();
    }
}

void
game::finish(bool success)
{
    if (!this->state.finished) {
        W->save_cache(W->level_id_type, W->level.local_id);

        this->state.finish_step = W->step_count;
        this->state.finished = true;
        this->state.success = success;

        uint8_t level_id_type = W->level_id_type;

        if (level_id_type >= LEVEL_LOCAL_STATE) {
            level_id_type -= LEVEL_LOCAL_STATE;
        }

        if (G->state.is_main_puzzle) {
            level_id_type = LEVEL_MAIN;
        }

        lvl_progress *p = 0;

        if (level_id_type < LEVEL_LOCAL_STATE) {
            p = progress::get_level_progress(level_id_type, W->level.local_id);
        }

        if (success) {
            if (p) {
                p->completed = 1;
            }

            W->events[WORLD_EVENT_LEVEL_COMPLETED] ++;
        } else {
            W->events[WORLD_EVENT_GAME_OVER] ++;
        }

        if (p && (success || W->level.flag_active(LVL_STORE_SCORE_ON_GAME_OVER))) {
            p->last_score = this->get_real_score();
            tms_debugf("Last score set to %d", p->last_score);

            if (W->level.flag_active(LVL_LOWER_SCORE_IS_BETTER)) {
                if (p->last_score < p->top_score || p->top_score == 0) {
                    tms_debugf("%d < %d || %d == 0, top score set", p->last_score, p->top_score, p->top_score);
                    p->top_score = p->last_score;
                }
            } else if (p->last_score > p->top_score) {
                tms_debugf("%d > %d, top score set", p->last_score, p->top_score);
                p->top_score = p->last_score;
            }
        }

        if ((W->level.pause_on_finish || !success) && !W->level.flag_active(LVL_DISABLE_ENDSCREENS)) {
            sm::stop_all();
            this->state.waiting = true;
        } else {
            //this->proceed();
        }

        if (p) {
            progress::commit();
        }

        if (!W->level.flag_active(LVL_DISABLE_ENDSCREENS)) {
            if (success) {
                sm::play(&sm::win, 0.f, 0.f, 0, 1.f, false, 0, true);
            } else {
                sm::play(&sm::lose, 0.f, 0.f, 0, 1.f, false, 0, true);
            }

        }

        if (!this->state.submitted_score && W->level.flag_active(LVL_ALLOW_HIGH_SCORE_SUBMISSIONS)
                && W->level_id_type == LEVEL_DB
                && p
                && (settings["score_automatically_submit"]->v.b || W->level.flag_active(LVL_AUTOMATICALLY_SUBMIT_SCORE))) {
            this->submit_score();
        }

        tms_infof("game FINISH");
    }

    this->refresh_widgets();
}

void
game::open_play(int id_type, uint32_t id, pkginfo *pkg, bool test_playing/*=false*/, int is_main_puzzle/*=0*/)
{
    tms_infof("playing level %d, type %d", id, id_type);

    this->layer_vis_saved = this->layer_vis;
    this->layer_vis = 7;

    this->reset();

    if (this->state.pkg != pkg) {
        this->previous_level = 0;
    }
    this->state.pkg = pkg;
    this->state.sandbox = false;
    this->state.test_playing = test_playing;
    this->opened_special_level = 0;

    bool paused = false;

    if (is_main_puzzle == 1 || G->state.puzzle_state == 1) {
        paused = true;
    }

    G->state.is_main_puzzle = (is_main_puzzle > 0);

    if (W->open(id_type, id, paused, false)) {
        lvl_progress *p = progress::get_level_progress(id_type, id);

        if (p) {
            tms_infof("completion score: %u",p->top_score);
            tms_infof("last score: %u",p->last_score);
            tms_infof("num plays: %u",p->num_plays);
            p->num_plays ++;
        }
    }

    if (W->is_puzzle() && id_type == LEVEL_DB && G->state.puzzle_state == 0) {
        G->state.puzzle_state = 1;
        W->save();
        this->open_play(id_type, id, pkg, test_playing, is_main_puzzle);
        return;
    }

#ifndef SCREENSHOT_BUILD
    if (!W->is_puzzle()) {
        if (just_paused || (!this->state.sandbox && !W->level.flag_active(LVL_DISABLE_INITIAL_WAIT) && !this->state.test_playing)) {
            this->state.waiting = true;
        }
    }
#endif

    just_paused = false;

    this->apply_level_properties();
    this->add_entities(&W->all_entities, &W->groups, &W->connections, &W->cables);

    W->begin();
    this->begin_play();

#ifndef SCREENSHOT_BUILD
    if (test_playing) {
        this->state.fade = 0.2f;
    } else {
        this->state.fade = 1.0f;
    }
#endif

    this->refresh_widgets();
}

/**
 * Prepare playing the world, this is always called after a level
 * has been opened for playing
 **/
void
game::begin_play(bool has_state)
{
    if (W->is_adventure()) {
        if (this->state.adventure_id != 0) {
            entity *e = W->get_entity_by_id(this->state.adventure_id);

            if (e && e->flag_active(ENTITY_IS_CREATURE)) {
                creature *c = static_cast<creature*>(e);

                /* if we have a state, the cam pos and everything else is set
                 * from that, otherwise we just call adventure::set_player and it will
                 * set everything up for us */
                if (has_state) {
                    adventure::player = c;
                } else {
                    adventure::set_player(c, true);
                }
            }
        }

        adventure::setup(); /* XXX TODO state handling of resources */
    }

    this->refresh_widgets();
}

void
game::create_level(int type, bool empty, bool play)
{
    switch (type) {
        case LCAT_PUZZLE:     tms_infof("Creating a Puzzle level"); break;
        case LCAT_ADVENTURE:  tms_infof("Creating an Adventure level"); break;
        case LCAT_CUSTOM:     tms_infof("Creating a Custom level"); break;
    }

    this->reset();
    this->state.sandbox = !play;
    this->state.test_playing = false;
    this->state.puzzle_state = 0;

    uint64_t seed = 0;

    if (!empty) {
        init_genrand(clock());
        seed = (((uint64_t)genrand_int32()) << 32ull) | ((uint64_t)genrand_int32());
    }

    W->create(type, seed, play);

    this->apply_level_properties();
    this->add_entities(&W->all_entities, &W->groups, &W->connections, &W->cables);
    W->begin();

    if (!empty && play) {
        sprintf(W->level.name, "Random Adventure");
        W->level.name_len = strlen("Random Adventure");
    }
}

void
game::open_state(int id_type, uint32_t id, uint32_t save_id)
{
    if (id_type < LEVEL_LOCAL_STATE) {
        id_type += LEVEL_LOCAL_STATE;
    }

    tms_infof("opening state %u of %d level %u", save_id, id_type, id);

    bool test = this->state.test_playing;

    this->reset();
    W->open(id_type, id, false, false, save_id);
    this->apply_level_properties();
    this->load_state();
    this->add_entities(&W->all_entities, &W->groups, &W->connections, &W->cables);
    W->begin();
    this->begin_play(true);

    this->state.waiting = false;
    this->state.test_playing = test;
}

void
game::open_sandbox(int id_type, uint32_t id)
{
    this->reset();
    this->state.sandbox = true;
    this->state.puzzle_state = 0;

    if (id == 0 && id_type == LEVEL_LOCAL) {
        /* open autosave */
        W->open_autosave();
    } else {
        W->open(id_type, id, true, true);
    }

    this->apply_level_properties();
    this->add_entities(&W->all_entities, &W->groups, &W->connections, &W->cables);
    W->begin();

    this->refresh_widgets();
}

bool
game::delete_level(int id_type, uint32_t id, uint32_t save_id)
{
    char path[1024];
    pkgman::get_level_full_path(id_type, id, save_id, path);

    return (unlink(path) == 0);
}

bool
game::delete_partial(uint32_t id)
{
    char path[1024];
    snprintf(path, 1023, "%s/%d.pobj", pkgman::get_level_path(LEVEL_LOCAL), id);
    tms_debugf("Deleting partial %u at %s", id, path);

    return (unlink(path) == 0);
}

void
game::save_state()
{
    if (W->level.version < LEVEL_VERSION_1_5) {
        ui::message("State saving not supported in levels created with Principia<1.5");
        return;
    }
    tms_debugf("saving state");
    W->level.sandbox_cam_x = this->cam->_position.x;
    W->level.sandbox_cam_y = this->cam->_position.y;
    W->level.sandbox_cam_zoom = this->cam->_position.z;
    W->save(SAVE_TYPE_STATE);
}

bool
game::autosave()
{
    if (!this->state.sandbox) {
        tms_debugf("Can't save while outside the sandbox.");
        return false;
    }

    this->state.modified = false;

    W->level.sandbox_cam_x = this->cam->_position.x;
    W->level.sandbox_cam_y = this->cam->_position.y;
    W->level.sandbox_cam_zoom = this->cam->_position.z;

    return W->save(SAVE_TYPE_AUTOSAVE);
}

bool
game::save(bool create_icon/*=true*/, bool force/*=false*/)
{
    if (!this->state.sandbox && !force) {
        tms_debugf("Can't save while outside the sandbox.");
        return false;
    }

    this->state.modified = false;

    W->level.sandbox_cam_x = this->cam->_position.x;
    W->level.sandbox_cam_y = this->cam->_position.y;
    W->level.sandbox_cam_zoom = this->cam->_position.z;

    if (create_icon) {
        tms_infof("Creating level icon...");
        this->create_icon();
    }

    return W->save();
}

bool
game::save_copy()
{
    W->level.local_id = 0;
    W->level.parent_id = W->level.community_id;
    W->level.community_id = 0;

    return this->save();
}

void
game::select_random_entity()
{
    entity *e = 0;
    size_t sz = W->all_entities.size();

    do {
        if (sz > 0) {
            std::map<uint32_t, entity*>::iterator i = W->all_entities.begin();
            std::advance(i, rand()%sz);
            e = static_cast<entity*>(i->second);
            this->cam->_position.x = e->get_position().x;
            this->cam->_position.y = e->get_position().y;

            this->selection.select(e, NULL, (tvec2){0,0}, 0, false);
        } else {
            return;
        }
    } while (!e || e->g_id != O_MINI_TRANSMITTER);
}

void
game::snap_to_camera(screenshot_marker *sm)
{
    if (sm->is_hidden()) {
        this->cam->_position.x = sm->saved_position.x;
        this->cam->_position.y = sm->saved_position.y;
        this->cam->_position.z = sm->properties[0].v.f;
    } else {
        this->cam->_position.x = sm->get_position().x;
        this->cam->_position.y = sm->get_position().y;
        this->cam->_position.z = sm->properties[0].v.f;
    }

    /* Reset current camera movement */
    this->cam_vel.x = 0.f;
    this->cam_vel.y = 0.f;
    this->cam_vel.z = 0.f;
}

bool
game::player_can_build()
{
    return W->level.type == LCAT_ADVENTURE && adventure::player && adventure::is_player_alive()
            && adventure::player->get_tool() && adventure::player->get_tool_type() == TOOL_BUILDER;
}

/**
 * Create an icon for the current level
 **/
void
game::create_icon()
{
    GLuint err;
    float cam_width = (float)_tms.window_height;
    float cam_height = (float)_tms.window_height;

    this->cam->enable(TMS_CAMERA_PERSPECTIVE);
    this->cam->width = cam_width;
    this->cam->height = cam_height;
    this->cam->calculate();

    tvec3 dd = tms_camera_project(this->cam, this->cam->_position.x, this->cam->_position.y, LAYER_DEPTH*1.f);

    tvec3 top = tms_camera_unproject(this->cam, 0.f, cam_height, dd.z);

    this->cam->disable(TMS_CAMERA_PERSPECTIVE);

    this->cam->width = fabsf(top.x - this->cam->_position.x)*2.f;
    this->cam->height = fabsf(top.y - this->cam->_position.y)*2.f;
    this->cam->owidth = cam_width;
    this->cam->oheight = cam_height;

    this->cam->calculate();

    settings["render_gui"]->set(false);

    tms_fb_bind(this->icon_fb);
    this->render();

    if ((err = glGetError()) != 0) tms_infof("opengl error in icon creation rendering: %u", err);

    glViewport(0,0,_tms.opengl_width, _tms.opengl_height);

    SDL_Surface *srf = SDL_CreateSurface(512, 512,
            SDL_GetPixelFormatForMasks(32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000));
    glReadPixels(0, 0, 512, 512, GL_RGBA, GL_UNSIGNED_BYTE, srf->pixels);

    if ((err = glGetError()) != 0) tms_infof("glReadPixels: %u", err);

    SDL_Surface *n = zoomSurface(srf, .25, .25);

    for (int y=0; y<128; y++) {
        for (int x=0; x<128; x++) {
            int C = 60;

            float _r = ((float)((unsigned char*)n->pixels)[y * n->pitch + x*4]) * .299f;
            float _g = ((float)((unsigned char*)n->pixels)[y * n->pitch + x*4+1]) * .587f;
            float _b = ((float)((unsigned char*)n->pixels)[y * n->pitch + x*4+2]) * .114f;

            float lum = (_r+_g+_b);

            int c = (int) (lum)/ 2;
            int f = (259*(C+255))/(255*(259-C));
            int new_c = f*(c-128)+128;
            if (new_c < 0) new_c = 0;
            if (new_c > 255) new_c = 255;
            W->level.icon[y*128+x] = (uint8_t)new_c&0xff;
        }
    }
    SDL_DestroySurface(n);

    SDL_DestroySurface(srf);

    tms_fb_unbind(this->icon_fb);

#ifndef NO_UI
    settings["render_gui"]->set(true);
#endif

    if ((err = glGetError()) != 0) tms_infof("icon creation done: %u", err);
}

/* resize the current level to fit the borders around the content */
void
game::fit_level_borders()
{
    float min_x = -100, max_x = 100, min_y = 100, max_y = 100;

    /* calculate bounds wants a std set, we have a map... bad luck */
    std::set<entity*> entities;

    for (std::map<uint32_t, entity*>::iterator i = W->all_entities.begin();
            i != W->all_entities.end(); i++) {
        entities.insert(i->second);
    }

    W->calculate_bounds(&entities, &min_x, &max_x, &min_y, &max_y);

    min_x = -roundf(fminf(0.f, min_x))+3;
    max_x = roundf(fmaxf(0.f, max_x))+3;
    min_y = -roundf(fminf(0.f, min_y))+3;
    max_y = roundf(fmaxf(0.f, max_y))+3;

    W->level.size_x[0] = (uint16_t)min_x;
    W->level.size_x[1] = (uint16_t)max_x;
    W->level.size_y[0] = (uint16_t)min_y;
    W->level.size_y[1] = (uint16_t)max_y;

    this->apply_level_properties();

    tms_infof("borders: %f %f %f %f", min_x, max_x, min_y, max_y);

    ui::emit_signal(SIGNAL_REFRESH_BORDERS);
}

void
game::select_import_object(uint32_t id)
{
    if (this->multi.import) {
        delete this->multi.import;
        this->multi.import = 0;
    }

    this->multi.import = new lvledit;

    if (this->multi.import->open(LEVEL_PARTIAL, id)) {
        if (this->multi.import->lvl.type != LCAT_PARTIAL) {
            tms_errorf("Could not load partial (game::select_import_object)");
            delete this->multi.import;
            this->multi.import = 0;

            return;
        }

        this->multi.cursor_size.x = this->multi.import->lvl.max_x - this->multi.import->lvl.min_x + .5f;
        this->multi.cursor_size.y = this->multi.import->lvl.max_y - this->multi.import->lvl.min_y + .5f;

        this->refresh_info_label();
    } else {
        delete this->multi.import;
        this->multi.import = 0;
    }

    this->refresh_widgets();
}

void
game::import_object(uint32_t id)
{
    std::map<uint32_t, entity*> entities;
    std::map<uint32_t, group*> groups;
    std::set<connection*> connections;
    std::set<cable*> cables;

    W->load_partial(id, this->multi.cursor, &entities, &groups, &connections, &cables);
    this->add_entities(&entities, &groups, &connections, &cables);
}

void
game::export_object(const char *name)
{
    if (this->selection.m) {
        W->save_partial(this->selection.m, name, pkgman::get_next_object_id());
    }
}

void
game::handle_draw(int pid, int mx, int my)
{
    tvec3 tproj;
    W->get_layer_point(this->cam, mx, my, 0, &tproj);
    int depth = this->state.edit_layer;
    int z = 0;

    if (!this->brush_layer_inclusion) {
        z = depth;
    }

    for (; z<depth+1; z++) {

        int cx = (int)floor(tproj.x*2);
        int cy = (int)floor(tproj.y*2);

        int size = (this->brush+1)*6;

        std::set<level_chunk *> affected;

        for (int h=-size/2; h<=size/2; h++) {
            for (int w=-size/2; w<=size/2; w++) {
                affected.insert(W->cwindow->set_pixel(cx+w, cy+h, z, this->brush_material));
            }
        }

        for (std::set<level_chunk*>::iterator i = affected.begin(); i != affected.end(); i++) {
            (*i)->remerge();
            W->to_be_reloaded.insert((*i));
        }
    }
}

void
game::numkey_pressed(uint8_t key)
{
    if (this->get_mode() == GAME_MODE_MULTISEL) {
        principia_wdg *wdg = this->wm->get_widget(AREA_BOTTOM_LEFT, key);

        if (wdg && wdg->_type == TMS_WDG_BUTTON) {
            wdg->click();
        }
    } else {
        if (key > MAX_COPY_ENTITIES) {
            tms_errorf("handle_slot called with slot %u, even though the max number of copy entities is %d", key, MAX_COPY_ENTITIES);
            return;
        }

        if (this->shift_down()) {
            this->copy_properties(this->selection.e, copy_entity[key], true);
        } else {
            this->set_copy_entity(key, this->selection.e);
        }
    }
}

/**
 * Available keybindings.
 *
 * I            Show help about selected object.
 *
 * CTRL+S       Save level
 * U            Save level
 * CTRL+O       Open "open level"-dialog
 * CTRL+N       New level
 * CTRL+Q       Quit
 * Space        Quickadd, or confirm socket selection (choosing first available)
 * J            Publish level
 * R            Toggle rotate entity mode
 * G            Toggle grab entity mode
 * P            Play level
 *
 * WASD         Move camera
 * M/N          Zoom camera
 * Shift+M      Toggle multiselect mode
 * B            Back to main menu
 *
 * Del          Delete selected object
 * Shift+Del    Delete selected multiobject
 *
 * Page Down    Move selected entity one layer forward (looping)
 * X            Move selected entity one layer forward (looping)
 * Z            Move selected entity one layer backward (looping)
 *
 * SHIFT+Q      Disconnect all
 * SHIFT+E      Connect all
 * T            Emulate socket disconnect button click
 * Y            Emulate config button click
 * H            "Connection selection"
 * Comma        Duplicate selected entity
 * 1-5          Copy selected entity properties
 * Shift+1-5    Paste copied properties on to selected entity
 * F5           Restore camera position
 * CTRL+F7      Select random entity
 *
 * Debug bindings:
 * F1           Enable debug draw
 * F2           Toggle robot roaming
 * CTRL+F8      Save copy
 * CTRL+F9      Clamp IDs of current level
 * CTRL+F10     Open next UI dialog
 * CTRL+F11     Reload graphics
 * CTRL+F12     Delete all loose cables and plugs
 * O            Open latest saved level
 **/
static const float CAM_NORMAL_JUMP  = 0.25f;
static const float CAM_BIG_JUMP     = 1.f;

int
game::handle_input_paused(tms::event *ev, int action)
{
    if (ev->type == TMS_EV_KEY_DOWN) {
        if (this->menu_handle_event(ev)) {
            return EVENT_DONE;
        }

        switch (ev->data.key.keycode) {
            case TMS_KEY_A:
                this->cam->_position.x -= (this->shift_down() ? CAM_BIG_JUMP : CAM_NORMAL_JUMP);
                break;
            case TMS_KEY_D:
                this->cam->_position.x += (this->shift_down() ? CAM_BIG_JUMP : CAM_NORMAL_JUMP);
                break;
            case TMS_KEY_W:
                this->cam->_position.y += (this->shift_down() ? CAM_BIG_JUMP : CAM_NORMAL_JUMP);
                break;
            case TMS_KEY_S:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    // Prevent camera from moving when saving with Ctrl+S
                } else {
                    this->cam->_position.y -= (this->shift_down() ? CAM_BIG_JUMP : CAM_NORMAL_JUMP);
                }

                break;
            case TMS_KEY_V:
#ifdef DEBUG
#ifdef SINGLE_STEP_WORLD
                tms_debugf("STEPPING WORLD");
                W->b2->Step(.001f, 20, 20);
#endif
#endif
                break;

            case TMS_KEY_MINUS: this->cam->_position.z += 1.f; break;
            case TMS_KEY_EQUALS:this->cam->_position.z -= 1.f; break;
        }
    } else if (ev->type == TMS_EV_KEY_PRESS) {
        if (this->menu_handle_event(ev) == EVENT_DONE) {
            return EVENT_DONE;
        }

        switch (ev->data.key.keycode) {
            case TMS_KEY_LEFT_SHIFT:
            case TMS_KEY_RIGHT_SHIFT:
                if (this->get_mode() == GAME_MODE_MULTISEL) {
                    this->multi.additive_selection = !this->multi.additive_selection;
                    this->wdg_additive->faded = !this->multi.additive_selection;
                }
                break;

            case TMS_KEY_F7:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    disable_menu = true;
                    this->select_random_entity();
                }
                break;

#ifdef DEBUG
            case TMS_KEY_F6:
                if (ev->data.key.mod & TMS_MOD_SHIFT) {
                    G->create_level(LCAT_ADVENTURE, true, false);
                } else {
                    tms_infof("Seed: 0x%016" PRIx64, W->level.seed);
                }
                break;

            case TMS_KEY_F8:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    disable_menu = true;
                    this->save_copy();
                }
                break;

            case TMS_KEY_F9:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    disable_menu = true;
                    this->clamp_entities();
                }
                break;

            case TMS_KEY_F10:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    disable_menu = true;
                    static int cur_dialog = 115;
                    tms_debugf("Opening dialog %d", cur_dialog);
                    ui::open_dialog(cur_dialog ++);
                }
                break;

            case TMS_KEY_F11:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    disable_menu = true;
                    P.can_reload_graphics = true;
                    P.can_set_settings = true;
                    P.add_action(ACTION_RELOAD_GRAPHICS, 0);
                }
                break;

            case TMS_KEY_F12:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    disable_menu = true;

                    std::set<entity*> entities_to_remove;

                    for (std::set<cable*>::iterator it = W->cables.begin(); it != W->cables.end(); ++it) {
                        cable *c = static_cast<cable*>(*it);
                        entity *e = static_cast<entity*>(c);
                        if (!c->p[0]->s && !c->p[1]->s) {
                            entities_to_remove.insert(e);
                        }
                    }

                    for (std::map<uint32_t, entity*>::iterator it = W->all_entities.begin(); it != W->all_entities.end(); ++it) {
                        entity *e = it->second;
                        if (e->g_id == O_JUMPER || e->g_id == O_RECEIVER || e->g_id == O_MINI_TRANSMITTER) {
                            plug_base *pb = static_cast<plug_base*>(e);
                            if (!pb->is_connected()) {
                                entities_to_remove.insert(e);
                            }
                        }
                    }

                    for (std::set<entity*>::iterator it = entities_to_remove.begin(); it != entities_to_remove.end(); ++it) {
                        entity *e = *it;
                        this->selection.select(e);
                        this->delete_selected_entity();
                    }
                }
                break;
#endif

            /* I: Show help about selected object */
            case TMS_KEY_I:
                if (this->selection.e) {
                    this->info_btn_pressed(this->selection.e);
                }
                break;

            case TMS_KEY_SPACE:
                /* confirm whichever task might be available */
                if (this->get_mode() == GAME_MODE_SELECT_SOCKET && this->ss_num_socks > 0) {
                    /* if there are more than one socket available, choose the "first" option */
                    this->select_socksel(0);
                    disable_menu = true;
                }
                break;

            case TMS_KEY_R:
                if (!settings["touch_controls"]->v.b) {
                    if (this->get_mode() == GAME_MODE_ROTATE)
                        this->set_mode(GAME_MODE_DEFAULT);
                    else if (this->get_mode() == GAME_MODE_DEFAULT) {
                        if (this->selection.e != 0) {
                            this->set_mode(GAME_MODE_ROTATE);

                            float mx, my;
                            SDL_GetMouseState(&mx, &my);
                            this->rot_mouse_pos = tvec2f(mx, my);
                            this->rot_mouse_base = this->selection.e->gr ? this->selection.e->gr->get_angle() : this->selection.e->get_angle();
                        }
                    }
                }
                break;

#define SMALL_STEP  0.01f
#define MEDIUM_STEP 0.05f
#define LARGE_STEP  0.1f

            case TMS_KEY_LEFT:
                if (this->selection.e) {
                    b2Vec2 old_pos = this->selection.e->get_position();

                    if (ev->data.key.mod & TMS_MOD_SHIFT && ev->data.key.mod & TMS_MOD_CTRL) {
                        disable_menu = true;
                        old_pos.x -= LARGE_STEP;
                    } else if (ev->data.key.mod & TMS_MOD_SHIFT) {
                        old_pos.x -= MEDIUM_STEP;
                    } else {
                        old_pos.x -= SMALL_STEP;
                    }

                    this->selection.e->set_position(old_pos.x, old_pos.y);
                }
                break;

            case TMS_KEY_RIGHT:
                if (this->selection.e) {
                    b2Vec2 old_pos = this->selection.e->get_position();

                    if (ev->data.key.mod & TMS_MOD_SHIFT && ev->data.key.mod & TMS_MOD_CTRL) {
                        disable_menu = true;
                        old_pos.x += LARGE_STEP;
                    } else if (ev->data.key.mod & TMS_MOD_SHIFT) {
                        old_pos.x += MEDIUM_STEP;
                    } else {
                        old_pos.x += SMALL_STEP;
                    }

                    this->selection.e->set_position(old_pos.x, old_pos.y);
                }
                break;
            case TMS_KEY_UP:
                if (this->selection.e) {
                    b2Vec2 old_pos = this->selection.e->get_position();

                    if (ev->data.key.mod & TMS_MOD_SHIFT && ev->data.key.mod & TMS_MOD_CTRL) {
                        disable_menu = true;
                        old_pos.y += LARGE_STEP;
                    } else if (ev->data.key.mod & TMS_MOD_SHIFT) {
                        old_pos.y += MEDIUM_STEP;
                    } else {
                        old_pos.y += SMALL_STEP;
                    }

                    this->selection.e->set_position(old_pos.x, old_pos.y);
                }
                break;
            case TMS_KEY_DOWN:
                if (this->selection.e) {
                    b2Vec2 old_pos = this->selection.e->get_position();

                    if (ev->data.key.mod & TMS_MOD_SHIFT && ev->data.key.mod & TMS_MOD_CTRL) {
                        disable_menu = true;
                        old_pos.y -= LARGE_STEP;
                    } else if (ev->data.key.mod & TMS_MOD_SHIFT) {
                        old_pos.y -= MEDIUM_STEP;
                    } else {
                        old_pos.y -= SMALL_STEP;
                    }

                    this->selection.e->set_position(old_pos.x, old_pos.y);
                }
                break;

            /* Toggle multiselect */
            case TMS_KEY_M:
                if (this->get_mode() == GAME_MODE_MULTISEL) {
                    this->set_mode(GAME_MODE_DEFAULT);
                } else {
                    this->set_mode(GAME_MODE_MULTISEL);
                    this->multi.additive_selection = false;
                }
                break;

            case TMS_KEY_T:
                if (this->selection.e) {
                    this->selection.e->disconnect_all();
                    this->refresh_widgets();
                }
#if 0
                if (this->state.sandbox && this->selection.e && this->selection.e->flag_active(ENTITY_IS_EDEVICE)) {
                    this->open_socket_selector(0, this->selection.e->get_edevice());
                    this->state.modified = true;
                } else {
                    if (this->get_mode() == GAME_MODE_DRAW) {
                        this->set_mode(GAME_MODE_DEFAULT);
                    } else {
                        this->set_mode(GAME_MODE_DRAW);
                    }
                }
#endif
                break;

            case TMS_KEY_S:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    disable_menu = true;

                    bool ask_for_new_name = (W->level.name_len == 0);

                    if (ask_for_new_name)
                        ui::open_dialog(DIALOG_SAVE);
                    else
                        P.add_action(ACTION_SAVE, 0);
                }
                break;
            case TMS_KEY_N:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    disable_menu = true;

                    ui::open_dialog(DIALOG_NEW_LEVEL);
                } else {
                    G->toggle_entity_lock(G->selection.e);
                    this->refresh_widgets();
                }
                break;

            case SDL_SCANCODE_AC_BACK:
            case TMS_KEY_B: this->back(); break;

            case TMS_KEY_DELETE:
                if (this->get_mode() == GAME_MODE_MULTISEL) {
                    if (this->selection.m && !this->selection.m->empty()) {
                        ui::confirm("Are you sure you want to delete these objects?",
                                "Yes",  ACTION_MULTI_DELETE,
                                "No",   ACTION_IGNORE);
                    }
                } else if (this->state.sandbox) {
                    if (this->selection.e && this->selection.e->requires_delete_confirmation()) {
                        ui::confirm("Are you sure you want to delete this object?",
                                "Yes",  ACTION_DELETE_SELECTION,
                                "No",   ACTION_IGNORE);
                    } else
                        this->delete_selected_entity();
                }
                break;

            case TMS_KEY_F:
            case TMS_KEY_G:
                if (ev->data.key.mod & TMS_MOD_SHIFT) {
                    this->set_architect_mode(!this->state.abo_architect_mode);
                    this->refresh_widgets();
                    break;
                } else {
#if 0
                    if (this->get_mode() == GAME_MODE_DEFAULT) {
                        if (this->selection.e != 0) {
                            this->set_mode(GAME_MODE_GRAB);

                            int mx, my;
                            SDL_GetMouseState(&mx, &my);
                            this->rot_mouse_pos = (tvec2){mx,my};
                            this->grab_mouse_pos = this->selection.e->get_position();
                        }
                    } else {
                        this->set_mode(GAME_MODE_DEFAULT);
                    }
#endif
                }
            case TMS_KEY_H:
                if (ev->data.key.mod & TMS_MOD_SHIFT) {
                    if (this->state.sandbox && this->selection.e && this->selection.e->flag_active(ENTITY_IS_EDEVICE)) {
                        this->open_socket_selector(0, this->selection.e->get_edevice(), SS_ACTION_SELECT);
                    }
                    break;
                }

            case TMS_KEY_J:
                if (ev->data.key.mod & TMS_MOD_SHIFT) {
                    ui::open_dialog(DIALOG_PUBLISH);
                    break;
                }
            case TMS_KEY_K:
            case TMS_KEY_L:
                {
                    int key = ev->data.key.keycode - TMS_KEY_F;
                    this->apply_pending_connection(key);
                }
                break;

            case TMS_KEY_PAGEDOWN:
            case TMS_KEY_X:
                if (this->selection.e && this->state.sandbox) {
                    this->selection.e->set_layer((this->selection.e->get_layer()+1)%3);
                    this->animate_disconnect(this->selection.e);
                    this->selection.e->disconnect_all();
                }
                this->refresh_widgets();
                break;

            /* Shift+Q: Disconnect all connections */
            /* CTRL+Q: Open Quit dialog */
            case TMS_KEY_Q:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    disable_menu = true;
                    ui::open_dialog(DIALOG_CONFIRM_QUIT);
                } else if (ev->data.key.mod & TMS_MOD_SHIFT) {
                    if (this->state.sandbox && this->selection.e) {
                        this->selection.e->disconnect_all();
                        this->animate_disconnect(this->selection.e);
                        this->state.modified = true;
                    }
                } else {
#ifdef DEBUG
                    this->print_stats();
#endif
                }
                break;

            /* Shift+E: Connect all */
            case TMS_KEY_E:
                if (ev->data.key.mod & TMS_MOD_SHIFT
                        && this->state.sandbox && this->selection.e) {
                    entity *saved = this->selection.e;
                    for (c_map::iterator it = this->pairs.begin(); it != this->pairs.end(); ++it) {
                        connection *c = it->second;

                        if (!c->typeselect) {
                            this->apply_connection(c, 0);
                        }
                    }

                    this->pairs.clear();

                    this->selection.select(saved);
                }
                break;

            case TMS_KEY_Z:
                if (this->selection.e && this->state.sandbox) {
                    if (this->selection.e->get_layer() > 0) {
                        this->selection.e->set_layer((this->selection.e->get_layer()-1));
                    } else {
                        this->selection.e->set_layer(2);
                    }
                    this->animate_disconnect(this->selection.e);
                    this->selection.e->disconnect_all();

                    this->refresh_widgets();
                }
                break;

            case TMS_KEY_O:
                if (ev->data.key.mod & TMS_MOD_CTRL) {
                    disable_menu = true;

                    ui::open_dialog(DIALOG_OPEN);
                }
#ifdef DEBUG
                else {
                    uint32_t latest_id = pkgman::get_latest_level_id(LEVEL_LOCAL);

                    if (latest_id != 0) {
                        this->open_sandbox(LEVEL_LOCAL, latest_id); // open the last modified level
                    } else {
                        this->open_sandbox(LEVEL_LOCAL, pkgman::get_next_level_id() - 1);
                    }
                }
#endif
                break;

            case TMS_KEY_Y:
                if (this->selection.e) {
                    this->config_btn_pressed(this->selection.e);
                } else if (this->selection.m && this->get_mode() == GAME_MODE_MULTISEL) {
                    ui::open_dialog(DIALOG_MULTI_CONFIG);
                }
                break;

            /**
             * COMMA        = Duplicate selected entity and place it on the current cursor position.
             *
             * SHIFT+COMMA  = Duplicate selected entity twice, place it on the same position as the
             *                selected entity, and fill the two layers that the selected entity
             *                is not currently occupying.
             **/
            case TMS_KEY_COMMA:
                {
                    if (ev->data.key.mod & TMS_MOD_SHIFT) {
                        if (this->selection.e) {
                            int layer = this->selection.e->get_layer();
                            b2Vec2 pos = this->selection.e->get_position();

                            entity *e1 = this->editor_construct_entity(this->selection.e->g_id);
                            entity *e2 = this->editor_construct_entity(this->selection.e->g_id);

                            entity *cur_e = e1;

                            for (int x=0; x<NUM_LAYERS; ++x) {
                                if (x != layer) {
                                    cur_e->set_layer(x);
                                    cur_e->set_position(pos);

                                    if (cur_e == e1) {
                                        cur_e = e2;
                                    }
                                }
                            }
                        }
                    } else {
                        if (this->selection.e) {
                            this->editor_construct_entity(this->selection.e->g_id);
                        }
                    }
                }
                break;

            case TMS_KEY_1: this->numkey_pressed(0); break;
            case TMS_KEY_2: this->numkey_pressed(1); break;
            case TMS_KEY_3: this->numkey_pressed(2); break;
            case TMS_KEY_4: this->numkey_pressed(3); break;
            case TMS_KEY_5: this->numkey_pressed(4); break;
            case TMS_KEY_6: this->numkey_pressed(5); break;
            case TMS_KEY_7: this->numkey_pressed(6); break;
            case TMS_KEY_8: this->numkey_pressed(7); break;
            case TMS_KEY_9: this->numkey_pressed(8); break;
            case TMS_KEY_0: this->numkey_pressed(9); break;

            case TMS_KEY_U: /* Save. */
                if (W->level.name_len == 0) {
                    ui::open_dialog(DIALOG_SAVE);
                } else {
                    P.add_action(ACTION_SAVE, 0);
                }
                break;

            case TMS_KEY_P:
                if (W->is_puzzle() && G->state.sandbox) {
                    G->puzzle_play(PUZZLE_TEST_PLAY);
                } else {
                    if (W->is_puzzle()) {
                        this->save(false, true);
                        G->state.puzzle_state = 2;
                    }
                    G->do_play();
                }
                break;

            case SDL_SCANCODE_MENU:
                if (this->state.sandbox) {
                    ui::open_dialog(DIALOG_SANDBOX_MENU);
                }
                break;

            case TMS_KEY_ESC:
                if (this->get_mode() != GAME_MODE_DEFAULT) {
                    this->set_mode(GAME_MODE_DEFAULT);
                } else {
                    if (!this->selection.enabled() && !disable_menu) {
                        ui::open_dialog(DIALOG_SANDBOX_MENU);
                        tms_debugf("open main menu");
                    }

                    if (dragging[0] && moving[0]) {
                        dragging[0] = false;
                        moving[0] = false;
                        rotating[0] = false;
                        down[0] = false;
                        cam_move_x[0] = 0.f;
                        cam_move_x[1] = 0.f;
                        cam_move_y[0] = 0.f;
                        cam_move_y[1] = 0.f;
                        this->selection.e->set_position(this->selection.e->old_pos);
                        this->recheck_all_placements();
                        this->cam->_position.x = old_cam_pos.x;
                        this->cam->_position.y = old_cam_pos.y;
                    } else {
                        this->selection.disable();
                    }
                }

                disable_menu = false;
                break;
        }
    } else if (ev->type == TMS_EV_KEY_UP) {
        switch (ev->data.key.keycode) {
            case TMS_KEY_SPACE:
                if (!disable_menu) {
                    ui::open_dialog(DIALOG_QUICKADD);
                }

                disable_menu = false;
                break;

            case TMS_KEY_LEFT_SHIFT:
            case TMS_KEY_RIGHT_SHIFT:
                if (this->get_mode() == GAME_MODE_MULTISEL) {
                    this->multi.additive_selection = !this->multi.additive_selection;
                    this->wdg_additive->faded = !this->multi.additive_selection;
                }
                break;

            case TMS_KEY_LEFT_CTRL:
            case TMS_KEY_RIGHT_CTRL:
                if (!disable_menu)
                    ui::open_dialog(DIALOG_SANDBOX_MENU);

                disable_menu = false;
                break;

        }
    } else if (ev->type == TMS_EV_POINTER_MOVE) {
        if (this->menu_handle_event(ev) == EVENT_DONE) {
            return EVENT_DONE;
        }

        if (!settings["touch_controls"]->v.b) {
            if (this->get_mode() == GAME_MODE_ROTATE) {
                if (!this->selection.e) {
                    this->set_mode(GAME_MODE_DEFAULT);
                } else {
                    float mx, my;
                    SDL_GetMouseState(&mx, &my);
                    float dist = my - this->rot_mouse_pos.y;
                    dist *= 1.f/100.f;

                    /* TODO: add snap */
                    if (this->selection.e->gr) {
                        entity *re = this->selection.e->gr;
                        b2Vec2 p = this->selection.e->get_position(this->selection.frame);
                        b2Vec2 lock = p;

                        re->set_angle(rot_mouse_base-dist);
                        lock -= this->selection.e->get_position(this->selection.frame);
                        re->set_position(re->get_position()+lock);
                    } else {
                        entity *re = this->selection.e->gr ? this->selection.e->gr : this->selection.e;

                        re->set_angle(rot_mouse_base-dist);
                    }
                }
            } else if (this->get_mode() == GAME_MODE_GRAB) {
                if (!this->selection.e) {
                    this->set_mode(GAME_MODE_DEFAULT);
                } else {
                    float mx, my;
                    SDL_GetMouseState(&mx, &my);
                    float dist_x = mx - this->rot_mouse_pos.x;
                    float dist_y = my - this->rot_mouse_pos.y;
                    dist_x *= 1.f/100.f;
                    dist_y *= -1.f/100.f;

                    b2Vec2 npos = b2Vec2(grab_mouse_pos.x + dist_x, grab_mouse_pos.y + dist_y);
                    this->selection.e->set_position(npos.x, npos.y, this->selection.frame);
                }
            }
        }
    } else if (ev->type == TMS_EV_POINTER_DOWN) {
        if (this->get_mode() == GAME_MODE_ROTATE || this->get_mode() == GAME_MODE_GRAB) {
            this->set_mode(GAME_MODE_DEFAULT);
        }
        /* close all menus */
        ui::open_dialog(CLOSE_ALL_DIALOGS);

        int pid = ev->data.motion.pointer_id;
        down[pid] = true;

        tvec3 tproj;
        W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, 0, &tproj);

        tvec2 click_pt = tvec2f(ev->data.motion.x, ev->data.motion.y);
        int64_t diff = (int64_t)_tms.last_time - (int64_t)touch_time[pid];
        float dist = std::abs(tvec2_dist(touch_pos[pid], click_pt));

        touch_proj[pid] = (tvec2){tproj.x, tproj.y};
        touch_pos[pid] = (tvec2){ev->data.motion.x, ev->data.motion.y};
        touch_time[pid] = _tms.last_time;
        //tms_infof("touch_time updated");
        dragging[pid] = false;

        //tms_infof("DOWN__ FPS %d", pid);

        int r = this->menu_handle_event(ev);
        rotating[pid] = 0;
        resizing[pid] = 0;

        if (r == 2) {
            down[pid] = false;
            return T_OK;
        } else if (r == 1) {
            return T_OK;
        }

        if (pid == 0 || pid == 1) {
            if (down[0] && down[1]) {
                tms_infof("Initiate zoom.");
                if (((dragging[0] && moving[0]) || (dragging[1] && moving[1])) && this->selection.e) {
                    snap[pid] = true;
                    return T_OK;
                } else {
                    zoom_dist = tvec2_dist(touch_pos[0], touch_pos[1]);
                    zooming = true;
                    zoom_stopped = false;
                }
            }
        }

        if (this->get_mode() == GAME_MODE_DRAW
                && pid == 0
            ) {
            //this->handle_draw(pid, ev->data.motion.x, ev->data.motion.y);
            /* this is now handled in step */
            drawing = 1;
        } else if (this->selection.e) {
            /* an object is selected, the player might be clicking the
             * rotation icon */

            if (this->check_click_shape_resize(ev->data.motion.x, ev->data.motion.y)) {
                resizing[pid] = 1;
                return T_OK;
            }

            if (this->check_click_rotate(ev->data.motion.x, ev->data.motion.y)) {
                rotating[pid] = 1;
                return T_OK;
            }

            if (diff < 200000 && this->selection.e->flag_active(ENTITY_HAS_CONFIG) && dist < .5f
                && !this->selection.e->is_edevice()) {
                G->config_btn_pressed(G->selection.e);
                return T_OK;
            }

            if (this->selection.e->g_id == O_SERVO_MOTOR || this->selection.e->g_id == O_DC_MOTOR) {
                motor *s = (motor*)this->selection.e;
                tvec3 pt;
                W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, this->selection.e->get_layer(), &pt);
                b2Vec2 r = this->selection.e->local_to_world(b2Vec2(cosf(s->properties[1].v.f) * 3.f, sinf(s->properties[1].v.f)*3.f), this->selection.frame);

                if ((r - b2Vec2(pt.x, pt.y)).Length() < .5f) {
                    rotating[pid] = 2;
                    b2Vec2 p = this->selection.e->get_position();

                    float a1 = atan2f(r.y - p.y, r.x - p.x);
                    float a2 = atan2f(pt.y - p.y, pt.x - p.x);

                    this->rot_offs = tmath_adist(a2, a1);
                    W->query(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, &this->sel_p_ent, &this->sel_p_body, &this->sel_p_offs, &this->sel_p_frame, this->layer_vis);
                    return T_OK;
                }
            }
        } else if (this->get_mode() == GAME_MODE_MULTISEL
                && (settings["touch_controls"]->v.b || pid == 0)) {
            if (this->multi.box_select == 1) {
                W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, 0, &begin_box_select);
                box_select_pid = pid;
                this->multi.box_select = 2;
                return T_OK;
            } else {
                W->query(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, &this->sel_p_ent, &this->sel_p_body, &this->sel_p_offs, &this->sel_p_frame, this->layer_vis);

                if (this->sel_p_ent) {
                    if (!this->multi.additive_selection && this->selection.m && this->selection.m->find(this->sel_p_ent) != this->selection.m->end()) {
                    } else {
                        this->apply_multiselection(this->sel_p_ent);
                    }
                } else {
                    this->selection.disable();
                    tvec3 pt;
                    W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, 0, &pt);

                    this->multi.cursor.x = pt.x;
                    this->multi.cursor.y = pt.y;
                }
            }
        }

        if (this->get_mode() == GAME_MODE_MULTISEL) {
            if (this->multi.import && diff < 300000 && dist < 50.f
                    && (settings["touch_controls"]->v.b || pid == 0)) {
                tms_debugf("IMPORT (%.2f)", dist);
                this->import_object(this->multi.import->lvl_id);
            }
        } else {
            /* TODO: Make sure we're selecting the object that's actually closest. */
            W->query(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, &this->sel_p_ent, &this->sel_p_body, &this->sel_p_offs, &this->sel_p_frame, this->layer_vis);
            if (this->sel_p_ent) {
                this->check_quick_plug(diff, ev->data.motion.x, ev->data.motion.y);
            }
        }
    } else if (ev->type == TMS_EV_POINTER_DRAG) {
        if (this->menu_handle_event(ev)) {
            return T_OK;
        }

        int pid = ev->data.motion.pointer_id;

        if (!down[pid]) {
            return T_OK;
        }

        if (snap[pid]) {
            return T_OK;
        }

        tvec2 tdown = (tvec2){ev->data.motion.x, ev->data.motion.y};
        tvec2 td = (tvec2){tdown.x-touch_pos[pid].x, tdown.y-touch_pos[pid].y};

        tvec3 tproj;
        W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, 0, &tproj);

        float td_mag = tvec2_magnitude(&td);

        if (zooming && (pid == 0 || pid == 1)) {
            if (!zoom_stopped) {
                touch_pos[pid] = tdown;
                touch_time[pid] = _tms.last_time;
                touch_proj[pid] = (tvec2){tproj.x, tproj.y};

                float dist = tvec2_dist(touch_pos[0], touch_pos[1]);
                float offs = dist - zoom_dist;
                zoom_dist = dist;
                if (settings["smooth_zoom"]->v.b) {
                    this->cam_vel.z -= offs * .25f * settings["zoom_speed"]->v.f;
                } else {
                    this->cam_vel.z -= offs * 2.5f * settings["zoom_speed"]->v.f;
                }
            }
        } else if (this->get_mode() == GAME_MODE_DRAW
                && pid == 0
            ) {
            touch_pos[pid] = tdown;
            //this->handle_draw(pid, ev->data.motion.x, ev->data.motion.y);
            /* this is now handled in step */
        } else {
            if (this->get_mode() == GAME_MODE_QUICK_PLUG) {
                if (this->selection.e) {
                    touch_proj[pid] = (tvec2){tproj.x, tproj.y};
                    touch_pos[pid] = tdown;
                    W->query(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, &this->sel_p_ent, &this->sel_p_body, &this->sel_p_offs, &this->sel_p_frame, this->layer_vis);

                    if (this->sel_p_ent && (!this->sel_p_ent->flag_active(ENTITY_IS_EDEVICE) || this->sel_p_ent == this->selection.e)) {
                        this->sel_p_ent = 0;
                    }
                    W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, this->selection.e->get_layer(), &touch_quickplug_pos);
                    //touch_time[pid] = _tms.last_time;
                    return T_OK;
                } else {
                    this->set_mode(GAME_MODE_DEFAULT);
                }
            }

            /*
            tms_infof("DRAG_DIST_EPS: %.2f", DRAG_DIST_EPS);
            tms_infof("td_mag: %.2f", td_mag);
            tms_infof("DRAG_TIME_EPS: %d", DRAG_TIME_EPS);
            tms_infof("tms.last_time: %llu", _tms.last_time);
            tms_infof("touch_time[%d]: %llu", pid, touch_time[pid]);
            */

            if (!dragging[pid]
                    && (!settings["touch_controls"]->v.b || td_mag > DRAG_DIST_MIN_EPS)
                    && (_tms.last_time - touch_time[pid] > DRAG_TIME_EPS
                        || td_mag > DRAG_DIST_EPS)
                    && (settings["touch_controls"]->v.b || pid == 0)
                    ) {

                if (rotating[pid] || resizing[pid]) {
                    moving[pid] = true;
                } else {
                    if (this->get_mode() == GAME_MODE_MULTISEL) {
                        if (this->multi.additive_selection == false && this->selection.m) {
                            if (this->sel_p_ent) {
                                entity *first = this->sel_p_ent;
                                tms_debugf("first: %s", first->get_name());
                                W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, first->get_layer(), &tproj);
                                b2Vec2 first_pos = first->get_position();
                                this->selection.offs = (tvec2){tproj.x-first_pos.x,tproj.y-first_pos.y};
                                moving[pid] = true;
                            } else {
                                moving[pid] = false;
                            }
                        } else {
                            moving[pid] = false;
                        }
                    } else if (this->sel_p_ent || this->sel_p_body) {
                        this->selection.select(this->sel_p_ent, this->sel_p_body, this->sel_p_offs, this->sel_p_frame, true);
                        if (this->selection.e) {
                            this->selection.e->old_pos = this->selection.e->get_position();
                            old_cam_pos = this->cam->_position;
                            this->selection.e->on_grab(this);
                        }

                        moving[pid] = true;
                    } else {
                        moving[pid] = false;
                    }
                }

                dragging[pid] = true;
                touch_time[pid] = _tms.last_time;
                //tms_infof("touch_time updated");
            }

            /* pid 2 = middle mouse button */
            if (dragging[pid] || (!settings["touch_controls"]->v.b && pid == 2)) {
                if (moving[pid]) {
                    this->state.modified = true;

                    tvec3 pos;
                    if (this->selection.m && this->sel_p_ent && this->selection.m->size()) {
                        W->step_count = 0;
                        entity *first = this->sel_p_ent;
                        W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, first->get_layer(), &tproj);

                        b2Vec2 p = b2Vec2(tproj.x, tproj.y);
                        p.x -= this->selection.offs.x;
                        p.y -= this->selection.offs.y;

                        b2Vec2 diff = p - first->get_position();

                        /* only allow one entity in each group to move, since
                         * each entity will force the whole group to move */
                        std::set<group*> cache;

                        for (std::set<entity*>::iterator i = this->selection.m->begin();
                                i != this->selection.m->end(); i++) {
                            (*i)->on_grab(this);
                        }

                        for (std::set<entity*>::iterator i = this->selection.m->begin();
                                i != this->selection.m->end(); i++) {

                            if ((*i)->gr) {
                                /* see comment aboout groups above */
                                if (cache.find((*i)->gr) != cache.end())
                                    continue;

                                cache.insert((*i)->gr);
                            }

                            p = (*i)->get_position();
                            p += diff;
                            (*i)->set_position(p);
                        }

                        for (std::set<entity*>::iterator i = this->selection.m->begin();
                                i != this->selection.m->end(); i++) {
                            (*i)->on_release(this);
                        }
                    } else if (this->selection.e) {
                        W->step_count = 0;
                        W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, this->selection.e->get_layer(), &pos);

                        /* Stop the entity from being rotated or moved if connected to a static entity */
                        if (this->selection.e->gr && this->selection.e->gr->is_locked() && !this->selection.e->is_locked()) {
                            return EVENT_DONE;
                        } else {
                            std::set<entity*> entities;
                            this->selection.e->gather_connected_entities(&entities, false, false, true);

                            bool found_static = false;

                            for (std::set<entity*>::iterator it = entities.begin();
                                    it != entities.end(); ++it) {
                                if ((*it) == this->selection.e) continue;

                                if ((*it)->flag_active(ENTITY_IS_STATIC)) {
                                    found_static = true;
                                    break;
                                }
                            }

                            if (found_static) {
                                return EVENT_DONE;
                            }
                        }

                        bool simple_snap = false;
                        /* Architect mode emulates the shift functionality */
                        if (this->state.abo_architect_mode) {
                            simple_snap = !simple_snap;
                        }

                        /* Holding down shift when architect mode is active neutralizes the effect */
                        if (this->shift_down()) {
                            simple_snap = !simple_snap;
                        }

                        // On touch platforms we include alternate snap-methods (holding a second finger down on the screen)
                        if (settings["touch_controls"]->v.b && (snap[0] || snap[1]))
                            simple_snap = !simple_snap;

                        bool advanced_snap = false;

                        /* Currently only used with rotations to make finer rotation snaps. */
                        if (this->ctrl_down()) {
                            advanced_snap = !advanced_snap;
                        }

                        if (!rotating[pid] && !resizing[pid]) {
                            /* If the Snap by Default level-flag is enabled, toggle the snap mode again!
                             * This is only used for movements, not for rotations. */
                            if (!this->state.sandbox && W->level.flag_active(LVL_SNAP)) {
                                simple_snap = !simple_snap;
                            }

                            pos.x -= this->selection.offs.x;
                            pos.y -= this->selection.offs.y;

                            if (this->selection.b && this->selection.b != this->selection.e->get_body(this->selection.frame)) {
                                this->selection.b->SetTransform(b2Vec2(pos.x, pos.y), this->selection.b->GetAngle());
                            } else {

                                if (simple_snap) {
                                    /* Shift-dragging pixels remove them from their grid */
                                    if (this->selection.e->g_id == O_PIXEL || this->selection.e->g_id == O_TPIXEL) {
                                        this->selection.e->entity::set_position(
                                                roundf(pos.x/state.gridsize)*state.gridsize,
                                                roundf(pos.y/state.gridsize)*state.gridsize,
                                                this->selection.frame);
                                    } else {
                                        this->selection.e->set_position(
                                                roundf(pos.x/state.gridsize)*state.gridsize,
                                                roundf(pos.y/state.gridsize)*state.gridsize,
                                                this->selection.frame);
                                    }
                                } else {
#define BORDER_SCROLL_SIZE 80
                                    int border_x = BORDER_SCROLL_SIZE;
                                    int border_y = BORDER_SCROLL_SIZE;
                                    int ox = (int)ev->data.motion.x - _tms.window_width/2;
                                    int oy = (int)ev->data.motion.y - _tms.window_height/2;
                                    int dir_x = ox > 0;
                                    int dir_y = oy > 0;

                                    if (dir_x == 1 && this->state.sandbox) { /* right */
                                        ox += this->get_menu_width();
                                    }

                                    int str_x = (_tms.window_width / 2) - std::abs(ox);
                                    if (str_x < border_x) {
                                        cam_move_x[dir_x] = tclampf(1.f-(float)str_x/border_x, 0.f, 1.f);
                                    } else {
                                        cam_move_x[dir_x] = 0.f;
                                    }

                                    int str_y = (_tms.window_height / 2) - std::abs(oy);
                                    if (str_y < border_y) {
                                        cam_move_y[dir_y] = tclampf(1.f-(float)str_y/border_y, 0.f, 1.f);
                                    } else {
                                        cam_move_y[dir_y] = 0.f;
                                    }

                                    this->selection.e->set_position(pos.x, pos.y, this->selection.frame);
                                }
                            }

                            this->state.modified = true;
                        } else {
                            if (resizing[pid]) {
                                this->handle_shape_resize(pos.x, pos.y);

                                this->state.modified = true;
                            } else if (rotating[pid] == 1) {
                                if (this->selection.e->gr) {
                                    entity *re = this->selection.e->gr;
                                    float a = this->selection.e->get_angle(this->selection.frame);
                                    b2Vec2 p = this->selection.e->get_position(this->selection.frame);
                                    //float a = this->selection.e->get_angle();
                                    //b2Vec2 p = this->selection.e->get_position();
                                    b2Vec2 lock = p;

                                    b2Vec2 cs = b2Vec2(pos.x - p.x, pos.y - p.y);
                                    cs *= 1.f/cs.Length();

                                    float na = atan2f(cs.y, cs.x) + this->rot_offs;
                                    float da = tmath_adist(a, na);

                                    re->set_angle(re->get_angle()+da);
                                    lock -= this->selection.e->get_position(this->selection.frame);
                                    re->set_position(re->get_position()+lock);
                                } else {
                                    entity *re = this->selection.e->gr ? this->selection.e->gr : this->selection.e;
                                    float a = re->get_angle();
                                    b2Vec2 p = re->get_position();

                                    b2Vec2 cs = b2Vec2(pos.x - p.x, pos.y - p.y);
                                    cs *= 1.f/cs.Length();

                                    float na = atan2f(cs.y, cs.x) + this->rot_offs;

                                    /**
                                     * Holding down shift and ctrl produces 64-angle snapping
                                     * Holding down shift produces 16-angle snapping
                                     * Holding down ctrl produces 4-angle snapping
                                     **/

                                    if (simple_snap && advanced_snap) {
                                        na = na/(M_PI/32.f);
                                        na = roundf(na);
                                        na = na*(M_PI/32.f);
                                    } else if (simple_snap) {
                                        na = na/(M_PI/8.f);
                                        na = roundf(na);
                                        na = na*(M_PI/8.f);
                                    } else if (advanced_snap) {
                                        na = na/(M_PI/2.f);
                                        na = roundf(na);
                                        na = na*(M_PI/2.f);
                                    }

                                    float da = tmath_adist(a, na);
                                    re->set_angle(a+da);
                                }

                                this->state.modified = true;
                            } else if (rotating[pid] == 2) {
                                /*b2Vec2 p = this->selection.e->get_position();
                                b2Vec2 cs = b2Vec2(pos.x - p.x, pos.y - p.y);
                                cs *= 1.f/cs.Length();*/

                                b2Vec2 p = this->selection.e->world_to_local(b2Vec2(pos.x, pos.y), 0);
                                p *= 1.f/p.Length();
                                float angle = atan2f(p.y, p.x);

                                angle /= M_PI/90.f;
                                angle = roundf(angle);
                                angle *= M_PI/90.f;

                                if (angle < 0.f) angle += M_PI*2;

                                G->show_numfeed(angle * (180.f/M_PI));

                                motor *s = (motor*)this->selection.e;
                                s->properties[1].v.f = angle;

                                this->state.modified = true;
                            }
                        }
                    } else if (this->selection.m) {
                        tms_infof("dragging multiselect");
                    }
                } else {
                    if (this->get_mode() == GAME_MODE_MULTISEL && (this->multi.box_select == 2 || this->multi.box_select == 3) && pid == box_select_pid) {
                        W->get_layer_point(this->cam, touch_pos[pid].x, touch_pos[pid].y, 0, &end_box_select);
                        this->multi.box_select = 3;
                    } else {
                        tvec3 lastproj;
                        W->get_layer_point(this->cam, touch_pos[pid].x, touch_pos[pid].y, 0, &lastproj);
                        tvec2 diff = (tvec2){tproj.x, tproj.y};
                        diff = tvec2_sub(diff, (tvec2){lastproj.x, lastproj.y});

                        if (settings["smooth_cam"]->v.b) {
                            this->cam_vel.x -= diff.x * settings["cam_speed_modifier"]->v.f * 10.f;
                            this->cam_vel.y -= diff.y * settings["cam_speed_modifier"]->v.f * 10.f;
                        } else {
                            this->cam_move(
                                    diff.x * settings["cam_speed_modifier"]->v.f,
                                    diff.y * settings["cam_speed_modifier"]->v.f,
                                    0);
                        }
                    }
                }

                touch_proj[pid] = (tvec2){tproj.x, tproj.y};
                touch_pos[pid] = tdown;
                //tms_infof("touch_time set to 0");
                touch_time[pid] = 0;
                //touch_time[pid] = _tms.last_time;
            } else {
                //touch_time[pid] = _tms.last_time;
            }
        }
    } else if (ev->type == TMS_EV_POINTER_UP) {
        int pid = ev->data.motion.pointer_id;
        if (!down[pid]) return T_OK;
        cam_move_x[0] = 0.f;
        cam_move_x[1] = 0.f;
        cam_move_y[0] = 0.f;
        cam_move_y[1] = 0.f;
        down[pid] = false;
        snap[pid] = false;

        //touch_time[pid] = _tms.last_time;

        if (this->menu_handle_event(ev))
            return T_OK;

        if (zooming && (pid == 0 || pid == 1)) {
            zoom_stopped = true;
            if (!down[0] && !down[1])
                zooming = false;
        } else {
            if (this->get_mode() == GAME_MODE_DRAW
                    && pid == 0
                ) {
                drawing = 0;
            } else if (this->get_mode() == GAME_MODE_QUICK_PLUG) {
                W->query(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, &this->sel_p_ent, &this->sel_p_body, &this->sel_p_offs, &this->sel_p_frame, this->layer_vis);

                this->set_mode(GAME_MODE_DEFAULT);

                if (this->sel_p_ent && this->sel_p_ent->flag_active(ENTITY_IS_EDEVICE) && this->sel_p_ent != this->selection.e && this->selection.e) {
                    tms_infof("quickplug detected: %s", this->sel_p_ent->get_name());

                    for (int t=0; t<3; t++) {
                        int m1 = this->selection.e->get_edevice()->get_inout_mask(t);
                        int m2 = this->sel_p_ent->get_edevice()->get_outin_mask(t);

                        if ((m1 & m2)) {
                            this->open_socket_selector(this->selection.e, this->sel_p_ent->get_edevice());
                            tms_infof("compatible");
                            break;
                        }
                    }
                } else {
                    tms_infof("nothing detected");
                }

                return T_OK;
            } else if (this->get_mode() == GAME_MODE_MULTISEL && this->multi.box_select > 0) {
                if (this->multi.box_select == 3) {
                    this->multi.box_select = 1;

                    b2AABB aabb;

                    float lower_x = std::min(begin_box_select.x, end_box_select.x);
                    float lower_y = std::min(begin_box_select.y, end_box_select.y);

                    float upper_x = std::max(begin_box_select.x, end_box_select.x);
                    float upper_y = std::max(begin_box_select.y, end_box_select.y);

                    aabb.lowerBound.Set(lower_x, lower_y);
                    aabb.upperBound.Set(upper_x, upper_y);

                    W->b2->QueryAABB(&box_select_handler, aabb);

                    entity_set *loop = new entity_set();
                    for (entity_set::iterator it = box_select_entities.begin();
                            it != box_select_entities.end(); ++it) {
                        entity *e = *it;
                        loop->insert(e);

                        if (e->is_edevice()) {
                            /* Add all jumpers/mini-emitters/receivers */
                            edevice *edev = e->get_edevice();

                            if (edev) {
                                for (int x=0; x<edev->num_s_in; ++x) {
                                    const isocket &s = edev->s_in[x];

                                    if (s.p) {
                                        entity *p = static_cast<entity*>(s.p);

                                        switch (p->g_id) {
                                            case O_RECEIVER:
                                            case O_JUMPER:
                                                loop->insert(p);
                                                break;
                                        }
                                    }
                                }

                                for (int x=0; x<edev->num_s_out; ++x) {
                                    const isocket &s = edev->s_out[x];

                                    if (s.p) {
                                        entity *p = static_cast<entity*>(s.p);

                                        switch (p->g_id) {
                                            case O_MINI_TRANSMITTER:
                                                loop->insert(p);
                                                break;
                                        }
                                    }
                                }
                            }
                        }
                    }

                    if (loop->size()) {
                        this->selection.select(loop);
                    } else {
                        delete loop;

                        if (!this->multi.additive_selection) {
                            this->selection.disable();
                        }
                    }

                    tms_debugf("got %d objects", (int)box_select_entities.size());

                    box_select_entities.clear();
                } else {
                    this->multi.box_select = 1;
                }
            }

            W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, 0, &pt[0]);
            W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, 1*LAYER_DEPTH, &pt[1]);
            W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y, 2*LAYER_DEPTH, &pt[2]);
            W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y,
                     + .5f,
                    &half_pt[0]);
            W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y,
                    1.f * LAYER_DEPTH + .5f,
                    &half_pt[1]);
            W->get_layer_point(this->cam, (int)ev->data.motion.x, (int)ev->data.motion.y,
                    2.f * LAYER_DEPTH + .5f,
                    &half_pt[2]);

            if (!dragging[pid]) {
                if (this->get_mode() == GAME_MODE_SELECT_SOCKET) {
                    if (this->check_click_socksel()) {
                        return T_OK;
                    }
                }

                if (this->get_mode() != GAME_MODE_SELECT_SOCKET) {
                    if (this->get_mode() == GAME_MODE_SELECT_CONN_TYPE) {
                        if (this->check_click_conntype(ev->data.motion.x, ev->data.motion.y)) {
                            W->step_count = 0;
                            return T_OK;
                        }
                    }

                    if (this->get_mode() == GAME_MODE_CONN_EDIT) {
                        for (std::set<connection*>::iterator i = W->connections.begin();
                                i != W->connections.end(); i++) {
                            connection *c = *i;

                            if (this->state.sandbox || (c->e->is_moveable() || c->o->is_moveable())) {
                                b2Vec2 pos = c->e->local_to_world(b2Vec2(c->p.x, c->p.y), c->f[0]);

                                int layer = c->layer;

                                tvec3 dd = tms_camera_project(this->cam, this->cam->_position.x, this->cam->_position.y,c->layer*LAYER_DEPTH+((LAYER_DEPTH/2.f)*c->multilayer));
                                tvec3 v1 = tms_camera_unproject(this->cam, 0.f, 0.f, dd.z);
                                tvec3 v2 = tms_camera_unproject(this->cam, _tms.xppcm*.5f, 0.f, dd.z);
                                float w = v2.x-v1.x;

                                if ((b2Vec2(pt[layer].x, pt[layer].y)-pos).Length() < w) {
                                    this->selection.select(c);
                                    //tms_infof("clicked existing connection");
                                    return T_OK;
                                }
                            }
                        }
                    }

                    /* see if any pending connection was clicked */

                    if (this->check_click_conn(ev->data.motion.x, ev->data.motion.y)) {
                        W->step_count = 0;
                        return T_OK;
                    }
                }

                if (this->get_mode() == GAME_MODE_SELECT_OBJECT) {
                    this->check_select_object(ev->data.motion.x, ev->data.motion.y, pid);
                } else {
                    /* if nothing was "clicked" then simply apply the new selection */
                    /* TODO: Make sure we actually select the object that's closest to the users click */
                    if (this->get_mode() != GAME_MODE_DRAW && this->get_mode() != GAME_MODE_MULTISEL) {
                        if (!dragging[0] || (!moving[0] && !rotating[0])) {
                            /* You can only use pid 1 to select entities, not to deselect entities.
                             * Pid 0 can be used freely for all purposes */
                            if ((this->sel_p_ent && pid <= 1) || pid == 0) {
                                this->selection.select(this->sel_p_ent, this->sel_p_body, this->sel_p_offs, this->sel_p_frame, true);
                            }
                            this->set_mode(GAME_MODE_DEFAULT);
                            if (this->selection.e) {
                                tms_infof("clicked %s", this->selection.e->get_name());
                            }
                        } else {
#ifdef DEBUG
                            if (moving[0]) {
                                tms_infof("moving 0");
                            }
                            if (moving[1]) {
                                tms_infof("moving 1");
                            }

                            if (rotating[0]) {
                                tms_infof("rotating 0");
                            }

                            if (rotating[1]) {
                                tms_infof("rotating 1");
                            }
#endif
                        }
                    }
                }
            } else {
                if (this->selection.e) {
                    this->recheck_all_placements();
                }
            }
        }

        if (!settings["touch_controls"]->v.b && pid == 1) {
            ui::open_dialog(DIALOG_SANDBOX_MENU);
            return T_OK;
        }

        moving[pid] = false;
        dragging[pid] = false;
        rotating[pid] = false;
        resizing[pid] = false;
    } else if (ev->type == TMS_EV_POINTER_SCROLL) {
        if (this->menu_handle_event(ev)) {
            return T_OK;
        }

        float z = ev->data.scroll.y < 0 ? -2.f : 2.f;

        if (settings["smooth_zoom"]->v.b) {
            this->cam_vel.z -= (z*2.f) * settings["zoom_speed"]->v.f;
        } else {
            this->cam_move(
                    0,
                    0,
                    z * settings["zoom_speed"]->v.f);
        }
    }
    return T_OK;
}

void
game::post_interact_select(entity *e)
{
    W->post_interact.insert(e);
}

int
game::interact_select(entity *e)
{
    int found = -1;

    if (!e) return -1;
    if (e->flag_active(ENTITY_IS_STATIC)) return -1;
    if (e->flag_active(ENTITY_IS_BULLET)) return -1;
    if (!e->flag_active(ENTITY_CAN_BE_GRABBED)) return -1;
    if (e != adventure::player && e->is_creature() && !static_cast<creature*>(e)->is_dead()) return -1;
    if (e->g_id == O_CHUNK) return -1;

    for (int ip=0; ip<MAX_INTERACTING; ip++) {
        found = ip;

        if (interacting[ip]) {
            destroy_mover(ip, (interacting[ip] == e));
        }
    }

    if (e == adventure::player) {
        tms_debugf("clicked player");
        if (adventure::player->inventory[RESOURCE_WOOD] >= 2) {
            adventure::player->inventory[RESOURCE_WOOD] -= 2;
            entity *l = of::create(O_LADDER_STEP);

            l->_pos = adventure::player->get_position();
            l->_angle = 0;
            l->set_layer(adventure::player->get_layer());

            G->emit(l, adventure::player, b2Vec2(0.f, 0.f), true);

            e = l;
            this->sel_p_ent = e;
            this->sel_p_offs = (tvec2){0.f, 0.f};
        } else {
            ui::message("Ladder step requires 2 wood to construct.");
            return -1;
        }
    }

    if (found != -1) {
        int ip = found;

        if (W->is_adventure() && adventure::player) {
            b2Vec2 p1 = adventure::player->get_position();
            b2Vec2 p2 = e->get_position();
            if ((p2-p1).LengthSquared() > INTERACT_REACH_SQUARED)
                return -1;

            if (e->is_protected(true)) {
                return -1;
            }

            interacting_discharge[ip] = new discharge_effect(
                    p1,
                    p2,
#if 0
                    b2Vec2(pt.x, pt.y),
#endif
                    layer[ip] * LAYER_DEPTH,
                    layer[ip] * LAYER_DEPTH,
                    8,
                    100.f
                    );
            interacting_discharge[ip]->line_width = .075f;
#if 0
            interacting_discharge_lp[ip] = interacting[ip]->world_to_body(b2Vec2(pt.x,pt.y), 0);
#endif
            interacting_discharge_lp[ip] = b2Vec2(0.f, 0.f);
            this->add_entity(interacting_discharge[ip]);

            if (e->is_creature() && W->level.flag_active(LVL_ABSORB_DEAD_ENEMIES)) {
                /* Reset the timer on our absorb if we were interacted with
                 * by the adventure robot! */
                this->timed_absorb(e->id, W->level.dead_enemy_absorb_time);
            }
        }

        interacting[ip] = e;
        interacting_p[ip] = 1;
        layer[ip] = interacting[ip]->get_layer();
        e->interacted_with = true;

#if 0
        tvec3 pt;
        W->get_layer_point(this->cam,
                (int)ev->data.motion.x,
                (int)ev->data.motion.y,
                layer[ip], &pt);
#endif

        //interacting[ip]->prepare_fadeout();
        for (int x=0; x<INTERACT_TRAIL_LEN; x++) {
            tmat4_copy(interacting_M[ip][x], interacting[ip]->M);
            tmat3_copy(interacting_N[ip][x], interacting[ip]->N);
        }

        if (mover_joint[ip]) {
            W->b2->DestroyJoint(mover_joint[ip]);
            mover_joint[ip] = 0;
        }

        b2BodyDef bd;
        bd.type = b2_kinematicBody;
        bd.position = interacting[ip]->get_position();

        b2MotorJointDef mjd;
        mjd.bodyA = W->ground;
        mjd.bodyB = interacting[ip]->get_body(0);
        mjd.linearOffset = mjd.bodyB->GetPosition();
        mjd.angularOffset = mjd.bodyB->GetAngle();
        mjd.maxForce = 3.5f;

        if (interacting[ip] && interacting[ip]->flag_active(ENTITY_IS_CRANE_PULLEY)) {
            mjd.maxTorque = 0.f;
        } else {
            mjd.maxTorque = 3.5f*M_PI;
        }

        mjd.correctionFactor = .35f;
        mjd.collideConnected = true;

        if (mjd.bodyB->GetType() != b2_staticBody && interacting[ip]->conn_ll == 0) {
            b2MassData m;
            mjd.bodyB->GetMassData(&m);

            m.mass = .0125f;
            m.I = .0125f;

            mjd.bodyB->SetMassData(&m);
            mjd.bodyB->SetAngularDamping(4.f);
            mjd.bodyB->SetLinearDamping(4.f);
            mjd.bodyB->SetGravityScale(0.f);

            mover_joint[ip] = static_cast<b2MotorJoint*>(W->b2->CreateJoint(&mjd));

            edevice *ed;
            if ((ed = e->get_edevice())) {
                ed->recreate_all_cable_joints();
            }
        }
    }

    return found;
}

#define BOLD_BEGIN
#define BOLD_END

// used with ENTITY_HAS_TRACKER
void
game::check_select_object(int x, int y, int pid)
{
    if (this->selection.e_saved != 0) {
        entity *e = this->sel_p_ent;

        switch (this->selection.e_saved->g_id) {
            case O_ESCRIPT:
                {
                    down[pid] = false;
                    char msg[2048];

                    if (this->sel_p_ent) {
                        entity *e = this->sel_p_ent;
                        snprintf(msg, 2047,
                                      BOLD_BEGIN "Name:" BOLD_END " %s\n"
                                      BOLD_BEGIN "ID:" BOLD_END " %u\n"
                                      BOLD_BEGIN "Type ID (g_id):" BOLD_END " %u\n"
                                      BOLD_BEGIN "Position:" BOLD_END " %.2f/%.2f\n"
                                      BOLD_BEGIN "Angle:" BOLD_END " %.2f\n",
                                      e->get_name(),
                                      e->id,
                                      e->g_id,
                                      e->get_position().x, e->get_position().y,
                                      e->get_angle()
                                );
                    } else {
                        tvec3 p;
                        W->get_layer_point(this->cam, x, y, 0, &p);
                        snprintf(msg, 2047,
                                      BOLD_BEGIN "No entity selected." BOLD_END "\n"
                                      BOLD_BEGIN "Click position:" BOLD_END " %.2f/%.2f\n",
                                      p.x, p.y
                                );
                    }

                    ui::alert(msg);
                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);

                }
                break;

            case O_RC_ACTIVATOR:
                {
                    rcactivator *_of = static_cast<rcactivator*>(this->selection.e_saved);

                    down[pid] = false;
                    if (this->sel_p_ent && this->sel_p_ent->flag_active(ENTITY_IS_CONTROL_PANEL)) {
                        _of->set_property(0, (uint32_t)this->sel_p_ent->id);
                        this->add_highlight(this->sel_p_ent, false);

                        char msg[256];
                        snprintf(msg, 256, "RC Activator connected to RC#%d",  this->sel_p_ent->id);
                        ui::message(msg);
                    } else if (this->sel_p_ent && this->sel_p_ent->id == _of->id) {
                        _of->set_property(0, (uint32_t)this->sel_p_ent->id);
                        this->add_highlight(this->sel_p_ent, false);
                        ui::message("RC Activator will now deactivate any activated RC upon activation.");
                    } else {
                        ui::message("RC Activator disconnected.");
                        _of->set_property(0, (uint32_t)0);
                    }
                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);
                }
                break;

            case O_PLAYER_ACTIVATOR:
                {
                    player_activator *_of = static_cast<player_activator*>(this->selection.e_saved);

                    down[pid] = false;
                    if (this->sel_p_ent && this->sel_p_ent->flag_active(ENTITY_IS_CREATURE)) {
                        _of->set_property(0, (uint32_t)this->sel_p_ent->id);
                        this->add_highlight(this->sel_p_ent, false);

                        char msg[256];
                        snprintf(msg, 256, "Player Activator connected to %d",  this->sel_p_ent->id);
                        ui::message(msg);
                    } else {
                        ui::message("Player Activator disconnected.");
                        _of->set_property(0, (uint32_t)0);
                    }
                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);
                }
                break;

            case O_ID_FIELD:
                {
                    objectfield *_of = static_cast<objectfield*>(this->selection.e_saved);

                    down[pid] = false;
                    if (this->sel_p_ent && this->sel_p_ent != _of && this->sel_p_ent->id != 0) {
                        _of->set_property(2, (uint32_t)this->sel_p_ent->id);
                        this->add_highlight(this->sel_p_ent, false);

                        char msg[256];
                        snprintf(msg, 256, "Now tracking %s(Unique ID %d)!", this->sel_p_ent->get_name(), this->sel_p_ent->id);
                        ui::message(msg);
                    } else {
                        ui::message("Resetting ID field detection.");
                        _of->set_property(2, (uint32_t)0);
                    }
                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);
                }
                break;

            case O_VENDOR:
                {
                    vendor *v = static_cast<vendor*>(this->selection.e_saved);

                    down[pid] = false;
                    bool set = false;

                    if (e && e != v && e->id != 0) {
                        if (e->g_id == O_RESOURCE) {
                            v->properties[0].v.i = e->g_id;
                            v->properties[1].v.i = ((resource*)e)->get_resource_type();
                            ui::messagef("Vendor now accepts %s as its currency.", resource_data[v->properties[1].v.i].name);
                            set = true;
                        } else if (e->g_id == O_ITEM) {
                            v->properties[0].v.i = e->g_id;
                            v->properties[1].v.i = ((item*)e)->get_item_type();
                            ui::messagef("Vendor now accepts %s as its currency.", item_options[v->properties[1].v.i].name);
                            set = true;
                        }
                    }

                    if (!set) {
                        if (e) {
                            ui::messagef("Vendor cannot accept %s as a currency.", e->get_name());
                        } else {
                            ui::message("Vendor now accepts no currencies.");
                            v->properties[0].v.i = 0;
                            v->properties[1].v.i = 0;
                        }
                    }

                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);
                }
                break;

            case O_TARGET_SETTER:
                {
                    objectfield *_of = static_cast<objectfield*>(this->selection.e_saved);

                    down[pid] = false;
                    if (this->sel_p_ent && this->sel_p_ent != _of && this->sel_p_ent->id != 0) {
                        _of->set_property(2, (uint32_t)this->sel_p_ent->id);
                        this->add_highlight(this->sel_p_ent, false);

                        char msg[256];
                        snprintf(msg, 256, "Now setting target to %s(Unique ID %d)!", this->sel_p_ent->get_name(), this->sel_p_ent->id);
                        ui::message(msg);
                    } else {
                        ui::message("This target setter will now reset the robots target.");
                        _of->set_property(2, (uint32_t)0);
                    }
                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);
                }
                break;

            case O_CAM_TARGETER:
                {
                    camtargeter *_of = static_cast<camtargeter*>(this->selection.e_saved);

                    down[pid] = false;
                    if (this->sel_p_ent && this->sel_p_ent != _of && this->sel_p_ent->id != 0) {
                        _of->set_property(0, (uint32_t)this->sel_p_ent->id);
                        this->add_highlight(this->sel_p_ent, false);

                        char msg[256];
                        snprintf(msg, 256, "Cam targeter now following %s!", this->sel_p_ent->get_name());
                        ui::message(msg);
                    } else {
                        ui::message("Now targeting nothing.");
                        _of->set_property(0, (uint32_t)0);
                    }
                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);

                }
                break;

            case O_OBJECT_FIELD:
                {
                    objectfield *_of = static_cast<objectfield*>(this->selection.e_saved);

                    down[pid] = false;
                    if (this->sel_p_ent && this->sel_p_ent != _of && this->sel_p_ent->id != 0) {
                        _of->set_property(2, (uint32_t)this->sel_p_ent->g_id);
                        this->add_highlight(this->sel_p_ent, false);

                        char msg[256];
                        snprintf(msg, 256, "Now tracking %s!", this->sel_p_ent->get_name());
                        ui::message(msg);
                    } else {
                        ui::message("Resetting object field detection.");
                        _of->set_property(2, (uint32_t)0);
                    }
                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);
                }
                break;

            case O_MINI_EMITTER:
            case O_EMITTER:
                {
                    emitter *_e = static_cast<emitter*>(this->selection.e_saved);

                    down[pid] = false;
                    if (this->sel_p_ent && this->sel_p_ent != _e && this->sel_p_ent->id != 0) {
                        if (_e->can_handle(this->sel_p_ent)) {
                            _e->copy_properties(this->sel_p_ent);
                            _e->set_property(1, (uint32_t)this->sel_p_ent->g_id);
                            _e->set_property(2, (uint32_t)this->sel_p_ent->id);
                            this->add_highlight(this->sel_p_ent, false);

                            char msg[256];
                            snprintf(msg, 256, "Now emitting %s!", this->sel_p_ent->get_name());
                            ui::message(msg);
                        } else {
                            char msg[256];
                            snprintf(msg, 256, "Unable to emit %s.", this->sel_p_ent->get_name());
                            ui::message(msg);
                        }
                    } else {
                        ui::message("Now emitting nothing!");
                        _e->set_property(1, (uint32_t)0);
                    }
                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);
                }
                break;

            case O_MINI_ABSORBER:
            case O_ABSORBER:
                {
                    absorber *_a = static_cast<absorber*>(this->selection.e_saved);

                    down[pid] = false;
                    if (this->sel_p_ent && this->sel_p_ent != _a && this->sel_p_ent->id != 0) {
                        if (_a->can_handle(this->sel_p_ent)) {
                            _a->set_property(1, (uint32_t)this->sel_p_ent->g_id);
                            this->add_highlight(this->sel_p_ent, false);

                            char msg[256];
                            snprintf(msg, 256, "Now absorbing %s!", this->sel_p_ent->get_name());
                            ui::message(msg);
                        } else {
                            char msg[256];
                            snprintf(msg, 256, "Unable to absorb %s.", this->sel_p_ent->get_name());
                            ui::message(msg);
                        }
                    } else {
                        ui::message("Now absorbing all possible objects!");
                        _a->set_property(1, (uint32_t)0);
                    }
                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);
                }
                break;

            case O_OBJECT_FINDER:
                {
                    object_finder *_e = static_cast<object_finder*>(this->selection.e_saved);

                    down[pid] = false;
                    if (this->sel_p_ent && this->sel_p_ent != _e && this->sel_p_ent->id != 0) {
                        char msg[256];
                        snprintf(msg, 256, "Now tracking %s!", this->sel_p_ent->get_name());
                        ui::message(msg);
                        _e->set_property(0, (uint32_t)this->sel_p_ent->id);
                        this->add_highlight(this->sel_p_ent, false);
                    } else {
                        ui::message("Now tracking nothing!");
                        _e->set_property(0, (uint32_t)0);
                    }

                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);
                }
                break;

            case O_HP_CONTROL:
                {
                    hp_control *hc = static_cast<hp_control*>(this->selection.e_saved);

                    down[pid] = false;
                    if (this->sel_p_ent && this->sel_p_ent != hc && this->sel_p_ent->id != 0) {
                        if (this->sel_p_ent->g_id == O_CHECKPOINT || this->sel_p_ent->id == W->level.get_adventure_id()) {
                            hc->properties[0].v.i = W->level.get_adventure_id();
                            ui::message("Controlling HP for the adventure robot!");
                        } else {
                            char msg[256];
                            snprintf(msg, 256, "Controlling HP for %s!", this->sel_p_ent->get_name());
                            ui::message(msg);
                            hc->properties[0].v.i = this->sel_p_ent->id;
                        }
                        this->add_highlight(this->sel_p_ent, false);
                    } else {
                        ui::message("HP control reset.");
                        hc->properties[0].v.i = 0;
                    }

                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);
                }
                break;

            case O_ROBOTMAN:
                {
                    robotman *rm = static_cast<robotman*>(this->selection.e_saved);

                    down[pid] = false;
                    if (this->sel_p_ent && this->sel_p_ent != rm && this->sel_p_ent->id != 0) {
                        if (this->sel_p_ent->flag_active(ENTITY_IS_ROBOT)) {
                            ui::messagef("Managing robot with id %u.", this->sel_p_ent->id);
                            rm->properties[0].v.i = this->sel_p_ent->id;
                        }
                        this->add_highlight(this->sel_p_ent, false);
                    } else {
                        ui::message("Robot Manager target reset.");
                        rm->properties[0].v.i = 0;
                    }

                    this->selection.load();
                    this->set_mode(GAME_MODE_DEFAULT);
                }
                break;
        }
    } else {
        entity *e;
        b2Body *body_unused;
        tvec2 offs_unused;
        uint8_t frame_unused;
        W->query(this->cam, x, y, &e, &body_unused, &offs_unused, &frame_unused, this->layer_vis, true);

        this->info_btn_pressed(e);
        if (e) this->add_highlight(e, false);
        this->set_mode(GAME_MODE_DEFAULT);
    }
}

bool
game::check_click_socksel()
{
    b2Vec2 click = b2Vec2(pt[this->ss_edev->get_entity()->get_layer()].x, pt[this->ss_edev->get_entity()->get_layer()].y);

    /* loop through all socket icons and check distance */
    for (int x=0; x<this->ss_num_socks; x++) {
        b2Vec2 ipos = this->ss_socks[x]->lpos;
        //ipos *= 1.f/ipos.Length();

        if (!this->ss_edev->scaleselect) {
            float ia = atan2f(ipos.y, ipos.x);
            ia += this->ss_socks[x]->abias;
            ipos = b2Vec2(cosf(ia), sinf(ia));

            ipos *= 1.5f * this->ss_anim;
        } else {
            ipos *= this->ss_edev->scalemodifier * this->ss_anim;
        }
        ipos = this->ss_edev->get_entity()->local_to_world(ipos, 0);

        float dist = (ipos - click).Length();

        if (dist < .375f) {
            this->select_socksel(x);
            return true;
        }
    }

    return false;
}

bool
game::check_click_rotate(int x, int y)
{
    if (this->selection.e->flag_active(ENTITY_ALLOW_ROTATION) && !this->selection.e->flag_active(ENTITY_CONNECTED_TO_BREADBOARD) &&
            (W->is_paused() || this->player_can_build())) {
        tvec3 pt;
        W->get_layer_point(this->cam, x, y, this->selection.e->get_layer(), &pt);
        b2Vec2 r = selection.e->local_to_world(b2Vec2(selection.e->get_width()+1.f, 0.f), this->selection.frame);
        float length = (r - b2Vec2(pt.x, pt.y)).Length();
        //tms_infof("length: %.2f", length);
        if (length < .75f) {
            b2Vec2 p;

            if (this->selection.e->gr) {
                p = this->selection.e->gr->get_position();
            } else {
                p = this->selection.e->get_position();
            }

            float a1 = atan2f(r.y - p.y, r.x - p.x);
            float a2 = atan2f(pt.y - p.y, pt.x - p.x);

            this->rot_offs = tmath_adist(a2, a1);

            /* set the pending to the current selected to override the deselect */
            //this->sel_p_ent = this->selection.e;
            //this->sel_p_body = this->selection.b;
            //this->sel_p_offs = this->selection.offs;
            //this->sel_p_frame = this->selection.frame;
            W->query(this->cam, x, y, &this->sel_p_ent, &this->sel_p_body, &this->sel_p_offs, &this->sel_p_frame, this->layer_vis);
            return true;
        }
    }

    return false;
}

bool
game::check_click_conntype(int x, int y)
{
    tvec3 dd = tms_camera_project(this->cam, this->cam->_position.x, this->cam->_position.y,this->cs_conn->layer*LAYER_DEPTH+((LAYER_DEPTH/2.f)*this->cs_conn->multilayer));
    tvec3 v1 = tms_camera_unproject(this->cam, 0.f, 0.f, dd.z);
    tvec3 v2 = tms_camera_unproject(this->cam, _tms.xppcm*.5f, 0.f, dd.z);

    float w = v2.x-v1.x;
    b2Vec2 p1 = this->cs_conn->p + b2Vec2(-CSCONN_OFFSX*w, CSCONN_OFFSY*w);
    b2Vec2 p2 = this->cs_conn->p + b2Vec2(CSCONN_OFFSX*w, CSCONN_OFFSY*w);

    b2Vec2 point = this->cs_conn->multilayer
                    ? b2Vec2(half_pt[this->cs_conn->layer].x, half_pt[this->cs_conn->layer].y)
                    : b2Vec2(pt[this->cs_conn->layer].x, pt[this->cs_conn->layer].y);

    if ((p1 - point).Length() < w) {
        this->apply_connection(this->cs_conn, 0);
    } else if ((p2 - point).Length() < w) {
        this->apply_connection(this->cs_conn, 1);
    }

    this->cs_conn = 0;

    /* always reset the mode after the first click */
    this->set_mode(GAME_MODE_DEFAULT);
    return true;
}

bool
game::check_quick_plug(uint64_t diff, int x, int y)
{
    tms_infof("checking quickplug %" PRIu64, diff);
    if (((this->state.sandbox && W->is_paused()) || (!W->is_paused() && this->player_can_build())) && this->get_mode() == GAME_MODE_DEFAULT) {
        if (diff < 300000) {
            if (this->sel_p_ent && this->sel_p_ent->flag_active(ENTITY_IS_EDEVICE)) {
                edevice *ed = this->sel_p_ent->get_edevice();

                bool avail = false;
                for (int xx=0; xx<ed->num_s_in; xx++) {
                    if (!ed->s_in[xx].p) {
                        avail = true;
                        break;
                    }
                }
                if (!avail) {
                    for (int xx=0; xx<ed->num_s_out; xx++) {
                        if (!ed->s_out[xx].p) {
                            avail = true;
                            break;
                        }
                    }
                }

                if (avail) {
                    this->selection.select(this->sel_p_ent, this->sel_p_body, this->sel_p_offs, this->sel_p_frame, true);
                    this->set_mode(GAME_MODE_QUICK_PLUG);
                    W->get_layer_point(this->cam, x, y, this->selection.e->get_layer(), &touch_quickplug_pos);
                    tms_infof("quickplug succeed");
                    return true;
                }
            }
            tms_infof("double click");
        }
    } else {
        tms_infof("mode is %d", this->get_mode());
    }

    return false;
}

bool
game::check_click_shape_resize(int x, int y)
{
    tms_debugf("check click shape resize");

    if (W->is_paused() && this->selection.e && this->selection.e->flag_active(ENTITY_IS_RESIZABLE)) {
        tms_debugf("checking");
        entity *e = this->selection.e;
        b2PolygonShape *sh = e->get_resizable_shape();

        if (!sh) return false;

        int vertices[POLYGON_MAX_CORNERS];

        int num_verts = e->get_resizable_vertices(vertices);

        tvec3 pt;
        W->get_layer_point(this->cam, x, y, e->get_layer(), &pt);
        b2Vec2 pp = b2Vec2(pt.x, pt.y);

        for (int x=0; x<num_verts; x++) {
            b2Vec2 p = e->local_to_world(sh->m_vertices[vertices[x]], 0);

            if (b2Distance(pp, p) < .25f) {
                tms_debugf("clicked corner %u (%u), local coord %f %f", vertices[x], x, sh->m_vertices[vertices[x]].x, sh->m_vertices[vertices[x]].y);
                resize_type = 0;
                resize_index = vertices[x];
                return true;
            }
        }

        /* TODO: check edges */
    }

    resize_index = -1;

    return false;
}

void
game::handle_shape_resize(float x, float y)
{
    entity *e = this->selection.e;

    if (!e) return;

    b2Vec2 projected = e->world_to_local(b2Vec2(x,y), 0);

    if (resize_type == 0) {
        /* snap the placements */
        projected.x = roundf(projected.x * 8.f)/8.f;
        projected.y = roundf(projected.y * 8.f)/8.f;

        b2PolygonShape *sh = e->get_resizable_shape();

        if (sh && resize_index >= 0) {
            b2Vec2 saved = sh->m_vertices[resize_index];
            sh->m_vertices[resize_index] = projected;

            /* perform sanity checks, make sure no edge angle makes a concave polygon */
            /* also make sure the distance to the previous or next vertex is greater than 1./16. */
            bool valid = sh->Validate()
                        && sh->ValidateMinEdgeLength(1.f/16.f)
                        //&& sh->ValidateVertexOrder()
                        && sh->ValidateAreaMin(.125f)
                        ;

            if (!valid) {
                tms_debugf("invalid polygon");
                sh->m_vertices[resize_index] = saved;
            } else {
                if (e->on_resize_vertex(resize_index, projected)) {
                    sh->RecalculateCentroid();
                    sh->RecalculateNormals();
                    e->get_body(0)->ResetMassData();
                } else {
                    sh->m_vertices[resize_index] = saved;
                }
            }
        }
    } else {
        /* TODO: edges */
    }
}

int
game::get_selected_shape_corner()
{
    return resize_index;
}

void
game::render_shape_resize()
{
    entity *e = this->selection.e;

    b2PolygonShape *sh = e->get_resizable_shape();

    if (!sh) return;

    int vertices[POLYGON_MAX_CORNERS];

    int num_verts = e->get_resizable_vertices(vertices);

    for (int x=0; x<num_verts; x++) {
        b2Vec2 p = e->local_to_world(sh->m_vertices[vertices[x]], 0);

        float size = .125f + (vertices[x] == resize_index)*.125f;

        if (vertices[x] == resize_index) {
            tms_ddraw_set_color(this->dd, .7f, .7f, 1.3f, .9f);
        } else {
            tms_ddraw_set_color(this->dd, .5f, .5f, 1.f, .8f);
        }

        tms_ddraw_circle(this->dd,
                p.x, p.y,
                size, size);

        tms_ddraw_set_color(this->dd, 1.f, 1.f, 1.f, .8f);
        tms_ddraw_lcircle(this->dd,
                p.x, p.y,
                size, size);
    }
}

bool
game::check_click_conn(int x, int y)
{
    c_map::iterator i = this->pairs.begin();

    for (;i != this->pairs.end(); ) {
        tms_infof("checking pair");
        connection *c = i->second;

        b2Vec2 point = c->multilayer
                        ? b2Vec2(half_pt[c->layer].x, half_pt[c->layer].y)
                        : b2Vec2(pt[c->layer].x, pt[c->layer].y);

        tvec3 dd = tms_camera_project(this->cam, this->cam->_position.x, this->cam->_position.y,c->layer*LAYER_DEPTH+((LAYER_DEPTH/2.f)*c->multilayer));
        tvec3 v1 = tms_camera_unproject(this->cam, 0.f, 0.f, dd.z);
        tvec3 v2 = tms_camera_unproject(this->cam, _tms.xppcm*.5f, 0.f, dd.z);
        float w = v2.x-v1.x;

        if ((point - c->p).Length() < w) {
            tms_infof("yes");
            if (c->typeselect) {
                /* let the user choose between the available types, like
                 * a weld joint or a pivot joint */
                this->set_mode(GAME_MODE_SELECT_CONN_TYPE);
                this->cs_timer = 0.f;
                this->cs_conn = c;
                return true;
            }

            if (W->is_adventure() && W->is_playing()) {
                this->apply_connection(c, c->option);
            } else {
                this->apply_connection(c, 0);
            }

            this->pairs.erase(i++);
            G->refresh_widgets();

            return true;
        } else {
            i ++;
        }
    }
    return false;
}

void
game::recheck_all_placements()
{
    W->step_count = 0;
    if (this->check_placement_allowed(this->selection.e)) {
        this->selection.e->on_release(this);

        if (this->selection.e->get_body(0) && this->selection.e->get_body(0)->GetType() == b2_kinematicBody)
            this->selection.e->get_body(0)->SetType(b2_dynamicBody);

        this->remove_highlight(this->selection.e);
    } else {
        this->add_highlight(this->selection.e, true);
        this->selection.e->on_release(this);
        this->selection.e->body->SetType(b2_kinematicBody);
        //this->selection.e->body->GetFixtureList()->SetFilterData(world::get_filter_for_layer(this->selection.e->get_layer(), 0));
        tms_infof("ERROR");
    }

    /* re-check all erroneous objects */
    for (int x=0; x<NUM_HL; x++) {
        if (this->hls[x].type & HL_TYPE_ERROR) {
            if (this->hls[x].e && this->hls[x].e != this->selection.e) {
                if (this->check_placement_allowed(this->hls[x].e)) {
                    if (this->hls[x].e->get_body(0)) {
                        if (this->hls[x].e->get_body(0)->GetType() == b2_kinematicBody)
                            this->hls[x].e->get_body(0)->SetType(b2_dynamicBody);
                    }
                    this->remove_highlight(this->hls[x].e);
                }
            }
        }
    }
}

bool
overlap_query::ReportFixture(b2Fixture *fx)
{
    b2Body* body = fx->GetBody();
    b2Shape* shape = fx->GetShape();

    entity *e = static_cast<entity*>(fx->GetUserData());

    if (e && (body->GetType() == b2_staticBody || !W->is_paused()) && !fx->IsSensor()) {

        if (e->get_layer() - test_e->get_layer() == this->desired_layerdist && (e->layer_mask & test_e->layer_mask) != 0) {
            if (b2TestOverlap(shape, 0, this->test_sh, 0, body->GetTransform(), this->test_bd->GetTransform()))
            {
                this->overlap = true;
                return false;
            }
        }
    }

    return true;
}

bool
game::ingame_layerswitch_test(entity *e, int dir)
{
    if (e->body && e->conn_ll == 0) {
        b2AABB aabb;
        overlap_query oq;
        oq.desired_layerdist = dir;

        b2Shape *shh = e->body->GetFixtureList()->GetShape();

        b2CircleShape c_sh;
        b2PolygonShape p_sh;

        oq.test_sh = shh;

        oq.test_e = e;
        oq.test_bd = e->body;

        e->body->GetFixtureList()->GetShape()->ComputeAABB(&aabb, e->body->GetTransform(), 0);
        //W->b2->QueryAABB(&oq, aabb);
        W->query_aabb(&oq, aabb);

        return !oq.overlap;
    }

    return false;
}

bool
game::check_placement_allowed(entity *e)
{
    if (this->state.sandbox)
        return true;

    if (e->body && e->conn_ll == 0 && (e->body->GetType() == b2_dynamicBody || e->body->GetType() == b2_kinematicBody)) {
        b2AABB aabb;
        overlap_query oq;
        oq.desired_layerdist = 0;

        b2Shape *shh = e->body->GetFixtureList()->GetShape();

        b2CircleShape c_sh;
        b2PolygonShape p_sh;

        /* bias the size of the shape */
        if (shh->m_type == b2Shape::e_circle) {
            c_sh = *((b2CircleShape*)shh);
            c_sh.m_radius -= fminf(OVERLAP_THRESHOLD, c_sh.m_radius/2.f);
            oq.test_sh = &c_sh;
        } else if ((shh->m_type = b2Shape::e_polygon)) {
            p_sh = *((b2PolygonShape*)shh);

            for (int x=0; x<p_sh.m_count; x++) {
                if (p_sh.m_vertices[x].x > 0.f) p_sh.m_vertices[x].x -= fminf(OVERLAP_THRESHOLD, p_sh.m_vertices[x].x/2.f);
                if (p_sh.m_vertices[x].x < 0.f) p_sh.m_vertices[x].x += fminf(OVERLAP_THRESHOLD, -p_sh.m_vertices[x].x/2.f);
                if (p_sh.m_vertices[x].y > 0.f) p_sh.m_vertices[x].y -= fminf(OVERLAP_THRESHOLD, p_sh.m_vertices[x].y/2.f);
                if (p_sh.m_vertices[x].y < 0.f) p_sh.m_vertices[x].y += fminf(OVERLAP_THRESHOLD, -p_sh.m_vertices[x].y/2.f);
            }
            oq.test_sh = &p_sh;
        } else {
            return true;
        }

        oq.test_e = e;
        oq.test_bd = e->body;

        e->body->GetFixtureList()->GetShape()->ComputeAABB(&aabb, e->body->GetTransform(), 0);
        W->b2->QueryAABB(&oq, aabb);

        return !oq.overlap;
    }

    return true;
}

int
game::handle_input(tms::event *ev, int action)
{
    if (ev->type == TMS_EV_KEY_PRESS || ev->type == TMS_EV_KEY_UP || ev->type == TMS_EV_KEY_REPEAT) {
        // Whenever a keyevent is received, we store its keymod state
        this->previous_keymod = this->current_keymod;
        this->current_keymod = ev->data.key.mod;
    }

    if (pscreen::handle_input(ev, action) == EVENT_DONE) {
        return EVENT_DONE;
    }

    switch (ev->type) {
        case TMS_EV_POINTER_DOWN:
#ifdef DEBUG
            this->print_screen_point_info((int)ev->data.motion.x, (int)ev->data.motion.y);
#endif

            if (prompt_is_open) return T_OK;

            this->hov_ent = 0;
            this->hov_text->active = false;

            P.focused = true;
            break;

        case TMS_EV_POINTER_UP:

            move_time = _tms.last_time;
            move_pos = tvec2f(ev->data.motion.x, ev->data.motion.y);
            move_queried = false;
            break;

        case TMS_EV_POINTER_MOVE:
            move_time = _tms.last_time;
            move_pos = tvec2f(ev->data.motion.x, ev->data.motion.y);
            move_queried = false;
            break;

        case TMS_EV_POINTER_DRAG:
            this->hov_ent = 0;
            this->hov_text->active = false;
            break;
    }

    if (W->is_paused()) {
        return this->handle_input_paused(ev, action);
    }

    return this->handle_input_playing(ev, action);
}

void
game::animate_disconnect(entity *e)
{
    /* create disconnect animations */
    connection *c = e->conn_ll;
    while (c) {
        this->add_ca(1.f, c->get_position());
        c = c->get_next(e);
    }
}

bool
game::damage_entity(entity *e, b2Fixture *fx, float dmg, const b2Vec2 &world_point,
        damage_type dmg_type, uint8_t damage_source, uint32_t attacker_id,
        bool damage_creature/*=true*/,
        bool damage_block/*=true*/,
        bool damage_interactive/*=true*/,
        bool damage_plant/*=true*/
        )
{
    if (e->is_interactive()) {
        if (damage_interactive) {
            this->damage_interactive(e, fx, fx->GetUserData2(), dmg, world_point, dmg_type);

            return true;
        }

        return false;
    }

    if (e->is_creature()) {
        if (damage_creature) {
            creature *c = static_cast<creature*>(e);

            c->damage(dmg, fx, dmg_type, damage_source, attacker_id);

            return true;
        }

        return false;
    }

    if (e->g_id == O_PLANT) {
        if (damage_plant) {
            this->damage_interactive(e, fx, fx->GetUserData2(), dmg, world_point, dmg_type);

            return true;
        }

        return false;
    }

    if (e->g_id == O_CHUNK || e->g_id == O_TPIXEL) {
        if (damage_block) {
            this->damage_interactive(e, fx, fx->GetUserData2(), dmg, world_point, dmg_type);

            return true;
        }

        return false;
    }

    return false;
}

void
game::damage_interactive(entity *e, b2Fixture *f, void *udata2, float dmg, const b2Vec2 &world_point, damage_type dmg_type)
{
    if (e->g_id == O_TPIXEL || e->g_id == O_CHUNK) {
        this->damage_tpixel(e, f, udata2, dmg, world_point, dmg_type);
    } else if (e->g_id == O_PLANT) {
        static_cast<plant*>(e)->damage_section(static_cast<plant_section*>(udata2), dmg, dmg_type);
    } else {
        e->interactive_hp -= dmg;
        tms_infof("interactive hp: %.2f", e->interactive_hp);
        if (e->interactive_hp <= 0.f) {
            if (this->absorb(e)) {
                W->events[WORLD_EVENT_INTERACTIVE_DESTROY] ++;
            }
        } else {
            this->add_highlight(e, HL_PRESET_DEFAULT, .2f);
        }
    }
}

void
game::damage_tpixel(entity *e, b2Fixture *f, void *udata2, float dmg, const b2Vec2 &world_point, damage_type dmg_type)
{
    struct tpixel_desc *desc = (struct tpixel_desc *)udata2;

    bool do_fadeout = false;
    float fadeout_time = .25f;
    level_chunk *c;

    int real_x, real_y;

    if (!desc) {
        tms_errorf("tpixel %p does not have desc [%d]", e, e->g_id);
        return;
    }

    if (desc->hp < 0.f) {
        /* this pixel has already been destroyed */
        return;
    }

    switch (dmg_type) {
        case DAMAGE_TYPE_FORCE:
        case DAMAGE_TYPE_ELECTRICITY:
        case DAMAGE_TYPE_OTHER:
        case DAMAGE_TYPE_HEAVY_SHARP:
            break;

        default:
            /* ignore all other damage types */
            return;
    }

    int layer = 0;
    if (f) layer = world::fixture_get_layer(f);

    int size = desc->size;

    if (e->g_id == O_CHUNK) {
        c = static_cast<level_chunk*>(e);
        do_fadeout = true;
        real_x = desc->get_local_x();
        real_y = desc->get_local_y();

        /* if this is a bigger pixel, try to find the nearest "subpixel" that was damaged */
        int nearest_x=0, nearest_y=0;
        if (size > 0) {
            int usize = (1<<desc->size);
            float nearest = 10000.f;
            for (int y=0; y<usize; y++) {
                for (int x=0; x<usize; x++) {
                    float d = b2DistanceSquared(world_point,
                            b2Vec2(
                                c->pos_x*8.f + (float)(real_x+x)*.5f,
                                c->pos_y*8.f + (float)(real_y+y)*.5f
                                ));
                    if (d < nearest) {
                        nearest_x = x;
                        nearest_y = y;
                        nearest = d;
                    }
                }
            }
        }
        real_x += nearest_x;
        real_y += nearest_y;

        if (this->state.new_adventure) {
            if (layer == 0) {
                if (c->pixels[1][real_y][real_x]) {
                    if (dmg_type == DAMAGE_TYPE_ELECTRICITY) {
                        // We only notify the user of the zapping rules he's
                        // trying to mine with the Zapper.
                        ui::message("Please zap in the two outer layers!");
                    }
                    return;
                }
            }
        }
    }

    desc->hp -= dmg;

    if (desc->hp <= 0.f) {
        fadeout_time = .55f;
        this->force_static_update = 2;

        /*
        tpixel *p = static_cast<tpixel*>(e);
        float wsize = (float)(1 << size)*.25f;
        float whs = wsize/2.f;
        float angle = p->get_angle();
        b2Vec2 pos = p->get_position();
        int layer = p->get_layer();

        float cs, sn;
        tmath_sincos(p->get_angle(), &sn, &cs);

        float rest = -p->interactive_hp;
        int diff = 0;
        if (rest > 20) rest = 20;
        do {
            rest /= 4.f;
            size --;
            if (diff > 0)
                whs = whs+whs/2.f;
            diff ++;
        } while (rest >= 1.f && size>0);

        b2Vec2 base = pos;
        base.x -= (cs-sn)*whs;
        base.y -= (sn+cs)*whs;

        // TODO: set new pixels hp to rest

        if (size >= 0 && rest < 1.f) {
            int count = 2*diff;
            for (int x=0; x<count; x++) {
                for (int y=0; y<count; y++) {
                    tpixel *n = static_cast<tpixel*>(of::create(O_TPIXEL));

                    b2Vec2 pp = b2Vec2(
                            ((float)x/(float)count)*cs - ((float)y/(float)count)*sn,
                            ((float)x/(float)count)*sn + ((float)y/(float)count)*cs
                            );

                    n->properties[0].v.i8 = size;
                    n->properties[1].v.i8 = p->properties[1].v.i8;
                    n->_pos.x = base.x+pp.x*wsize*2;
                    n->_pos.y = base.y+pp.y*wsize*2;
                    n->_angle = angle;
                    n->set_layer(layer);
                    this->emit(n, 0, b2Vec2(0.f,0.f));
                }
            }
        } else {
            p->drop_loot(1+((size+1)*4));
        }
        */

        switch (e->g_id) {
            case O_TPIXEL:
                {
                    if (this->absorb(e)) {
                        static_cast<tpixel*>(e)->drop_loot(1+((size+1)*4));
                    }
                }
                break;

            case O_CHUNK:
                {
                    c->pixels[layer][real_y][real_x] = 0;

                    /* remerge the chunk to fill in the holes */
                    c->merge(desc->get_local_x(), desc->get_local_y(), layer, desc->get_local_x()+(1<<desc->size), desc->get_local_y()+(1<<desc->size), layer+1);
                    W->to_be_reloaded.insert(c);
                }
                break;
        }
    }


    if (do_fadeout) {
        fadeout_event *ev = new fadeout_event();
        fadeout_entity fe;

        /* create an arbitrary fadeout entity */
        tpixel *t = new tpixel();
        t->_pos = b2Vec2(c->pos_x*8.f + real_x*.5f, c->pos_y*8.f + real_y*.5f);
        t->_angle = 0.f;
        t->properties[0].v.i8 = 0;
        //t->set_block_type(desc->material);
        if (fadeout_time < .5f) {
            t->set_block_type(0);
        } else {
            t->set_block_type(4);
        }
        t->set_layer(layer);
        t->on_load(false, false);
        t->prepare_fadeout();
        t->M[14] += .05f;

        fe.velocity = b2Vec2(0.f, 0.f);
        fe.e = t;
        fe.do_free = true;
        ev->time = fadeout_time;
        ev->entities.push_back(fe);

        G->lock();
        G->fadeouts.insert(ev);
        G->unlock();
    }
}

void
game::emit_partial_from_buffer(const char *buf, uint16_t buf_len, b2Vec2 position)
{
    tms_infof("emit partial from buffer");
    tms_assertf(W->is_paused() == false, "emit (multi) called when world was paused");

    pending_emit ee(buf, buf_len, position);
    W->to_be_emitted.push_back(ee);
}

void
game::emit(entity *e, entity *emitter, b2Vec2 velocity, bool immediate)
{
    tms_assertf(W->is_paused() == false, "emit (single) called when world was paused");

    pending_emit ee(e, emitter, velocity);

    W->to_be_emitted.push_back(ee);

    if (immediate) {
        W->emit_all();
    }
}

/**
 * post_emit should be used whenever the emit needs to be placed in a "dangerous" place.
 * Dangerous places include any function that are called when an entity is loaded,
 * added to world, emitted. Such as:
 * init()
 * setup()
 * on_entity_play()
 * on_load()
 * add_to_world()
 **/
void
game::post_emit(entity *e, entity *emitter, b2Vec2 velocity)
{
    tms_assertf(W->is_paused() == false, "emit (single) called when world was paused");

    pending_emit ee(e, emitter, velocity);

    W->post_to_be_emitted.push_back(ee);
}

void
game::absorb(std::set<entity *> *loop)
{
    tms_assertf(W->is_paused() == false, "absorb (multi) called when world was paused");

    tms_infof("absorbing loop");

    for (std::set<entity*>::iterator i = loop->begin(); i != loop->end(); i++) {
        ((entity*)*i)->set_flag(ENTITY_IS_ABSORBED, true);
        W->to_be_absorbed.insert(pending_absorb(*i));
    }
}

bool
game::absorb(entity *e, bool include_connection/*=false*/, entity *absorber/*=0*/, b2Vec2 absorber_point/*=b2Vec2(0.f, 0.f)*/, uint8_t absorber_frame/*=0*/)
{
    tms_assertf(W->is_paused() == false, "absorb (simple) called when world was paused");

    // return false for any entities that have already been absorbed
    if (e->flag_active(ENTITY_IS_ABSORBED)) return false;

    if (!e->conn_ll || include_connection) { /* do not absorb connected objects */
        e->set_flag(ENTITY_IS_ABSORBED, true);

        W->to_be_absorbed.insert(pending_absorb(e, absorber, absorber_point, absorber_frame));

        if (this->selection.e == e) {
            this->selection.disable();
        }
        return true;
    }

    return false;
}

bool
game::timed_absorb(uint32_t id, double time)
{
    tms_assertf(W->is_paused() == false, "absorb (timed) called when world was paused");

    int64_t itime = (int64_t)(time * 1000000.0);
    std::pair<std::map<uint32_t, int64_t>::iterator, bool> ret;
    ret = W->timed_absorb.insert(std::pair<uint32_t, int64_t>(id, itime));

    if (!ret.second) {
        (ret.first)->second = itime;
    }

    return true;
}

bool
game::timed_absorb(entity *e, double time)
{
    if (e) {
        if (e->flag_active(ENTITY_IS_ABSORBED)) return false;

        if (!e->conn_ll) { /* do not absorb connected objects */
            return this->timed_absorb(e->id, time);
        }
    }

    return false;
}

/* construct an entity at the mouse position */
entity*
game::editor_construct_entity(uint32_t g_id, int pid/*=0*/, bool force_on_pid/*=false*/, b2Vec2 offs/*=b2Vec2(0.f,0.f)*/)
{
    if (!this->state.sandbox) {
        tms_errorf("can not create an entity if not sandbox");
        return 0;
    }

    // Override for partial
    if (g_id == O_DAMPER_2) {
        g_id = O_DAMPER;
    } else if (g_id == O_RUBBERBAND_2) {
        g_id = O_RUBBERBAND;
    } else if (g_id == O_OPEN_PIVOT_2) {
        g_id = O_OPEN_PIVOT;
    }

    tvec3 pos;
    if (!settings["touch_controls"]->v.b) {
        float mx, my;
        SDL_GetMouseState(&mx, &my);
        W->get_layer_point(this->cam, (int)mx, _tms.window_height-(int)my, 0.f, &pos);
    } else {
        if (force_on_pid)
            W->get_layer_point(this->cam, touch_proj[pid].x, touch_proj[pid].y, 0.f, &pos);
        else
            pos = this->cam->_position;
    }

    pos.x += offs.x;
    pos.y += offs.y;

    entity *e = of::create(g_id);

    if (!e) {
        tms_errorf("Unable to create an object with g_id %u", g_id);
        return 0;
    }

    e->_angle = 0.f;
    e->_pos = b2Vec2(pos.x, pos.y);
    e->set_layer(this->state.edit_layer);

    e->ghost_update();

    if (this->selection.e && e->compatible_with(this->selection.e) && this->selection.e->g_id != O_ROPE) {
        /* copy properties from the selected object */
        this->copy_properties(e, this->selection.e);

        e->_angle = this->selection.e->get_angle();
        e->set_angle(this->selection.e->get_angle());
        e->set_layer(this->selection.e->get_layer());
        e->set_moveable(this->selection.e->is_moveable());

    }

    e->on_load(true, false);

    W->add(e);
    this->add_entity(e);

    e->construct();
    e->on_pause();

    if (this->get_mode() == GAME_MODE_DRAW) {
        basepixel *p = static_cast<basepixel*>(e);
        basepixel::radius = BASE_PIXEL_RADIUS; /* Set pixel radius back to its default value */

        if (p->got_pos && (p->get_position().y > -((float)W->level.size_y[0]) || W->ground_fx[3] == 0)) {
            //p->merge_neighbours();
            return e;
        } else {
            int mid = (p->search_width-1)/2;
            int index = mid + (mid * p->search_width); // we offset 'middle' a little bit
            if (p->found[index]) {
#if 0
                //if (p->found[index]->properties[1].v.i8 != this->brush_material) {
                    int yy;
                    for (yy=mid; yy<p->search_width; yy++) {
                        int ii = mid+(yy*p->search_width);
                        if (!p->found[ii]) break;
                    }
                    if (yy-mid >= p->search_width/2)
                        p->found[index]->properties[1].v.i8 = 1;
                    else
                        p->found[index]->properties[1].v.i8 = this->brush_material;
                    /*
                    this->remove_entity(p->found[index]);
                    W->remove(p->found[index]);
                    delete p->found[index];
                    */
                //}
#endif
            }
            this->remove_entity(e);
            W->remove(e);
            delete e;
            return 0;
        }
    }

    if (e->type == ENTITY_CABLE) {
        cable *c = static_cast<cable*>(e);
        this->selection.select(c->p[0], c->p[0]->get_body(0), (tvec2){0,0}, 0, false);
    } else {
        this->selection.select(e, NULL, (tvec2){0,0}, 0, false);
    }

    if ((int)e->g_id != this->recent[0]) {
        int p = -1;
        for (int x=0; x<MAX_RECENT; x++) {
            if (this->recent[x] == (int)e->g_id) {
                p = x;
                break;
            }
        }

        if (p != -1) {
            for (int x=p; x>0; x--)
                this->recent[x] = this->recent[x-1];
        }
        for (int x=MAX_RECENT-1; x>0; x--)
            this->recent[x] = this->recent[x-1];

        this->recent[0] = (int)e->g_id;
    }

    if (W->is_paused()) {
        ui::emit_signal(SIGNAL_ENTITY_CONSTRUCTED, UINT_TO_VOID(e->id));
    }

    this->state.modified = true;

    return e;
}

/* construct an item at the mouse position */
entity*
game::editor_construct_item(uint32_t item_id)
{
    uint32_t g_id = O_ITEM;
    if (!this->state.sandbox) {
        tms_errorf("can not create an entity if not sandbox");
        return 0;
    }

    tvec3 pos;
    if (!settings["touch_controls"]->v.b) {
        float mx, my;
        SDL_GetMouseState(&mx, &my);
        W->get_layer_point(this->cam, (int)mx, _tms.window_height-(int)my, 0.f, &pos);
    } else {
        pos = this->cam->_position;
    }

    entity *e = of::create(g_id);

    if (!e) {
        tms_errorf("Unable to create an object with g_id %u", g_id);
        return 0;
    }

    e->_angle = 0.f;
    e->_pos = b2Vec2(pos.x, pos.y);
    e->set_layer(this->state.edit_layer);
    ((item*)e)->set_item_type(item_id);

    e->ghost_update();

    if (this->selection.e && e->g_id == this->selection.e->g_id && this->selection.e->g_id != 12 && (this->selection.e->g_id != O_TPIXEL || this->get_mode() != GAME_MODE_DRAW)) {
        e->_angle = this->selection.e->get_angle();
        e->set_angle(this->selection.e->get_angle());
        e->set_layer(this->selection.e->get_layer());
        e->set_moveable(this->selection.e->is_moveable());

    }

    e->on_load(true, false);

    W->add(e);
    this->add_entity(e);

    e->construct();
    e->on_pause();

    this->selection.select(e, NULL, (tvec2){0,0}, 0, false);

    return e;
}

/* construct an item at the mouse position */
entity*
game::editor_construct_decoration(uint32_t decoration_id)
{
    uint32_t g_id = O_DECORATION;
    if (!this->state.sandbox) {
        tms_errorf("can not create an entity if not sandbox");
        return 0;
    }

    tvec3 pos;
    if (!settings["touch_controls"]->v.b) {
        float mx, my;
        SDL_GetMouseState(&mx, &my);
        W->get_layer_point(this->cam, (int)mx, _tms.window_height-(int)my, 0.f, &pos);
    } else {
        pos = this->cam->_position;
    }

    entity *e = of::create(g_id);

    if (!e) {
        tms_errorf("Unable to create an object with g_id %u", g_id);
        return 0;
    }

    e->_angle = 0.f;
    e->_pos = b2Vec2(pos.x, pos.y);
    e->set_layer(this->state.edit_layer);
    ((decoration*)e)->set_decoration_type(decoration_id);

    e->ghost_update();

    if (this->selection.e && e->g_id == this->selection.e->g_id && this->selection.e->g_id != 12 && (this->selection.e->g_id != O_TPIXEL || this->get_mode() != GAME_MODE_DRAW)) {
        e->_angle = this->selection.e->get_angle();
        e->set_angle(this->selection.e->get_angle());
        e->set_layer(this->selection.e->get_layer());
        e->set_moveable(this->selection.e->is_moveable());

    }

    e->on_load(true, false);

    W->add(e);
    this->add_entity(e);

    e->construct();
    e->on_pause();

    this->selection.select(e, NULL, (tvec2){0,0}, 0, false);

    return e;
}

void
game::update_last_cursor_pos(int x, int y)
{
    this->last_cursor_pos_x = x;
    this->last_cursor_pos_y = y;
}

void
game::refresh_last_cursor_pos()
{
    if (!settings["touch_controls"]->v.b) {
        float mx, my;
        SDL_GetMouseState(&mx, &my);
        this->last_cursor_pos_x = (int)mx;
        this->last_cursor_pos_y = _tms.window_height - (int)my;
    }
}

b2Vec2
game::get_last_cursor_pos(int layer)
{
    this->refresh_last_cursor_pos();

    tvec3 pt;
    W->get_layer_point(this->cam, this->last_cursor_pos_x, this->last_cursor_pos_y, layer, &pt);
    return b2Vec2(pt.x, pt.y);
}

void
game::puzzle_play(int type)
{
    if (type == PUZZLE_SIMULATE) {
        this->save(false, true);
        this->do_play();
    } else if (type == PUZZLE_TEST_PLAY) {
        this->save(false);
        this->state.test_playing = true;
        this->state.puzzle_state = 1;
        this->save(false, true);
        this->open_play(LEVEL_LOCAL, W->level.local_id, NULL, true, 0);
        ui::message("Now testplaying your level! Press B to return.");
    } else {
        tms_warnf("Invalid input for puzzle play");
    }
}

void
game::refresh_score()
{
    this->state.m_score = this->get_real_score();
}

void
game::add_score(int score)
{
    if (this->state.finished) {
        return;
    }

    if (score > 0) {
        this->score_highlight += score/250.f;
        this->score_highlight = fminf(this->score_highlight, 1.f);
    }

    int new_score = this->get_real_score() + score;

    if (new_score < 0) {
        new_score = 0;
    }

    W->score_helper = new_score ^ SCORE_XOR;

    this->refresh_score();

    if (W->is_playing()) {
        if (this->get_score() >= W->level.final_score && W->level.final_score != 0) {
            this->finish(true);
        }
    }
}

void
game::set_score(int new_score)
{
    if (this->state.finished) {
        return;
    }

    if (this->get_score() != new_score) {
        this->score_highlight += (new_score - this->get_score()) / 250.f;
        this->score_highlight = fminf(this->score_highlight, 1.f);
    }

    if (new_score < 0) {
        new_score = 0;
    }

    W->score_helper = new_score ^ SCORE_XOR;

    this->refresh_score();

    if (W->is_playing()) {
        if (this->get_score() >= W->level.final_score && W->level.final_score != 0) {
            this->finish(true);
        }
    }
}

void
game::destroy_joint(b2Joint *j)
{
    if (W->is_paused()) return;
    W->to_be_destroyed.insert(j);
}

void
game::add_destructable_joint(b2Joint *j, float max_force)
{
    if (W->is_paused()) return;
    W->destructable_joints.insert(std::make_pair(j, max_force));
}

void
selection_handler::select(entity_set *new_m)
{
    if (G->multi.additive_selection && this->m) {
        /* First we check if this will be an addition or a removal */
        bool remove = false;
        if (G->sel_p_ent) {
            if (this->m->find(G->sel_p_ent) != this->m->end()) {
                remove = true;
            }
        } else {
            /* we will loop through all new entities and if all of them are already added,
             * we will remove them! */
            remove = true;

            for (entity_set::iterator it = new_m->begin(); it != new_m->end(); ++it) {
                if (this->m->find(*it) == this->m->end()) {
                    remove = false;
                    break;
                }
            }
        }

        G->add_highlight_multi(new_m, HL_PRESET_DEFAULT_MULTI, 1.f);

        if (remove) {
            tms_infof("selection: remove multi (%d)", (int)new_m->size());
            for (entity_set::iterator it = new_m->begin(); it != new_m->end(); ++it) {
                this->m->erase(*it);
            }
        } else {
            this->m->insert(new_m->begin(), new_m->end());
            tms_infof("selection: add multi (%d)", (int)new_m->size());
        }
    } else {
        this->disable(false);
        this->m = new_m;

        if (this->m) {
            tms_infof("selection: new multi (%d)", (int)new_m->size());
            G->add_highlight_multi(new_m, HL_PRESET_NO_FREE_MULTI, 1.f);
        }
    }

    uint8_t type = HL_PRESET_NO_FREE_MULTI + HL_TYPE_PERSISTENT + HL_TYPE_TINT;
    G->add_highlight_multi(this->m, type, 0.01f);

    G->refresh_widgets();
}

void
selection_handler::select(connection *c)
{
    this->disable(false);
    this->c = c;

    if (c) {
        tms_infof("selection: connection (%s:%p) (g_id: <%d,%d>, id: <%d,%d>)",
                c->type == CONN_GROUP ? "CONN_GROUP" :
                c->type == CONN_WELD ? "CONN_WELD" :
                c->type == CONN_PLATE ? "CONN_PLATE" :
                c->type == CONN_PIVOT ? "CONN_PIVOT" :
                c->type == CONN_CUSTOM ? "CONN_CUSTOM"
                : "<invalid>",
                c,
                c->e->g_id, c->o->g_id, c->e->id, c->o->id);
    }

    G->refresh_widgets();
}

void
selection_handler::select(entity *e, b2Body *b, tvec2 offs, uint8_t frame, bool ui)
{
    G->set_mode(GAME_MODE_DEFAULT);

    this->disable(false);

    if (e) G->starred.erase(e->get_property_entity());

    this->e = e;
    this->b = b;
    this->offs = offs;
    this->frame = frame;

#ifdef DEBUG
    if (e) {
        tms_infof("selection: entity %s:%p (g_id: %d, id: %u, pos: %4.2f/%4.2f, grouped:%s(%u,%d,%d)). sensor:%s",
                e->get_name(), e, e->g_id, e->id, e->get_position().x, e->get_position().y, e->gr?"YES":"NO", e->gr?e->gr->id:0, e->gr?(int)e->gr->entities.size():0, e->gr?(int)e->gr->connections.size():0,
                (e->get_body(0) ? (e->get_body(0)->GetFixtureList() ? (e->get_body(0)->GetFixtureList()->IsSensor() ? "YES" : "NO") : "NO") : "NO"));

        if (e->g_id == O_GOAL && G->state.pkg && G->state.pkg->type == LEVEL_MAIN) {
            G->finish(true);
        }
    }
#endif

    if (e) {
        e->set_flag(ENTITY_CONNECTED_TO_BREADBOARD, false);

        connection *c = e->conn_ll;

        if (c) {
            do {
                connection **ccn = &c->next[(c->e == e) ? 0 : 1];
                entity *o = c->e == e ? c->o : c->e;
                c = *ccn;
                if (!o) continue;
                if (o->g_id == O_BREADBOARD) {
                    e->set_flag(ENTITY_CONNECTED_TO_BREADBOARD, true);
                    break;
                }
            } while (c);
        }
    }

    G->refresh_widgets();
}

void
selection_handler::disable(bool refresh_widgets/*=true*/)
{
    //tms_infof("Disabling selection");
    if (this->m) {
        for (int x=0; x<NUM_HL; x++) {
            if (G->hls[x].entities == this->m) {
                G->hls[x].entities = 0;
                G->hls[x].type = HL_PRESET_DEFAULT;
                G->hls[x].time = 0.f;
            }
        }

        delete this->m;
    }

    this->m = 0;
    this->e = 0;
    this->c = 0;

    if (G) {
        G->state.edev_labels = false;

        if (refresh_widgets) {
            G->refresh_widgets();
        }
    }
}

void
game::say_goodbye(b2Joint *j)
{
    if (W->is_adventure() && adventure::is_player_alive()) {
        if (j == adventure::player->activator_joint) {
            adventure::player->activator_joint = 0;
        }
    }

    W->destructable_joints.erase(j);

    /**
     * Removing the joint from the to_be_destroyed list here is very risky;
     * there's a high chance this function is called
     * right when world::destroy_joints is called and the set
     * is being iterated.
     **/
    // W->to_be_destroyed.erase(j);

    int x = this->is_mover_joint(j);
    if (x != -1) {
        mover_joint[x] = 0;
    } else {
        joint_info *ji;
        if ((ji = (joint_info*)j->GetUserData())) {
            switch (ji->type) {
                case JOINT_TYPE_CONN:
                    //tms_debugf("say goodbye conn joint");
                    break;

                case JOINT_TYPE_CABLE:
                    {
                        cable *c = (cable*)ji->data;
                        if (c) {
                            c->joint = 0;
                            c->ji = 0;
                            //c->create_joint();
                        }
                    }
                    break;

                case JOINT_TYPE_BACKPACK:
                    {
                        /* FIXME */
#if 0
                        adventure::joint_backpack = 0;
                        adventure::bpack = 0;
                        adventure::bpack_panel = 0;
                        if (adventure::player) {
                            adventure::player->unset_attached();
                        }
                        this->set_control_panel(adventure::player);
#endif

                        /* we return here to stop the joint_info from being destroyed */
                        return;
                    }
                    break;

                case JOINT_TYPE_SCUP:
                    {
                        scup *e = static_cast<scup*>(ji->data);
                        for (int n=0; n<SCUP_NUM_JOINTS; ++n) {
                            if (j == e->j[n]) {
                                e->j[n] = 0;
                            }
                        }

                        e->stuck = false;
                    }
                    break;

                case JOINT_TYPE_RAGDOLL:
                    {
                        ragdoll *r = static_cast<ragdoll*>(ji->data);
                        for (int x=0; x<9; x++) {
                            if (r->joints[x] == j) {
                                r->joints[x] = 0;
                            }
                        }
                    }
                    break;

                default:
                    tms_debugf("Unhandled say_goodbye joint type: %d", ji->type);
                    break;
            }

            ji->destroy();
        }
    }
}

void
game::window_size_changed()
{
    this->cam->width = _tms.window_width;
    this->cam->height = _tms.window_height;
    if (this->get_surface() && this->get_surface()->ddraw) {
        float projection[16];
        tmat4_set_ortho(projection, 0, _tms.window_width, 0, _tms.window_height, 1, -1);
        tms_ddraw_set_matrices(this->get_surface()->ddraw, 0, projection);
    }

    this->refresh_gui();
}

static entity *previous_panel = 0;

void
game::set_mode(int new_mode)
{
    int cur_mode = this->get_mode();

    /* Any 'exit mode code' can be placed here */
    switch (cur_mode) {
        case GAME_MODE_FACTORY:
        case GAME_MODE_REPAIR_STATION:
            adventure::init_widgets();
            break;

        case GAME_MODE_INVENTORY:
            adventure::show_left_widgets();
            this->hide_inventory_widgets();
            break;

        case GAME_MODE_DRAW:
            this->state.edit_layer = 0;
            this->wdg_mode->s[0] = gui_spritesheet::get_sprite(S_CONFIG);
            break;

        case GAME_MODE_MULTISEL:
            this->selection.disable();
            this->wdg_mode->s[0] = gui_spritesheet::get_sprite(S_CONFIG);
            break;

        case GAME_MODE_CONN_EDIT:
            this->wdg_mode->s[0] = gui_spritesheet::get_sprite(S_CONFIG);
            break;

        case GAME_MODE_EDIT_PANEL:
            if (!W->is_paused()) {
                this->set_control_panel(previous_panel);
            }
            break;

        case GAME_MODE_SELECT_SOCKET:
            if (this->ss_quickplug_step2) {
                this->ss_quickplug_step2 = false;
                this->ss_plug->c->freeze = true;
                this->ss_plug->c->disconnect((plug*)this->ss_plug->get_other());
                delete (cable*)this->ss_plug->c;
            }
            break;
    }

    if (cur_mode != new_mode) {
        tms_infof("New mode: %d. Previous mode: %d", new_mode, cur_mode);
        /* Any 'init new mode' code can be placed here */
        switch (new_mode) {
            case GAME_MODE_FACTORY:
            case GAME_MODE_REPAIR_STATION:
                adventure::clear_widgets();
                break;

            case GAME_MODE_DRAW:
                this->state.edit_layer = 2;
                this->selection.disable(false);
                this->wdg_mode->s[0] = gui_spritesheet::get_sprite(S_TPIXEL_MULTI);
                break;

            case GAME_MODE_MULTISEL:
                this->wdg_mode->s[0] = gui_spritesheet::get_sprite(S_MULTISEL);
                this->selection.disable(false);
                this->multi.reset();
                break;

            case GAME_MODE_CONN_EDIT:
                this->wdg_mode->s[0] = gui_spritesheet::get_sprite(S_CONNEDIT);
                this->selection.disable(false);
                break;

            case GAME_MODE_INVENTORY:
                this->inventory_scroll_offset = 0.f; // should we reset the offset when we begin showing the inventory?
                adventure::hide_left_widgets();
                this->show_inventory_widgets();
                break;

            case GAME_MODE_EDIT_PANEL:
                {
                    if (!W->is_paused()) {
                        previous_panel = this->current_panel;
                        this->set_control_panel(0);
                    }

                    this->panel_edit_refresh();
                }
                break;
        }

        this->_mode = new_mode;
        this->refresh_widgets();
    }
}

static struct tms_wdg *inventory_widgets[NUM_RESOURCES];
static bool inventory_widgets_initialized = false;

void
inventory_widget_on_change(struct tms_wdg *w, float values[2])
{
    float value = values[0];
    if (value == 1.f) {
        int resource_id = VOID_TO_INT(w->data);
        G->drop_speed = 1.f;
        G->dropping = resource_id;
        G->drop_step = W->step_count;
        G->drop_amount = 1;

        adventure::player->drop_resource(resource_id, G->drop_amount, b2Vec2(adventure::player->look_dir*1.25f, .75f));

        if (!adventure::player->get_num_resources(resource_id)) {
            G->refresh_inventory_widgets();
        }
    } else {
        G->dropping = -1;
    }
}

static void
init_inventory_widgets()
{
    if (inventory_widgets_initialized) return;

    int iw = _tms.xppcm*.375f;
    int ih = _tms.yppcm*.375f;
    for (int n=0; n<NUM_RESOURCES; ++n) {
        inventory_widgets[n] = tms_wdg_alloc(TMS_WDG_BUTTON, gui_spritesheet::get_sprite(S_INVENTORY_ICONS0+n), 0);
        inventory_widgets[n]->on_change = &inventory_widget_on_change;
        inventory_widgets[n]->size.x = iw;
        inventory_widgets[n]->size.y = ih;
        inventory_widgets[n]->extra_right = iw * 1.2f;
        inventory_widgets[n]->data = INT_TO_VOID(n);
    }

    inventory_widgets_initialized = true;
}

void
game::show_inventory_widgets()
{
    init_inventory_widgets();

    int ih = _tms.yppcm*.375f;
    float x = this->get_bmenu_x();
    float y = _tms.window_height - this->get_bmenu_y() - this->inventory_scroll_offset;
    for (int n=0; n<NUM_RESOURCES; ++n) {
        this->get_surface()->add_widget(inventory_widgets[n]);
        if (adventure::player->get_num_resources(n)) {
            inventory_widgets[n]->pos.x = x;
            inventory_widgets[n]->pos.y = y;
            y -= ih*1.5f;
        } else {
            inventory_widgets[n]->pos.x = -500.f;
            inventory_widgets[n]->pos.y = -500.f;
        }
    }

    this->inventory_highest_y = y;
}

void
game::hide_inventory_widgets()
{
    for (int n=0; n<NUM_RESOURCES; ++n) {
        this->get_surface()->remove_widget(inventory_widgets[n]);
    }
}

void
game::refresh_inventory_widgets()
{
    if (this->get_mode() != GAME_MODE_INVENTORY) return;

    int ih = _tms.yppcm*.375f;
    float x = this->get_bmenu_x();
    float y = _tms.window_height - this->get_bmenu_y() - this->inventory_scroll_offset;
    for (int n=0; n<NUM_RESOURCES; ++n) {
        if (adventure::player->get_num_resources(n)) {
            inventory_widgets[n]->pos.x = x;
            inventory_widgets[n]->pos.y = y;
            y -= ih*1.5f;
        } else {
            /* XXX: Hide widgets in another way maybe? */
            inventory_widgets[n]->pos.x = -500.f;
            inventory_widgets[n]->pos.y = -500.f;
        }
    }

    this->inventory_highest_y = y;
}

void
game::draw_entity_bar(entity *e, float v, float y_offset, const tvec3 &color, float alpha)
{
    b2Vec2 p = e->get_position() + b2Vec2(0.f, y_offset);
    float barw = v * (BAR_WIDTH-.05f);
    float mv[16];

    tmat4_copy(mv, this->cam->view);
    tmat4_translate(mv, 0, 0, e->get_layer()*LAYER_DEPTH);
    tms_ddraw_set_matrices(this->dd, mv, this->cam->projection);

    tms_ddraw_set_color(this->dd, 0.f, 0.f, 0.f, alpha);
    tms_ddraw_square(this->dd,
            p.x, p.y,
            BAR_WIDTH,
            BAR_HEIGHT
            );

    tms_ddraw_set_color(this->dd, TVEC3_INLINE(color), alpha);
    tms_ddraw_square(this->dd,
            p.x, p.y,
            barw,
            BAR_HEIGHT * 0.75f
            );
}

/**
 * Can be used to perform an immediate camera move,
 * regardless of dt and the likes, bypassing cam_vel.
 **/
void
game::cam_move(float x, float y, float z)
{
    if (!W->level.flag_active(LVL_DISABLE_CAM_MOVEMENT) || (this->state.sandbox && W->is_paused())) {
        this->cam->_position.x -= x;
        this->cam->_position.y -= y;

        this->cam_rel_pos.x -= x;
        this->cam_rel_pos.y -= y;
    }

    if (!W->level.flag_active(LVL_DISABLE_ZOOM) || this->state.sandbox) {
        this->cam->_position.z -= z;
    }
}

/**
 * Delete the current multiselection
 **/
void
game::_multidelete()
{
    if (this->get_mode() == GAME_MODE_MULTISEL && this->selection.m) {
        do {
            entity_set my_copy(*this->selection.m);

            tms_debugf("BEGIN: %d", (int)my_copy.size());

            for (entity_set::iterator it = my_copy.begin();
                    it != my_copy.end(); ++it) {
                entity *e = static_cast<entity*>(*it);
                if (e->get_property_entity()) e = e->get_property_entity();
                tms_infof("Multideleting %p", e);
                tms_infof("%s", e->get_name());
                int r = this->delete_entity(e);

                if (r == 3) {
                    tms_debugf("we need to start iterating from the beginning!");
                    break;
                }
            }
        } while (!this->selection.m->empty());

        this->selection.disable();
    }
}

void
game::passthru_input(tms::event *ev)
{
    if (ev->type == TMS_EV_KEY_PRESS || ev->type == TMS_EV_KEY_UP) {
        for (std::set<key_listener*>::iterator it = W->key_listeners.begin();
                it != W->key_listeners.end(); ++it) {
            key_listener *kl = static_cast<key_listener*>(*it);

            if (kl->properties[0].v.i == ev->data.key.keycode) {
                kl->active = (ev->type == TMS_EV_KEY_PRESS);
            }
        }
    }

    for (std::set<escript*>::iterator it = W->escripts.begin();
            it != W->escripts.end(); ++it) {
        escript *e = static_cast<escript*>(*it);

        if (e->listen_on_input) {
            tms::event *new_ev = (tms::event*)malloc(sizeof(tms::event));
            memcpy(new_ev, ev, sizeof(tms::event));
            e->input_events.insert(new_ev);
        }
    }
}

void
game::perform_socket_action(int x)
{
    if (W->is_adventure() && W->is_playing()) {
        if (this->ss_socks[x]->p) {
            if (this->ss_action == SS_ACTION_SELECT) {
                this->selection.select(this->ss_socks[x]->p);
            } else {
                if (this->ss_socks[x]->p->c) {
                    this->absorb(this->ss_socks[x]->p->c);
                } else {
                    plug_base *p = this->ss_socks[x]->p;
                    this->ss_socks[x]->unplug();
                    this->absorb(p);
                }
            }
        }
    } else {
        if (this->ss_action == SS_ACTION_SELECT) {
            if (this->ss_socks[x]->p) {
                this->selection.select(this->ss_socks[x]->p);
            }
        } else {
            this->ss_socks[x]->unplug();

            this->add_ca(1, this->ss_edev->get_entity()->local_to_world(this->ss_socks[x]->lpos, 0));
        }
    }
}

void
game::play_sound(uint32_t sound_id, float x, float y, uint8_t random, float volume, bool loop/*=false*/, void *indent/*=0*/, bool global/*=false*/)
{
    sm_sound *snd = static_cast<sm_sound*>(soundman::translate(sound_id));
    if (!snd) return;

    typedef std::multimap<uint32_t, soundman*>::iterator iterator;
    std::pair<iterator, iterator> range = W->soundmanagers.equal_range(sound_id);

    iterator it = range.first;
    for (; it != range.second; ++it) {
        soundman *sm = it->second;
        if (!sm->is_busy()) {
            sm->enable();
            volume = sm->get_volume();
            break;
        }
    }

    if (volume <= SM_MIN_VOLUME && indent) {
        sm::stop(snd, indent);
    } else {
        sm::play(snd, x, y, random, volume, loop, indent, global);
    }
}

/**
 * Safe function to call from play-mode to queue up a level restart. (Same behaviour as P on PC)
 **/
void
game::restart_level()
{
    if (!this->_restart_level) {
        this->_restart_level = true;
        P.add_action(ACTION_WORLD_PAUSE, 0);
    }
}

/**
 * Safe function to call from play-mode to queue up a score submission.
 **/
void
game::submit_score()
{
    if (!this->state.submitted_score) {
        if (!this->_submit_score) {
            this->_submit_score = true;
            if (settings["score_ask_before_submitting"]->is_true()) {
                ui::confirm("Do you want to submit your highscore?",
                        "Yes",    principia_action(ACTION_SUBMIT_SCORE, 0),
                        "No",     principia_action(ACTION_IGNORE, 0)
                        );
            } else {
                P.add_action(ACTION_SUBMIT_SCORE, 0);
            }
        }
    }
}

void
game::destroy_possible_mover(entity *e)
{
    for (int x=0; x<MAX_INTERACTING; x++) {
        if (interacting[x] == e) {
            this->destroy_mover(x);
        }
    }
}

void
game::set_architect_mode(bool val)
{
    if (val) {
        if (!this->grident->scene) {
            this->get_scene()->add_entity(this->grident);
        }
    } else {
        if (this->grident->scene) {
            this->get_scene()->remove_entity(this->grident);
        }
    }

    this->state.abo_architect_mode = val;
}

static void
fix_entity(entity *e, uint32_t old_id, uint32_t new_id)
{
    for (uint8_t np = 0; np < e->num_properties; ++np) {
        property *p = &e->properties[np];

        if (p->type == P_ID && p->v.i == old_id) {
            p->v.i = new_id;
        }
    }
}

static void
update_entity_id_changed(uint32_t old_id, uint32_t new_id, std::map<uint32_t, entity*> *c1, std::map<uint32_t, entity*> *c2)
{
    if (old_id == new_id) return;

    if (c1) {
        for (std::map<uint32_t, entity*>::iterator it = c1->begin();
                it != c1->end(); ++it) {
            fix_entity(it->second, old_id, new_id);
        }
    }

    if (c2) {
        for (std::map<uint32_t, entity*>::iterator it = c2->begin();
                it != c2->end(); ++it) {
            fix_entity(it->second, old_id, new_id);
        }
    }
}

#ifdef DEBUG

void
game::clamp_entities()
{
    Uint32 ss = SDL_GetTicks();
    tms_debugf("Clamping all entities...");

    uint32_t id = 1;
    uint32_t prev_biggest = 0;

    uint32_t num_entities = 0;
    uint32_t num_cables = 0;
    uint32_t num_groups = 0;

    std::map<uint32_t, entity*> new_entities;

    for (std::map<uint32_t, entity*>::iterator it = W->all_entities.begin();
            it != W->all_entities.end();) {
        entity *e = it->second;
        uint32_t old_id = it->first;
        uint32_t new_id = id ++;

        if (old_id > prev_biggest) prev_biggest = old_id;

        if (W->is_adventure() && e->id == W->level.get_adventure_id() && e->is_creature()) {
            tms_debugf("IGNORE ADVENTURE ROBOT");
            // do not change id of adventure robot
            new_entities.insert(std::pair<uint32_t, entity*>(e->id, e));
            W->all_entities.erase(it++);

            continue;
        }

        e->id = new_id;

        new_entities.insert(std::pair<uint32_t, entity*>(new_id, e));
        W->all_entities.erase(it++);

        update_entity_id_changed(old_id, new_id, &W->all_entities, &new_entities);
        ++ num_entities;
    }

    W->all_entities = new_entities;

    for (std::set<cable*>::iterator it = W->cables.begin();
            it != W->cables.end(); ++it) {
        entity *e = (*it);
        uint32_t old_id = e->id;
        uint32_t new_id = id ++;

        if (old_id > prev_biggest) prev_biggest = old_id;
        e->id = new_id;

        ++ num_cables;
    }

    std::map<uint32_t, group*> new_groups;

    for (std::map<uint32_t, group*>::iterator it = W->groups.begin();
            it != W->groups.end();) {
        group *g = it->second;
        uint32_t old_id = it->first;
        uint32_t new_id = id ++;

        if (old_id > prev_biggest) prev_biggest = old_id;
        g->id = new_id;

        new_groups.insert(std::pair<uint32_t, group*>(new_id, g));
        W->groups.erase(it++);

        ++ num_groups;
    }

    W->groups = new_groups;

    tms_debugf("Done in %" PRIu64 " ticks", SDL_GetTicks()-ss);
    tms_debugf("Old biggest id: %u. New: %u", prev_biggest, id-1);
    tms_debugf("Entities: %u, Cables: %u, Groups: %u",
               num_entities, num_cables, num_groups);

    of::_id = id;
}

#endif

int
game::post_render()
{
    if (this->info_label && this->info_label->active) {
        this->info_label->render(this->get_surface()->ddraw, true);
    }

    pscreen::post_render();

    if (!settings["touch_controls"]->v.b && this->hov_text->active) {
        this->hov_text->render(this->get_surface()->ddraw, true);
    }

    return T_OK;
}

connection*
game::set_connection_strength(connection *c, float strength)
{
    tms_infof("Set connection strength: %.2f", strength);
    if (strength == 1.f) {
        c->max_force = INFINITY;
    } else {
        c->max_force = strength * CONN_MAX_FORCE;
    }

    tms_infof("p  0 %f %f %p %p", c->p.x, c->p.y, c->e->get_body(0), c->e->gr);
    tms_infof("p2 0 %f %f %p %p", c->p_s.x, c->p_s.y, c->o->get_body(0), c->o->gr);

    if (c->type == CONN_GROUP || c->type == CONN_PLATE) {
        /* readd the connection */
        connection copy = *c;
        copy.p = c->e->local_to_world(c->p, c->f[0]);
        tms_infof("destroy conn: %p", c);
        c->e->destroy_connection(c);

        if (copy.owned) {
            tms_infof("owned");
            *c = copy;
        } else {
            c = this->get_tmp_conn();
            *c = copy;
        }

        c->j = 0;
        c->self_ent = 0;

        if (strength == 1.f && c->type == CONN_PLATE) {
            c->type = CONN_GROUP;
            tms_infof("setting type to GROUP %p", c);
        } else if (strength < 1.f && c->type == CONN_GROUP) {
            tms_infof("setting type to PLATE %p", c);
            c->type = CONN_PLATE;
        }

        tms_infof("conn_ll: %p %p", c->e->conn_ll, c->o->conn_ll);

        c = this->apply_connection(c, -1);

        tms_infof("p  1 %f %f %p %p", c->p.x, c->p.y, c->e->get_body(0), c->e->gr);
        tms_infof("p2 1 %f %f %p %p", c->p_s.x, c->p_s.y, c->o->get_body(0), c->o->gr);
    }

    return c;
}

void
game::multiselect_perform(void (*cb)(entity*, void*), void *userdata)
{
    if (this->state.sandbox && W->is_paused() && !this->state.test_playing) {
        if (this->get_mode() == GAME_MODE_MULTISEL && this->selection.m) {
            // We need to copy the entity list, because we're likely going to modify it.
            std::set<entity*> cloned_ent(*this->selection.m);
            std::set<entity*>::iterator i = cloned_ent.begin();
            for (; i != cloned_ent.end(); i++) {
                cb(*i, userdata);
            }
            cloned_ent.clear();
        }
    }
}

bool
game::apply_multiselection(entity *e)
{
    bool include_custom_conns = true;
    bool include_static = false;

    entity_set *loop = new entity_set();
    if (this->multi.follow_connections) {
        if (this->sel_p_ent->g_id == O_PIXEL) {
            ((pixel*)this->sel_p_ent)->gather_connected_pixels(loop);
        } else {
            this->sel_p_ent->gather_connected_entities(
                    loop,
                    this->multi.follow_cables,
                    include_custom_conns,
                    include_static,
                    this->multi.select_through_layers);
        }
    } else {
        loop->insert(this->sel_p_ent);
    }

    if (loop->size()) {
        this->selection.select(loop);

        return true;
    } else {
        delete loop;

        return false;
    }
}

#ifdef DEBUG

void game::print_screen_point_info(int x, int y)
{
    tvec3 tproj[3];
    for (int l=0; l<3; l++) { W->get_layer_point(this->cam, x, y, l, &tproj[l]); }
    terrain_coord coord(tproj[0].x, tproj[0].y);

    level_chunk *c = W->cwindow->get_chunk(coord.chunk_x,coord.chunk_y);

    printf("--- CLICK AT %d %d ---\n", x, y);
    printf("pt layer 0: %f %f\n", tproj[0].x, tproj[0].y);
    printf("pt layer 1: %f %f\n", tproj[1].x, tproj[1].y);
    printf("pt layer 2: %f %f\n", tproj[2].x, tproj[2].y);
    printf("chunk x: %d\n", coord.chunk_x);
    printf("chunk y: %d\n", coord.chunk_y);
    printf("chunk in active?: %d\n", (W->cwindow->preloader.active_chunks.find(chunk_pos(coord.chunk_x, coord.chunk_y)) != W->cwindow->preloader.active_chunks.end()));
    printf("chunk num_fixtures: %d\n", c->num_fixtures);
    printf("chunk num_dyn_fixtures: %d\n", c->num_dyn_fixtures);
    printf("chunk load phase: %d\n", c->load_phase);
    printf("chunk generate phase: %d\n", c->generate_phase);
    printf("chunk in wastebin?: %d\n", (W->cwindow->preloader.wastebin.find(chunk_pos(coord.chunk_x, coord.chunk_y)) != W->cwindow->preloader.wastebin.end()));

    if (c) {
        printf("chunk num fixtures: %d\n", c->num_fixtures);
        printf("chunk garbage: %d\n", c->num_fixtures);

        for (int x=0; x<8; x++) {
            if (c->neighbours[x]) {
                tms_debugf("chunk neighbour %d num fixtures: %d", x, c->neighbours[x]->num_fixtures);
                tms_debugf("chunk neighbour %d garbage:      %d", x, c->neighbours[x]->garbage);
            }
        }
    }

}

void game::print_stats()
{
    printf("--- CHUNK PRELOADER ---\n");
    printf("heap size:\t\t%" PRIu64 "/%" PRIu64 "\n", W->cwindow->preloader.heap.size, W->cwindow->preloader.heap.cap);
    printf("level size:\t\t%" PRIu64 "/%" PRIu64 "\n", W->cwindow->preloader.w_lb.size, W->cwindow->preloader.w_lb.cap);
    printf("active chunks:\t\t%u\n", (int)W->cwindow->preloader.active_chunks.size());
    printf("wastebin:\t\t%u\n", (int)W->cwindow->preloader.wastebin.size());
    printf("gentypes:\t\t%u\n", (int)W->cwindow->preloader.gentypes.size());
    SDL_Delay(5000);
}

#endif

bool
game::autosave_exists()
{
    char autosave_path[1024];
    snprintf(autosave_path, 1023, "%s/.autosave", pkgman::get_level_path(LEVEL_LOCAL));

    return file_exists(autosave_path);
}

void
game::open_latest_state(bool require_equal_id, tms::screen *previous_screen/*=0*/)
{
    lvlfile *level = pkgman::get_levels(LEVEL_LOCAL_STATE);
    lvlfile *next;

    uint32_t latest_save_id = 0;
    uint32_t latest_level_id = 0;
    int latest_level_id_type = LEVEL_LOCAL;
    time_t latest_mtime = 0;

    while (level) {
        if (!require_equal_id || level->id == W->level.local_id) {
            if (level->mtime > latest_mtime) {
                latest_save_id = level->save_id;
                latest_level_id = level->id;
                latest_mtime = level->mtime;
                latest_level_id_type = level->id_type;
            }
        }

        next = level->next;
        delete level;
        level = next;
    }

    tms_infof("Latest save id: %d", latest_save_id);

    if (latest_save_id != 0) {
        this->open_state(latest_level_id_type, latest_level_id, latest_save_id);

        if (!require_equal_id) {
            this->state.test_playing = false;
            this->state.sandbox = false;
        }

        if (previous_screen) {
            G->screen_back = previous_screen;
        }

        this->resume_action = GAME_RESUME_OPEN;

        if (_tms.screen == &P.s_loading_screen->super) {
            P.s_loading_screen->set_next_screen(this);
        } else if (_tms.screen != &this->super){
            tms::set_screen(this);
        }
    } else {
        ui::message("Found no save we can continue from.");
    }
}

void
game::add_loot(entity *host, uint8_t resource_type, int num, float life/*=1.f*/)
{
    typedef std::multimap<entity*, struct loot>::iterator iterator;
    std::pair<iterator, iterator> ip = this->loots.equal_range(host);

    bool added = false;

    iterator it = ip.first;
    for (; it != ip.second; ++it) {
        struct loot &l = it->second;

        if (l.resource_type == resource_type) {
            l.num += num;
            l.life = life;
            added = true;
            break;
        }
    }

    if (!added) {
        this->loots.insert(std::pair<entity*, struct loot>(host, loot(resource_type, resource_data[resource_type].name, num, life)));
    }
}

void
game::close_tt(int what)
{
    for (int x=0; x<MAX_TUTORIAL_TEXTS; x++) {
        if (this->tt[x].what == what) {
            this->tt[x].life = 0.f;
        }
    }
}

void
game::add_tt(int what, entity *e, b2Vec2 pos, float life)
{
    if (!this->state.new_adventure) {
        return;
    }

    int x;
    for (x=0; x<MAX_TUTORIAL_TEXTS; x++) {
        if (this->tt[x].what == what) {
            break;
        }
    }

    if (x == MAX_TUTORIAL_TEXTS) {
        for (x=0; x<MAX_TUTORIAL_TEXTS; x++) {
            if (this->tt[x].life <= 0.f) {
                break;
            }
        }
    }

    if (x == MAX_TUTORIAL_TEXTS) {
        x = MAX_TUTORIAL_TEXTS-1;
    }

    this->tt[x].what = what;
    this->tt[x].e = e;
    this->tt[x].life = life;
    this->tt[x].pos = pos;
}

void
game::finished_tt(int what)
{
    if (!this->state.new_adventure) {
        return;
    }

    settings["tutorial"]->v.u32 |= what;
}

void
game::add_hp(entity *host, float percent, tvec3 &color/*=TV_HP_RED*/, float time/*=1.f*/, bool regen/*=false*/)
{
    int x;
    bool found = false;

    /* first look if it's already added */
    for (x=0; x<NUM_HP; x++) {
        if (hps[x].e == host) {
            found = true;
            break;
        }
    }

    if (!found) {
        for (x=0; x<NUM_HP; x++) {
            if (hps[x].time <= 0.f) {
                break;
            }
        }

    }

    x = x%NUM_HP;

    hps[x].time = time;
    hps[x].e = host;
    hps[x].color = color;
    hps[x].percent = percent;
    hps[x].regen = regen;
}

void
game::render_help_icon(const std::set<entity*> &set, float off_x, float off_y)
{
    struct tms_sprite *spr = gui_spritesheet::get_sprite(S_ROUNDED_HELP);

    const b2Vec2 player_pos = adventure::player->get_position();

    for (std::set<entity*>::const_iterator it = set.begin();
            it != set.end(); ++it) {
        entity *e = (*it);

        const b2Vec2 icon_pos   = e->local_to_world(b2Vec2(off_x, off_y), 0);

        const float dist        = b2Distance(player_pos, icon_pos);

        if (dist < RH_MIN_DIST || dist > RH_MAX_DIST) {
            continue;
        }

        float size = 0.005f * _tms.xppcm;
        float max_z = 60.f;
        if (!W->level.flag_active(LVL_DISABLE_ADVENTURE_MAX_ZOOM) && !W->is_paused() && W->is_adventure() && this->follow_object == adventure::player) {
            max_z = 20.f;
        }
        size *= tclampf((this->cam->_position.z*2.f) / max_z, 1.f, 1.5f);

        float alpha = 1.f;
        if (dist > RH_MAX_DIST_ALPHA) {
            alpha = 1.f-((dist-RH_MAX_DIST_ALPHA) / (RH_MAX_DIST-RH_MAX_DIST_ALPHA));
        }
        tms_ddraw_set_color(this->dd, 1.f, 1.f, 1.f, alpha);

        tms_ddraw_sprite_r(this->dd, spr,
                icon_pos.x,
                icon_pos.y,
                size,
                size,
                e->get_angle() + cos((double)_tms.last_time/100000.) * 8.f);
    }
}

bool
game::check_click_help_icon(const std::set<entity*> &set, float off_x, float off_y, b2Vec2 click_pos, struct principia_action click_action)
{
    const b2Vec2 player_pos = adventure::player->get_position();

    for (std::set<entity*>::const_iterator it = set.begin();
            it != set.end(); ++it) {
        entity *e = (*it);

        const b2Vec2 icon_pos = e->local_to_world(b2Vec2(off_x, off_y), 0);

        const float dist = b2Distance(player_pos, icon_pos);

        if (dist < RH_MIN_DIST || dist > RH_MAX_DIST) {
            continue;
        }

        const float click_dist = b2Distance(icon_pos, click_pos);

        float size = 0.005f * _tms.xppcm;
        float max_z = 60.f;
        if (!W->level.flag_active(LVL_DISABLE_ADVENTURE_MAX_ZOOM) && !W->is_paused() && W->is_adventure() && this->follow_object == adventure::player) {
            max_z = 20.f;
        }
        size *= tclampf((this->cam->_position.z*2.f) / max_z, 1.f, 1.5f);
        size *= 0.65f;

        tms_infof("Click dist: %.2f", click_dist);
        tms_infof("Size: %.2f", size);

        if (click_dist < size) {
            P.add_action(click_action.action_id, click_action.action_data);
            settings["tutorial"]->v.u32 |= TUTORIAL_REPAIR_STATION;
            return true;
        }
    }

    return false;
}
