view krapula.c @ 19:64017da3aa2f

Jee.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 29 Sep 2012 13:32:52 +0300
parents ccc5cdce91e3
children ea93b1d5c894
line wrap: on
line source

#include "dmlib.h"
#include "dmargs.h"
#include "dmvecmat.h"
#include "dmres.h"
#include "dmimage.h"

#include "jss.h"
#include "jssmod.h"
#include "jssmix.h"
#include "jssplr.h"

#include <math.h>

#define DM_COLORS (256)


typedef struct
{
    int x, y;
} DMCoords;


typedef struct
{
    int x, y;
    char *filename;
    SDL_Surface *img;
} DMCredits;


typedef struct
{
    int currFrame, endTime, startTime, totalFrameTime;
    BOOL pauseFlag, exitFlag;
    SDL_Surface *screen;
    SDL_Event event;
} DMEngineData;


typedef struct
{
    int currFrame, endTime, startTime;
} DMFrameData;




int optVFlags = SDL_SWSURFACE | SDL_HWPALETTE;
int optScrWidth = 640, optScrHeight = 480;
int optBenchmarkLen = 20;


DMOptArg optList[] = {
    { 0, '?', "help",       "Show this help", OPT_NONE },
    { 2, 'v', "verbose",    "Be more verbose", OPT_NONE },
    { 3, 'f', "fs",         "Fullscreen", OPT_NONE },
//    { 5, 's', "size",       "Screen resolution -s 640x480", OPT_ARGREQ },
};

const int optListN = sizeof(optList) / sizeof(optList[0]);



void argShowHelp()
{
    dmPrintBanner(stdout, dmProgName, "[options]");
    dmArgsPrintHelp(stdout, optList, optListN);
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    switch (optN) {
    case 0:
        argShowHelp();
        exit(0);
        break;

    case 2:
        dmVerbosity++;
        break;
    
    case 3:
        optVFlags |= SDL_FULLSCREEN;
        break;

    case 5:
        {
            int w, h;
            if (sscanf(optArg, "%dx%d", &w, &h) == 2)
            {
                if (w < 320 || h < 200 || w > 1024 || h > 768)
                {
                    dmError("Invalid width or height: %d x %d\n", w, h);
                    return FALSE;
                }
                optScrWidth = w;
                optScrHeight = h;
            }
            else 
            {
                dmError("Invalid size argument '%s'.\n", optArg);
                return FALSE;
            }
        }
        break;

    default:
        dmError("Unknown option '%s'.\n", currArg);
        return FALSE;
    }
    
    return TRUE;
}


void dmMakePalette(SDL_Surface *scr)
{
    SDL_Color pal[DM_COLORS];
    int n;

    for (n = 0; n < 256; n++)
    {
        pal[n].r = n;
        pal[n].g = n;
        pal[n].b = n;
    }

    SDL_SetColors(scr, pal, 0, DM_COLORS);
}


void dmRandom(SDL_Surface *screen, int q)
{
    Uint8 *pix = screen->pixels;
    int xc, yc;
    
    for (yc = 0; yc < screen->h; yc++)
    {
        Uint8 *dp = pix;

        for (xc = 0; xc < screen->w; xc++)
            *dp++ = yc + (xc ^ q) + (yc & q);

        pix += screen->pitch;
    }
}


void dmPerlin(SDL_Surface *screen, float f)
{
    Uint8 *pix = screen->pixels;
    int xc, yc;

    for (yc = 0; yc < screen->h; yc++)
    {
        Uint8 *dp = pix;

        for (xc = 0; xc < screen->w; xc++)
        {
            *dp++ = 128 + dmPerlinNoise2D(xc, yc, 0.01, 0.1, 3) / 34.0;
        }

        pix += screen->pitch;
    }
}


#define QWIDTH	256
#define QHEIGHT	160

typedef Uint8 DMBlockMap[QHEIGHT][QWIDTH];


static DMFloat dmClip(DMFloat a)
{
    return (a < 0.0f ? 0.0f : (a > 1.0f ? 1.0f : a));
}


void dmMakeBumpMap(DMBlockMap map, DMFloat q, DMFloat m)
{
    int x, y;
    for (y = 0; y < QHEIGHT; y++)
        for (x = 0; x < QWIDTH; x++)
        {
            DMFloat f = 0.40f + dmPerlinNoise2D(x, y, 1.1f, q, 2);
            map[y][x] = (int) (dmClip(f) * m);
        }
}


