If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle

See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// sprite.c -- 2D sprite library

#include "library.h"
#include "hal.h"

sprite_t *sprites = 0;

int spr_cachecycle = 0;

/*
==============
SPR_Draw

Draw the given sprite
==============
*/
void SPR_Draw(sprite_t *spr, float scrx, float scry, int animnum, float curtime, int alpha)
{
    spr_animation_t *anim;
    spr_frame_t *frame;
    int num;

    if (!spr)
        throw LError("SPR_Draw: NULL spr");

    if (animnum < 0)
        anim = &spr->base;
    else {
        for(num = 0, anim = spr->anims; num < spr->numanims; num++, anim++) {
            if (anim->id == animnum)
                break;
        }
        if (num >= spr->numanims)
            return;
    }

    num = (int)(curtime * anim->speed / 1000.0) % anim->numframes;
    frame = &anim->frames[num];

//  if (anim->id > 0)
//      L_Printf("draw: %i\n", anim->id);

    if (frame->pic)
        frame->pic->Draw(scrx + frame->offsets[0], scry + frame->offsets[1],
            frame->r, frame->g, frame->b, (frame->a*alpha) / 256);
}

/*
==============
SPR_GetAttach

Get the attachment of the given ID
==============
*/
spr_attach_t *SPR_GetAttach(sprite_t *spr, int id)
{
    static spr_attach_t null_attach = { 0, { 0, 0 } };
    int i;

    for(i = 0; i < spr->numattachs; i++) {
        if (spr->attachs[i].id == id)
            return &spr->attachs[i];
    }

    return &null_attach;
}

/*
==============
FreeAnimation

Free all frames associated with the given animation
==============
*/
static void FreeAnimation(spr_animation_t *anim)
{
    int i;

    if (anim->numframes) {
        for(i = 0; i < anim->numframes; i++)
            if (anim->frames[i].pic)
                anim->frames[i].pic->Release();
        L_Free(anim->frames);
    }
}

/*
==============
SPR_Free

Free the given sprite
==============
*/
void SPR_Free(sprite_t *spr)
{

    spr->used--;
}

/*
==============
SPR_DoFree

Actually free a sprite
==============
*/
void SPR_DoFree(sprite_t *spr)
{
    int i;

//  L_Printf("SPR_DoFree: %s\n", spr->name);

    if (spr->next)
        spr->next->pprev = spr->pprev;
    if (spr->pprev)
        *spr->pprev = spr->next;

    FreeAnimation(&spr->base);
    for(i = 0; i < spr->numanims; i++)
        FreeAnimation(&spr->anims[i]);

    L_Free(spr);
}

/*
==============
SpritePLGet

Get a picture that belongs to a sprite
==============
*/
static HPicture *SpritePLGet(sprite_t *spr, const char *name, int type, unsigned int colorkey)
{
    char path[MAX_OSPATH];
    char *slash, *p;

    xstrcpy(path, sizeof(path), spr->name);
    slash = path;
    for(p = path; *p; p++)
        if (*p == '/' || *p == '\\')
            slash = p;
    *slash = 0;

    xstrcat(path, sizeof(path), "/");
    xstrcat(path, sizeof(path), name);

    return HPicture::Get(path, type, colorkey);
}

/*
==============
ParseFrame

Parse a single frame definition (the '{' has already been parsed)
==============
*/
void ParseFrame(sprite_t *spr, spr_animation_t *anim, LParser *p)
{
    spr_frame_t *frame;
    char name[MAX_OSPATH];
    int type;
    unsigned int color;
    unsigned int colorkey;

    anim->frames = (spr_frame_t *)L_Realloc(anim->frames,
                sizeof(spr_frame_t)*(anim->numframes+1), TAG_SPRITE);
    frame = &anim->frames[anim->numframes++];
    memset(frame, 0, sizeof(spr_frame_t));

    p->NextToken();

    if (p->tok.type == '?') // empty frame
    {
        if (anim->numframes <= 1)
            throw p->Error("frame 0 empty");

        p->Expect("}");

        frame->pic = 0;
        frame->offsets[0] = frame->offsets[1] = 0;

        return;
    }

    if (p->tok.type != TOK_STRING && p->tok.type != TOK_QUOTED)
        throw p->Error("frame bitmap name expected");

    type = pic_normal;
    color = 0xffffffff;

    // need to cache the name for later
    xstrcpy(name, sizeof(name), p->tok.string);
    frame->offsets[0] = p->Float();
    frame->offsets[1] = p->Float();

    p->NextToken();

    if (p->tok.type == TOK_INT)
    {
        type = pic_colorkey;
        colorkey = (unsigned int)p->tok.v.f;
        p->NextToken();
    }
    else if (p->tok.type == TOK_STRING)
    {
        if (!strcmp(p->tok.string, "redalpha"))
        {
            type = pic_redalpha;
            color = p->Unsigned();
            p->NextToken();
        }
        else
            throw p->Error("unknown frame modifier %s", p->tok.string);
    }

    if (p->tok.type != '}')
        throw p->Error("'}' expected");

    if (anim == &spr->base && anim->numframes == 1)
        type |= pic_bitmask;

    frame->pic = SpritePLGet(spr, name, type, colorkey);

    frame->r = color & 0xff;
    frame->g = (color >> 8) & 0xff;
    frame->b = (color >> 16) & 0xff;
    frame->a = (color >> 24) & 0xff;
}

