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);
}
}