void dmShadowTraceHeightMap(DMBlockMap lightMap, DMBlockMap pheightMap, DMVector *light)
{
    int i, j;

    for (j = 0; j < QHEIGHT; j++)
        for (i = 0; i < QWIDTH; i++)
        {
            DMVector vr, vl, va;
            DMFloat vrayLen, vfactor;
            int vlen;
            BOOL wasHit;

            /* Perform shadow occlusion via simplistic raytracing */
            vr.x = i;
            vr.y = j;
            vr.z = light->z; // - 10.0;
//            vr.z = pheightMap[j][i];
            
            /* Calculate light vector vector */
            dm_vector_sub_r(&vl, &vr, light);
            vrayLen = dm_vector_length(&vl);
            
#if 1
            dm_vector_copy(&va, &vl);
            dm_vector_normalize(&va);
            dm_vector_scale(&va, 0.6f);
            dm_vector_copy(&vr, light);

            vlen = 0;
            wasHit = FALSE;
            do
            {
                float h;
                
                /* If ray is inside the heightmap, get value */
                if (vr.x >= 0 && vr.y >= 0 && vr.x < QWIDTH && vr.y < QHEIGHT)
                    h = pheightMap[(int) vr.y][(int) vr.x];
                else
                    break;
                
                /* Check for hits */
                if (h > vr.z)
                    wasHit = TRUE;
                else
                {
                    /* Move forwards */
                    dm_vector_add(&vr, &va);
                    vlen++;
                }
            }
            while (!wasHit && vlen <= vrayLen);

            /* Check if the ray hit something, e.g. is this point occluded? */
            if (wasHit && vlen < vrayLen)
            {
                vfactor = vlen * 0.01;
            }
            else
                vfactor = vlen * 0.02;
#endif

#if 1
            {
                /* Calculate light's intensity based on the angle it "hits"
                 *
                 * 1) Calculate the vectors that form the imaginary "plane"
                 * 2) Cross-product -> normal vector of the plane
                 * 2) Normalize the normal vector
                 * 3) Calculate light vector's hit angle by dot product
                 */
                DMVector v1, v2;
                DMFloat c;

                v1.x = 2.0f;
                v1.y = 0.0f;
                v1.z = (DMFloat) (pheightMap[j][i] - pheightMap[j][i + 1]);

                v2.x = 0.0f;
                v2.y = 2.0f;
                v2.z = (DMFloat) (pheightMap[j][i] - pheightMap[j + 1][i]);

                dm_vector_cross(&vr, &v1, &v2);
                dm_vector_normalize(&vr);
                dm_vector_normalize(&vl);
                c = dm_vector_dot(&vl, &vr);

		vrayLen = 255 - (vrayLen * 0.1) * vrayLen + (c * 128.0f) + (vfactor * vfactor * 1255);
            }
#else
            vrayLen = 255 - vrayLen * vrayLen * (vfactor * vfactor);
            if (vrayLen < 0) vrayLen = 0;
            vrayLen += pheightMap[j][i];
#endif

            /* Clip result */
            if (vrayLen < 0)
                vrayLen = 0;
            else if (vrayLen > 255.0f)
                vrayLen = 255.0f;

            lightMap[j][i] = vrayLen;
        }
}