/*
==============
ParseAnimation

Parse an entire animation frameset
==============
*/
void ParseAnimation(sprite_t *spr, spr_animation_t *anim, bool getid, LParser *p)
{
    anim->id = 0;

    if (getid)
        anim->id = p->Integer();

    p->NextToken();

    if (p->tok.type == '{')
    {
        anim->speed = 1; // dummy

        ParseFrame(spr, anim, p);
    }
    else if (p->tok.type == '[')
    {
        anim->speed = p->Float();
        if (anim->speed < 0.1)
            throw p->Error("speed is low (%f)", anim->speed);

        p->Expect("/");

        for(;;) {
            p->NextToken();
            if (p->tok.type == ']')
                break;

            if (p->tok.type != '{')
                throw p->Error("']' or '{' expected in framelist");

            ParseFrame(spr, anim, p);
        }
    }
    else
        throw p->Error("'[' or '{' expected after anim or base");
}


/*
==============
SPR_Load

Load the given sprite into memory
==============
*/
sprite_t *SPR_Load(const char *name)
{
    char path[MAX_OSPATH];
    sprite_t *spr;

//  L_Printf("SPR_Load: %s\n", name);

    spr = 0;

    try
    {
        LParser p;
        const char *keyword;

        snprintf(path, sizeof(path), "%s.sp", name);
        p.AddFile(path);

        spr = (sprite_t *)L_Malloc(sizeof(sprite_t), TAG_SPRITE);
        memset(spr, 0, sizeof(sprite_t));
        spr->used = 1;
        spr->cachecycle = spr_cachecycle;
        xstrcpy(spr->name, sizeof(spr->name), name);

        for(;;) {
            keyword = p.TryString();
            if (!keyword)
                break;

            if (!strcmp(keyword, "bbox")) // { minx miny maxx maxy }
            {
                p.Expect("{");
                spr->mins[0] = p.Float();
                spr->mins[1] = p.Float();
                spr->maxs[0] = p.Float();
                spr->maxs[1] = p.Float();
                p.Expect("}");
            }
            else if (!strcmp(keyword, "attach")) // { id x y }
            {
                spr_attach_t *attach;

                if (spr->numattachs >= MAX_ATTACHS)
                    throw p.Error("too many attachs");

                attach = &spr->attachs[spr->numattachs];

                p.Expect("{");
                attach->id = p.Integer();
                attach->offset[0] = p.Float();
                attach->offset[1] = p.Float();
                p.Expect("}");

                spr->numattachs++;
            }
            else if (!strcmp(keyword, "base"))
            {
                if (spr->base.numframes)
                    throw p.Error("only one base allowed");

                ParseAnimation(spr, &spr->base, false, &p);
            }
            else if (!strcmp(keyword, "anim"))
            {
                spr_animation_t *anim;

                if (spr->numanims >= MAX_ANIMATIONS)
                    throw p.Error("too many animation");

                anim = &spr->anims[spr->numanims++];
                ParseAnimation(spr, anim, true, &p);
            }
            else
                throw p.Error("unknown keyword %s", name, keyword);
        }

        if (!spr->base.numframes)
            throw LError("%s: no base", name);

        if (!spr->mins[0]) {
            spr_frame_t *frame;

            frame = &spr->base.frames[0];
            spr->mins[0] = frame->offsets[0];
            spr->mins[1] = frame->offsets[1];
            spr->maxs[0] = frame->offsets[0] + frame->pic->size[0];
            spr->maxs[1] = frame->offsets[1] + frame->pic->size[1];
        }
    }
    catch(...)
    {
        if (spr)
            SPR_DoFree(spr);
        throw;
    }

    spr->pprev = &sprites;
    spr->next = sprites;
    if (spr->next)
        spr->next->pprev = &spr->next;
    sprites = spr;

    return spr;
}

/*
==============
SPR_Get

Get a sprite
==============
*/
sprite_t *SPR_Get(const char *name)
{
    sprite_t *spr;

//  Com_DPrintf("SPR_Get: %s\n", name);

    for(spr = sprites; spr; spr = spr->next) {
        if (!strcmp(spr->name, name)) {
            spr->used++;
            spr->cachecycle = spr_cachecycle;
//          Com_DPrintf("  (use count = %i)\n", spr->used);
            return spr;
        }
    }

    return SPR_Load(name);
}

/*
==============
SPR_CacheCycle

Advance the cache counter, evict unused sprites
==============
*/
void SPR_CacheCycle()
{
    sprite_t *next, *spr;

    spr_cachecycle++;

    next = sprites;
    while(next) {
        spr = next;
        next = next->next;

        if (spr->used)
            spr->cachecycle = spr_cachecycle;
        else
        {
            if (spr_cachecycle - spr->cachecycle > 2)
                SPR_DoFree(spr);
        }
    }
}

/*
==============
SPR_Cleanup

Complain about leaks, free sprites that are currently in the cache
==============
*/
void SPR_Cleanup()
{
    while(sprites) {
        if (sprites->used)
            L_Printf("Sprite leak: %s\n", sprites->name);

        SPR_DoFree(sprites);
    }
}