void dmShadowTraceHeightMap2(DMBlockMap lightMap, DMBlockMap pheightMap, DMVector *light)
{
    int i, j;

    light->z = 150;

    for (j = 0; j < QHEIGHT; j++)
        for (i = 0; i < QWIDTH; i++)
        {
            DMVector vr, vl, va;
            DMFloat vrayLen, vfactor;
            int vlen;
            BOOL wasHit;

            /* Perform shadow occlusion via simplistic raytracing */
            vr.x = i;
            vr.y = j;
            vr.z = 200; //light->z; // - 10.0;
            
            /* Calculate light vector vector */
            dm_vector_sub_r(&vl, &vr, light);
            vrayLen = dm_vector_length(&vl);
            
#if 1
            dm_vector_copy(&va, &vl);
            dm_vector_normalize(&va);
            dm_vector_copy(&vr, light);

            vlen = 0;
            wasHit = FALSE;
            do
            {
                float h;
                
                /* If ray is inside the heightmap, get value */
                if (vr.x >= 0 && vr.y >= 0 && vr.x < QWIDTH && vr.y < QHEIGHT)
                    h = pheightMap[(int) vr.y][(int) vr.x];
                else
                    break;
                
                /* Check for hits */
                if (h > vr.z)
                    wasHit = TRUE;
                else
                {
                    /* Move forwards */
                    dm_vector_add(&vr, &va);
                    vlen++;
                }
            }
            while (!wasHit && vlen <= vrayLen);

            /* Check if the ray hit something, e.g. is this point occluded? */
            if (wasHit && vlen < vrayLen)
            {
                vfactor = vlen * 0.05;
            }
            else
                vfactor = vlen * 0.001;
#endif

#if 0
            {
                /* Calculate light's intensity based on the angle it "hits"
                 *
                 * 1) Calculate the vectors that form the imaginary "plane"
                 * 2) Cross-product -> normal vector of the plane
                 * 2) Normalize the normal vector
                 * 3) Calculate light vector's hit angle by dot product
                 */
                DMVector v1, v2;
                DMFloat c;

                v1.x = 2.0f;
                v1.y = 0.0f;
                v1.z = (DMFloat) (pheightMap[j][i] - pheightMap[j][i + 1]);

                v2.x = 0.0f;
                v2.y = 2.0f;
                v2.z = (DMFloat) (pheightMap[j][i] - pheightMap[j + 1][i]);

                dm_vector_cross(&vr, &v1, &v2);
                dm_vector_normalize(&vr);
                dm_vector_normalize(&vl);
                c = dm_vector_dot(&vl, &vr);

		vrayLen = 255 - (vrayLen * 0.1) * vrayLen + (c * 128.0f) + (vfactor * vfactor * 1255);
            }
#else
            vrayLen = 255 - vrayLen * vrayLen * (vfactor * vfactor);
            if (vrayLen < 0) vrayLen = 0;
            vrayLen -= pheightMap[j][i];
#endif

            /* Clip result */
            if (vrayLen < 0)
                vrayLen = 0;
            else if (vrayLen > 255.0f)
                vrayLen = 255.0f;

            lightMap[j][i] = vrayLen;
        }
}


void engineAudioCallback(void *userdata, Uint8 *stream, int len)
{
    JSSMixer *d = (JSSMixer *) userdata;

    if (d != NULL)
    {
        jvmRenderAudio(d, stream, len / jvmGetSampleSize(d));
    }
}

#define DEBUG 0

#define CREDITS_SPEED 1000
#define CREDITS_RAND  4

#define NOSFE_MIN     1
#define NOSFE_MAX     269

static const DMCoords randomCoords[] =
{
    { -300, -430 },
    { 700, -550 },
    { -200, 600 },
    { 700, 600 }
};
const int nrandomCoords = sizeof(randomCoords) / sizeof(randomCoords[0]);


static DMCredits credits[] =
{
    {   91,  223, "g4014.png", NULL },
    {  151,  250, "g4026.png", NULL },
    {  217,  227, "g4020.png", NULL },
    {  173,  268, "g4032.png", NULL },
    {  115,  359, "g4038.png", NULL },

    {  437,  130, "g4062.png", NULL },
    {  457,  102, "g4068.png", NULL },
    {  450,  210, "g4056.png", NULL },

    {  420,  320, "g4044.png", NULL },
    {  486,  381, "g4050.png", NULL },
};

const int ncredits = sizeof(credits) / sizeof(credits[0]);




DMEngineData engine;
DMFrameData frame;

int engineGetTick()
{
    return (frame.startTime - engine.startTime) + DEBUG * 1000;
}

float engineGetTimeDT()
{
    return (float) engineGetTick() / 1000.0f;
}


int engineGetTimeDTi()
{
    return (float) engineGetTick() / 1000;
}


int engineGetTime(int t)
{
    return engineGetTick() - (1000 * t);;
}


int engineGetDT(int t)
{
    return engineGetTime(t) / 1000;
}


int dmScaledBlitSurface32to32TransparentX(SDL_Surface *src, const int x0, const int y0, const int dwidth, const int dheight, SDL_Surface *dst);


int engineResImageLoad(DMResource *res)
{
    SDL_Surface *img = dmLoadImage(res);
    if (res != NULL)
    {
        res->rdata = img;
        return DMERR_OK;
    }
    else
        return dmferror(res);
}

void engineResImageFree(DMResource *res)
{
    SDL_FreeSurface((SDL_Surface *)res->rdata);
}

int engineResModuleLoad(DMResource *res)
{
    return jssLoadXM(res, (JSSModule **) &(res->rdata));
}

void engineResModuleFree(DMResource *res)
{
    jssFreeModule((JSSModule *) res->rdata);
}


static DMResourceDataOps engineResImage =
{
    engineResImageLoad,
    engineResImageFree
};

static DMResourceDataOps engineResModule =
{
    engineResModuleLoad,
    engineResModuleFree
};


int engineClassifier(DMResource *res)
{
    DMResourceDataOps *rops = NULL;
    char *fext;

    if (res == NULL)
        return DMERR_NULLPTR;
    
    if ((fext = strrchr(res->filename, '.')) != NULL)
    {
        if (strcasecmp(fext, ".png") == 0 || strcasecmp(fext, ".jpg") == 0)
            rops = &engineResImage;
        else
        if (strcasecmp(fext, ".xm") == 0 || strcasecmp(fext, ".jmod") == 0)
            rops = &engineResModule;
    }
    
    res->rops = rops;
    
    return DMERR_OK;
}


void *engineGetResource(const char *name)
{
    DMResource *res = dmres_find(name);
    if (res != NULL && res->rdata != NULL)
        return res->rdata;
    else
    {
        dmError("Could not find resource '%s'.\n", name);
        return NULL;
    }
}


#define engineGetResImage(name) (SDL_Surface *) engineGetResource(name)
#define engineGetResModule(name) (JSSModule *) engineGetResource(name)


int engineLoadResources()
{
    int err, loaded, total;
    
    err = dmres_preload(TRUE, &loaded, &total);

    while ((err = dmres_preload(FALSE, &loaded, &total)) == DMERR_PROGRESS)
    {
        // Show a nice progress bar while loading
        if (total > 0 && (loaded % 2) == 0)
        {
            int dx = 60,
                dh = 20,
                dw = engine.screen->w - (2 * dx),
                dy = (engine.screen->h - dh) / 2;
            
            if (SDL_MUSTLOCK(engine.screen) != 0 && SDL_LockSurface(engine.screen) != 0)
                return DMERR_INIT_FAIL;
            
            // Draw the progress bar
            dmClearSurface(engine.screen, dmMapRGBA(engine.screen, 0,0,0,0));
            dmFillRect(engine.screen, dx, dy, dx+dw, dy+dh, dmMapRGB(engine.screen, 255,255,255));
            dmFillRect(engine.screen, dx+1, dy+1, dx+dw-1, dy+dh-1, dmMapRGB(engine.screen, 0,0,0));

            if (total > 0)
            {
                dmFillRect(engine.screen,
                    dx+3, dy+3,
                    dx + 3 + ((dw - 3) * loaded) / total,
                    dy + dh - 3,
                    dmMapRGB(engine.screen, 200,200,200));
            }

            // Flip screen
            if (SDL_MUSTLOCK(engine.screen) != 0)
                SDL_UnlockSurface(engine.screen);

            SDL_Flip(engine.screen);
        }
    }
    
    return err;
}



int main(int argc, char *argv[])
{
    BOOL initSDL = FALSE;
    JSSModule *mod = NULL;
    JSSMixer *dev = NULL;
    JSSPlayer *plr = NULL;
    int err, i;
    SDL_AudioSpec afmt;

    memset(&afmt, 0, sizeof(afmt));
    memset(&frame, 0, sizeof(frame));
    memset(&engine, 0, sizeof(engine));

    dmInitProg("krapula", "Lauantai Aamun Krapula", "0.2", "(c) 2012 Anciat Prodz & TNSP", "PENIS.");
    if (!dmArgsProcess(argc, argv, optList, optListN,
        argHandleOpt, NULL, FALSE))
        exit(1);

    dmPrint(0, "%s\n", dmProgDesc);
    dmPrint(0, "%s\n", dmProgAuthor);
    dmPrint(0, "TNSP PIERUPASKA engine 2012 'passeli professional' loading.\n");

    // Initialize resource subsystem
    dmPrint(1, "Initializing resources subsystem.\n");
    if ((err = dmres_init("orvellys.dat", NULL, DRF_USE_PACK | DRF_PRELOAD_RES, engineClassifier)) != DMERR_OK)
    {
        dmError("Could not initialize resource manager: %d, %s.\n", err, dmErrorStr(err));
        goto error_exit;
    }


    // Initialize SDL components
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0)
    {
        dmError("Could not initialize SDL: %s\n", SDL_GetError());
        goto error_exit;
    }
    initSDL = TRUE;


    // Initialize JSS
    jssInit();

    afmt.freq     = 44100;
    afmt.format   = AUDIO_S16SYS;
    afmt.channels = 2;
    afmt.samples  = 16*1024;

    dmPrint(1, "Initializing miniJSS mixer with: %d, %d, %d\n",
        JSS_AUDIO_S16, afmt.channels, afmt.freq);

    if ((dev = jvmInit(JSS_AUDIO_S16, afmt.channels, afmt.freq, JMIX_AUTO)) == NULL)
    {
        dmError("jvmInit() returned NULL, voi perkele.\n");
        goto error_exit;
    }

    if ((plr = jmpInit(dev)) == NULL)
    {
        dmError("jmpInit() returned NULL\n");
        goto error_exit;
    }

    // Initialize SDL audio
    dmPrint(1, "Trying to init SDL audio with: %d, %d, %d\n",
        afmt.format, afmt.channels, afmt.freq);

    afmt.callback = engineAudioCallback;
    afmt.userdata = (void *) dev;
    
    if (SDL_OpenAudio(&afmt, NULL) < 0)
    {
        dmError("Couldn't open audio: %s\n", SDL_GetError());
        goto error_exit;
    }

    // Initialize SDL video
    dmPrint(1, "Initializing SDL video %d x %d x %dbpp - %08x flags\n",
        optScrWidth, optScrHeight, 32, optVFlags);

    engine.screen = SDL_SetVideoMode(optScrWidth, optScrHeight, 32, optVFlags);
    if (engine.screen == NULL)
    {
        dmError("Can't SDL_SetVideoMode(): %s\n", SDL_GetError());
        goto error_exit;
    }

    SDL_ShowCursor(SDL_DISABLE);
    SDL_WM_SetCaption(dmProgDesc, dmProgName);


    // Load resources
    err = engineLoadResources();
    if (err != DMERR_OK)
    {
        dmError("Error loading resources, %d: %s.\n",
            err, dmErrorStr(err));
        goto error_exit;
    }

    // Initialize effect stuff
    dmPerlinInit();
    SDL_Surface *nosfe[NOSFE_MAX - NOSFE_MIN + 1];
    for (i = 0; i < NOSFE_MAX; i++)
    {
        char fname[64];
        snprintf(fname, sizeof(fname), "%08d.jpg", NOSFE_MIN + i);
        nosfe[i] = engineGetResImage(fname);
    }

    for (i = 0; i < ncredits; i++)
        credits[i].img = engineGetResImage(credits[i].filename);

    SDL_Surface *bmap = SDL_CreateRGBSurface(SDL_SWSURFACE, QWIDTH, QHEIGHT, 8, 0, 0, 0, 0);


    // Initialize music player
    jvmSetCallback(dev, jmpExec, plr);
    jmpSetModule(plr, engineGetResModule("krapula.xm"));
    jmpPlayOrder(plr, 0);
    jvmSetGlobalVol(dev, 55);
    SDL_PauseAudio(0);


    engine.startTime = SDL_GetTicks();

    while (!engine.exitFlag)
    {
        while (SDL_PollEvent(&engine.event))
        switch (engine.event.type)
        {
            case SDL_KEYDOWN:
                switch (engine.event.key.keysym.sym)
                {
                    case SDLK_ESCAPE:
                        engine.exitFlag = TRUE;
                        break;
                    
                    case SDLK_SPACE:
                        engine.pauseFlag = !engine.pauseFlag;
                        break;

                    default:
                        break;
                }

                break;

            case SDL_VIDEOEXPOSE:
                break;

            case SDL_QUIT:
                engine.exitFlag = TRUE;
                break;
        }

        // Draw frame
        frame.startTime = SDL_GetTicks();

        if (SDL_MUSTLOCK(engine.screen) != 0 && SDL_LockSurface(engine.screen) != 0)
        {
            dmError("Can't lock surface.\n");
            goto error_exit;
        }

        float t = engineGetTimeDT();

        if (t < 5)
        {
            SDL_Surface *anciat;
            int dt = engineGetTime(0);
            static DMLerpContext lerpX, lerpY, lerpD;
            static DMScaledBlitFunc nblit;
            DMVector light;
            static BOOL nollattu = FALSE;
            if (!nollattu)
            {
                anciat = engineGetResImage("anciat.png");
                nblit = dmGetScaledBlitFunc(bmap->format, engine.screen->format, DMD_NONE);
                dmMakePalette(bmap);
                dmLerpInit(&lerpX, 0, QWIDTH, 5000);
                dmLerpInit(&lerpY, QHEIGHT * 0.25, QHEIGHT * 0.75, 5000);
                dmLerpInit(&lerpD, 0.04, 0.08, 5000);
                nollattu = TRUE;
            }

            light.x = dmLerpSCurve(&lerpX, dt);
            light.y = dmLerp1(&lerpY, dt);
            light.z = 128;

            dmShadowTraceHeightMap2(bmap->pixels, anciat->pixels, &light);
            nblit(bmap, 0, 0, engine.screen->w, engine.screen->h, engine.screen);
        }
        else
        if (t < 10)
        {
            SDL_Surface *logobg, *logolayer1, *logolayer2;
            int dt = engineGetTime(5);
            static DMScaledBlitFunc nblit, kblit;
            static DMLerpContext lerpD;
            static BOOL nollattu = FALSE;

            if (!nollattu)
            {
                logobg = engineGetResImage("logobg.png");
                logolayer1 = engineGetResImage("logolayer1.png");
                logolayer2 = engineGetResImage("logolayer2.png");

                nblit = dmGetScaledBlitFunc(logobg->format, engine.screen->format, DMD_TRANSPARENT);
                kblit = dmGetScaledBlitFunc(logobg->format, engine.screen->format, DMD_NONE);
                dmLerpInit(&lerpD, 0.01, 500, 10000);
                nollattu = TRUE;
            }

            float q = dmLerpSCurve(&lerpD, dt);
            float t = sin((float) dt / 150.0f);
            int x = t * 25.0f + q, y = t * 35.0f + q*2.0f,
                w = t * 70.0f + q, h = t * 40.0f + q*2.0f;

            float t2 = sin((float) dt / 150.0f + 0.2f);
            int x2 = t2 * 25.0f + q, y2 = t * 35.0f + q*2.0f,
                w2 = t2 * 70.0f + q, h2 = t * 40.0f + q*2.0f;

            kblit(logobg, 0, 0, engine.screen->w, engine.screen->h, engine.screen);
            nblit(logolayer1, -x, -y, engine.screen->w+w, engine.screen->h+h, engine.screen);
            nblit(logolayer2, -x2, -y2, engine.screen->w+w2, engine.screen->h+h2, engine.screen);
        }
        else
        if (t < 20)
        {
            SDL_Surface *gay, *logobg;
            int dt = engineGetTime(10);
            static DMLerpContext lerpX, lerpY, lerpD;
            static DMScaledBlitFunc nblit, kblit;
            static BOOL nollattu = FALSE;
            DMVector light;
            DMBlockMap heightMap;

            if (!nollattu)
            {
                gay = engineGetResImage("gay.png");
                logobg = engineGetResImage("logobg.png");
                nblit = dmGetScaledBlitFunc(bmap->format, engine.screen->format, DMD_NONE);
                kblit = dmGetScaledBlitFunc(logobg->format, engine.screen->format, DMD_TRANSPARENT);
                dmMakePalette(bmap);
                dmLerpInit(&lerpX, QWIDTH, 0, 10000);
                dmLerpInit(&lerpY, QHEIGHT * 0.25, QHEIGHT * 0.75, 10000);
                dmLerpInit(&lerpD, 0.04, 0.08, 10000);
                nollattu = TRUE;
            }

            light.x = dmLerpSCurve(&lerpX, dt);
            light.y = QHEIGHT * 0.5 + sin(dmLerp1(&lerpY, dt)) * 0.5;
            light.z = 128;

            dmMakeBumpMap(heightMap, dmLerpSCurve(&lerpD, dt), 254);

            dmShadowTraceHeightMap(bmap->pixels, heightMap, &light);

            nblit(bmap, 0, 0, engine.screen->w, engine.screen->h, engine.screen);

            if ((dt / 100) % 10 < 5)
            {
                kblit(gay, 0, 0, engine.screen->w, engine.screen->h, engine.screen);
            }
        }
        else
        if (t < 45)
        {
            static SDL_Surface *ruutu;
            static int currState, currCredit, creditStartTime;
            static DMLerpContext lerpX, lerpY, lerpZ;
            static DMScaledBlitFunc nblit, kblit;
            static BOOL stateChange, nollattu = FALSE;
            int currFrame = engineGetTime(20) * 15 / 1000;
            if (!nollattu)
            {
                ruutu = engineGetResImage("ruutu.png");
                dmClearSurface(ruutu, dmMapRGBA(ruutu, 0,0,0,0));
                nblit = dmGetScaledBlitFunc(nosfe[0]->format, engine.screen->format, DMD_NONE);
                kblit = dmGetScaledBlitFunc(credits[0].img->format, engine.screen->format, DMD_TRANSPARENT);
                currCredit = -1;
                currState = -1;
                stateChange = TRUE;
                nollattu = TRUE;
            }

            float gt = 1.0f + sin(engineGetTime(0) / 250.0f);
            int g1 = gt * 25.0f, g2 = gt * 50.0f;
            
            nblit(nosfe[currFrame % NOSFE_MAX], -g1, -g1, engine.screen->w+g2, engine.screen->h+g2, engine.screen);

            if (t >= 30)
            {
                int qtime = engineGetTime(30);
                int creditTime = (engineGetTime(0) - creditStartTime);
                float zscale;
                if ( ( (qtime / (CREDITS_SPEED + 500)) % 2) == 0 && currState == -1)
                    stateChange = TRUE;

                if (stateChange && currCredit < ncredits)
                {
//                    fprintf(stderr, "[%6d] stateChange: st=%d, credit=%d\n", creditTime, currState, currCredit);
                    stateChange = FALSE;
                    switch (currState)
                    {
                        case 0:
                            {
                            int qt = (qtime / 100) % nrandomCoords;
                            creditStartTime = engineGetTime(0);
                            creditTime = 0;
                            dmLerpInit(&lerpX, randomCoords[qt].x, credits[currCredit].x - 50, CREDITS_SPEED);
                            dmLerpInit(&lerpY, randomCoords[qt].y, credits[currCredit].y - 50, CREDITS_SPEED);
                            dmLerpInit(&lerpZ, 5.0f, 0.0f, CREDITS_SPEED);
                            currState = 1;
                            }
                            break;

                        case 2:
                            if (creditTime >= CREDITS_SPEED)
                                creditTime = CREDITS_SPEED - 1;

                            zscale = dmLerpSCurve(&lerpZ, creditTime);
                            dmScaledBlitSurface32to32TransparentX(
                                credits[currCredit].img,
                                dmLerpSCurve(&lerpX, creditTime) - (zscale * credits[currCredit].img->w),
                                dmLerpSCurve(&lerpY, creditTime) - (zscale * credits[currCredit].img->h),
                                credits[currCredit].img->w * (1.0f + zscale),
                                credits[currCredit].img->h * (1.0f + zscale),
                                ruutu);

                            currState = -1;
                            break;
                        
                        default:
                            currCredit++;
                            currState = 0;
                            stateChange = TRUE;
                            break;
                    }

//                    fprintf(stderr, "[%6d] changed: st=%d, credit=%d, chg=%d\n", creditTime, currState, currCredit, stateChange);
                }
                

                if (currCredit > 0)
                {
                    kblit(ruutu, 0, 0, engine.screen->w, engine.screen->h, engine.screen);
                }

                if (currState == 1)
                {
                    if (creditTime >= CREDITS_SPEED)
                    {
                        creditTime = CREDITS_SPEED;
                        stateChange = TRUE;
                        currState = 2;
                    }

                    zscale = dmLerpSCurve(&lerpZ, creditTime);
                    kblit(credits[currCredit].img,
                        dmLerpSCurve(&lerpX, creditTime) - (zscale * credits[currCredit].img->w),
                        dmLerpSCurve(&lerpY, creditTime) - (zscale * credits[currCredit].img->h),
                        credits[currCredit].img->w * (1.0f + zscale),
                        credits[currCredit].img->h * (1.0f + zscale),
                        engine.screen);
                }
            }
            
        }
        else
        if (t < 60)
        {
            SDL_Surface *logobg, *greets;
            int dt = engineGetTime(45);
            static DMScaledBlitFunc nblit, kblit;
            static DMLerpContext lerpD;
            static BOOL nollattu = FALSE;

            if (!nollattu)
            {
                logobg = engineGetResImage("logobg.png");
                greets = engineGetResImage("greetings.png");
                nblit = dmGetScaledBlitFunc(logobg->format, engine.screen->format, DMD_TRANSPARENT);
                kblit = dmGetScaledBlitFunc(logobg->format, engine.screen->format, DMD_NONE);
                dmLerpInit(&lerpD, 0.01, 500, 10000);
                nollattu = TRUE;
            }

            float q = dmLerpSCurve(&lerpD, dt);
            float t = sin((float) dt / 150.0f),
                  j = (1.0 + t) * 15;
            int x = t * 25.0f + q, y = t * 35.0f + q,
                w = t * 70.0f + q*2.0f, h = t * 40.0f + q*2.0f;

            kblit(logobg, -j, -j, engine.screen->w+j*2.0f, engine.screen->h+j*2.0f, engine.screen);
            nblit(greets, -x, -y, engine.screen->w+w, engine.screen->h+h, engine.screen);
        }
        else
            engine.exitFlag = TRUE;

        {
            static SDL_Surface *feidi;
            static int fadeStartTime;
            static BOOL fadeActive, nollattu = FALSE;
            static DMLerpContext fadeLerp;
            BOOL hit;
            int ch;

            if (!nollattu)
            {
                feidi = engineGetResImage("feidi.png");
                dmLerpInit(&fadeLerp, 255, 0, 250);
                nollattu = TRUE;
            }

            JSS_LOCK(plr);
            for (hit = FALSE, ch = 0; ch < 6; ch++)
            if (plr->iCExtInstrumentN[ch] == 0)
            {
                hit = TRUE;
                break;
            }
            JSS_UNLOCK(plr);

            if (hit && !fadeActive)
            {
                fadeActive = TRUE;
                fadeStartTime = engineGetTime(0);
            }
            if (fadeActive)
            {
                int fadeTime = engineGetTime(0) - fadeStartTime;
                if (fadeTime < 250)
                {
                    dmScaledBlitSurface32to32TransparentGA(feidi,
                        0, 0, engine.screen->w, engine.screen->h, engine.screen,
                        dmLerpSCurve(&fadeLerp, fadeTime));
                }
                else
                    fadeActive = FALSE;
            }
        }

        // Flip screen
        if (SDL_MUSTLOCK(engine.screen) != 0)
            SDL_UnlockSurface(engine.screen);

        SDL_Flip(engine.screen);
        SDL_Delay(20);

        // Get frame time, etc
        frame.endTime = SDL_GetTicks();
        engine.currFrame++;
        engine.totalFrameTime += frame.endTime - frame.startTime;
    }

    // Print benchmark results
    engine.endTime = SDL_GetTicks();
    dmPrint(1, "%d frames in %d ms, fps = %1.3f\n",
        engine.currFrame, engine.endTime - engine.startTime,
        (float) (engine.currFrame * 1000.0f) / (float) engine.totalFrameTime);


error_exit:
    dmPrint(1, "Shutting down.\n");
    SDL_ShowCursor(SDL_ENABLE);
    SDL_PauseAudio(1);

    if (engine.screen)
        SDL_FreeSurface(engine.screen);

    SDL_LockAudio();
    jmpClose(plr);
    jvmClose(dev);
    jssFreeModule(mod);
    jssClose();
    SDL_UnlockAudio();

    dmres_close();    

    if (initSDL)
        SDL_Quit();

    dmPrint(0, "SDL on muuten melko paska kirjasto.\n");
    
    return 0;
}