Import existing source tree; original VCS history is no longer available. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
721 lines
13 KiB
C
721 lines
13 KiB
C
/*(LGPL)
|
|
* Project Spitfire sprites + scrolling engine for SDL
|
|
----------------------------------------------------------------------
|
|
cs.c - Simplistic Control System
|
|
----------------------------------------------------------------------
|
|
* Copyright (C) 2001, 2003, 2007 David Olofson
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#define DBG(x)
|
|
|
|
#include "cs.h"
|
|
#include "logger.h"
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
|
|
/*#define ABS(x) (((x)>=0) ? (x) : (-(x)))*/
|
|
#define ABS(x) labs(x)
|
|
|
|
|
|
static inline void __wrap_point(cs_point_t *p, int wx, int wy)
|
|
{
|
|
if(wx)
|
|
{
|
|
if(p->gx < 0)
|
|
p->gx += (-p->gx / wx + 1) * wx;
|
|
p->gx %= wx;
|
|
}
|
|
if(wy)
|
|
{
|
|
if(p->gy < 0)
|
|
p->gy += (-p->gy / wy + 1) * wy;
|
|
p->gy %= wy;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Make one point do it's thing...
|
|
*/
|
|
static inline void __move_point(cs_point_t *p, int wx, int wy)
|
|
{
|
|
/*
|
|
* Push filter
|
|
*/
|
|
p->ox = p->v.x;
|
|
p->oy = p->v.y;
|
|
|
|
/*
|
|
* Update position and speed
|
|
*/
|
|
p->v.x += p->v.xv;
|
|
p->v.y += p->v.yv;
|
|
p->v.xv += p->v.xa;
|
|
p->v.yv += p->v.ya;
|
|
|
|
__wrap_point(p, wx, wy);
|
|
}
|
|
|
|
|
|
/*
|
|
* Update the motion filtered output of a point.
|
|
*/
|
|
static inline void __update_point_f(cs_point_t *p, int ff, int wx, int wy)
|
|
{
|
|
int ogx = p->gx;
|
|
int ogy = p->gy;
|
|
int ox = p->ox;
|
|
int oy = p->oy;
|
|
|
|
/* Unwrap filter for correct interpolation */
|
|
if(wx | wy)
|
|
{
|
|
if((ox - p->v.x) > (wx >> 1))
|
|
ox -= wx;
|
|
else if((p->v.x - ox) > (wx >> 1))
|
|
ox += wx;
|
|
if((oy - p->v.y) > (wy >> 1))
|
|
oy -= wy;
|
|
else if((p->v.y - oy) > (wy >> 1))
|
|
oy += wy;
|
|
}
|
|
|
|
/*
|
|
* Interpolate output graphics coordinate
|
|
*/
|
|
p->gx = (ff * p->v.x + (256 - ff) * ox) >> 8;
|
|
p->gy = (ff * p->v.y + (256 - ff) * oy) >> 8;
|
|
|
|
/*
|
|
* Wrap interpolated output
|
|
*/
|
|
if(wx)
|
|
{
|
|
while(p->gx < 0)
|
|
p->gx += wx;
|
|
p->gx %= wx;
|
|
}
|
|
if(wy)
|
|
{
|
|
while(p->gy < 0)
|
|
p->gy += wy;
|
|
p->gy %= wy;
|
|
}
|
|
|
|
/*
|
|
* Generate "changed" flag mask.
|
|
*/
|
|
p->changed = (ogx ^ p->gx) | (ogy ^ p->gy);
|
|
}
|
|
|
|
|
|
static inline void __update_point(cs_point_t *p, int ff)
|
|
{
|
|
int ogx = p->gx;
|
|
int ogy = p->gy;
|
|
|
|
/*
|
|
* Interpolate output graphics coordinate
|
|
*/
|
|
p->gx = p->v.x;
|
|
p->gy = p->v.y;
|
|
|
|
/*
|
|
* Generate "changed" flag mask.
|
|
*/
|
|
p->changed = (ogx ^ p->gx) | (ogy ^ p->gy);
|
|
}
|
|
|
|
|
|
/*
|
|
----------------------------------------------------------------------
|
|
* cs_obj_t
|
|
----------------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* Final adjustment of interpolated graphics coords to deal
|
|
* with the display window wrapping over the edge of the world.
|
|
*/
|
|
static inline void __fix_wrap(cs_engine_t *e, cs_obj_t *o)
|
|
{
|
|
if(o->point.gx + (o->w << __CS_SHIFT) < 0)
|
|
o->point.gx += e->wx;
|
|
else if(o->point.gx > (e->w << __CS_SHIFT))
|
|
o->point.gx -= e->wx;
|
|
if(o->point.gy + (o->h << __CS_SHIFT) < 0)
|
|
o->point.gy += e->wy;
|
|
else if(o->point.gy > (e->h << __CS_SHIFT))
|
|
o->point.gy -= e->wy;
|
|
}
|
|
|
|
|
|
static inline int __onscreen(cs_engine_t *e, cs_obj_t *o)
|
|
{
|
|
int gx = CS2PIXEL(o->point.gx);
|
|
int gy = CS2PIXEL(o->point.gy);
|
|
if((gx + o->w < 0) || (gx > e->w))
|
|
return 0;
|
|
if((gy + o->h < 0) || (gy > e->h))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
static inline void __obj_set_layer(cs_obj_t *o, unsigned int layer)
|
|
{
|
|
if(o->layer < 0)
|
|
o->layer = CS_DEFAULT_LAYER;
|
|
if(layer >= CS_LAYERS)
|
|
layer = CS_LAYERS - 1;
|
|
o->layer = layer;
|
|
}
|
|
|
|
/*
|
|
* Add object *first*, ie with lowest priority.
|
|
* (Hmmm... Isn't that how some h/w sprite generators do it,
|
|
* if you allocate in increasing channel order?)
|
|
*/
|
|
static inline void __obj_attach(cs_obj_t *o)
|
|
{
|
|
if(o->head || o->next || o->prev)
|
|
{
|
|
log_printf(ELOG, "cs: HEEEELP! Someone's trying"
|
|
" to short-circuit my guts!\n");
|
|
return;
|
|
}
|
|
|
|
if(o->layer < 0)
|
|
o->layer = CS_DEFAULT_LAYER;
|
|
o->next = o->owner->objects[o->layer];
|
|
o->head = &o->owner->objects[o->layer];
|
|
if(o->next)
|
|
o->next->prev = o;
|
|
o->owner->objects[o->layer] = o;
|
|
}
|
|
|
|
/*
|
|
* BEWARE! There's a nasty hack in here; these objects need to
|
|
* keep track of the addresses of their list heads in order to
|
|
* remove themselves properly!
|
|
*/
|
|
static inline void __obj_detach(cs_obj_t *o)
|
|
{
|
|
/* "Bypass" */
|
|
if(o->next)
|
|
o->next->prev = o->prev;
|
|
if(o->prev)
|
|
o->prev->next = o->next;
|
|
/* In case we're the first object in the list... */
|
|
if(o->head)
|
|
if(*(o->head) == o)
|
|
*(o->head) = o->next;
|
|
|
|
o->head = NULL;
|
|
o->next = NULL;
|
|
o->prev = NULL;
|
|
}
|
|
|
|
|
|
void cs_obj_layer(cs_obj_t *o, unsigned int layer)
|
|
{
|
|
if(layer == o->layer)
|
|
return;
|
|
|
|
__obj_set_layer(o, layer);
|
|
if(o->flags | CS_OBJ_ACTIVE)
|
|
{
|
|
__obj_detach(o);
|
|
__obj_attach(o);
|
|
}
|
|
}
|
|
|
|
|
|
void cs_obj_activate(cs_obj_t *o)
|
|
{
|
|
if(o->flags | CS_OBJ_ACTIVE)
|
|
{
|
|
DBG(log_printf(DLOG, "cs: Tried to activate active object!\n");)
|
|
return;
|
|
}
|
|
|
|
__obj_attach(o);
|
|
o->flags |= CS_OBJ_ACTIVE;
|
|
}
|
|
|
|
|
|
void cs_obj_deactivate(cs_obj_t *o)
|
|
{
|
|
if(!(o->flags | CS_OBJ_ACTIVE))
|
|
{
|
|
DBG(log_printf(DLOG, "cs: Tried to deactivate passive object!\n");)
|
|
return;
|
|
}
|
|
|
|
__obj_detach(o);
|
|
o->flags &= ~CS_OBJ_ACTIVE;
|
|
}
|
|
|
|
|
|
/* Basic methods */
|
|
|
|
void cs_obj_pos(cs_obj_t *o, int x, int y)
|
|
{
|
|
o->point.v.x = PIXEL2CS(x);
|
|
o->point.v.y = PIXEL2CS(y);
|
|
}
|
|
|
|
|
|
void cs_obj_vel(cs_obj_t *o, int xs, int ys)
|
|
{
|
|
o->point.v.xv = xs;
|
|
o->point.v.yv = ys;
|
|
}
|
|
|
|
|
|
/*Set movement acceleration*/
|
|
void cs_obj_acc(cs_obj_t *o, int xa, int ya)
|
|
{
|
|
o->point.v.xa = xa;
|
|
o->point.v.ya = ya;
|
|
}
|
|
|
|
|
|
/*Disable display*/
|
|
void cs_obj_hide(cs_obj_t *o)
|
|
{
|
|
o->flags &= ~CS_OBJ_VISIBLE;
|
|
}
|
|
|
|
|
|
/*Enable display*/
|
|
void cs_obj_show(cs_obj_t *o)
|
|
{
|
|
if(o->flags & CS_OBJ_VISIBLE)
|
|
return;
|
|
|
|
o->flags |= CS_OBJ_VISIBLE;
|
|
/*
|
|
* Kludge? I'm not sure this is really the right place...
|
|
*/
|
|
cs_point_force(&o->point);
|
|
}
|
|
|
|
|
|
/*
|
|
* Remove and throw back in the pool
|
|
*/
|
|
void cs_obj_free(cs_obj_t *o)
|
|
{
|
|
if(o->head == &o->owner->pool)
|
|
{
|
|
DBG(log_printf(DLOG, "cs: Tried to free free object %p!\n",
|
|
o);)
|
|
return;
|
|
}
|
|
cs_obj_deactivate(o);
|
|
if(o->on_free)
|
|
o->on_free(o);
|
|
cs_obj_clear(o);
|
|
|
|
/* Set up head pointer for error checking. */
|
|
o->head = &o->owner->pool;
|
|
|
|
/* Link to pool; singly only */
|
|
o->next = o->owner->pool;
|
|
o->owner->pool = o;
|
|
|
|
++o->owner->pool_free;
|
|
}
|
|
|
|
void cs_obj_clear(cs_obj_t *o)
|
|
{
|
|
o->flags = 0;
|
|
o->layer = -1;
|
|
o->fire.rate = 0;
|
|
o->score = 0;
|
|
o->health = 0;
|
|
}
|
|
|
|
void cs_obj_vector(cs_obj_t *o, cs_vector_t * v)
|
|
{
|
|
if(v)
|
|
o->point.v = *v;
|
|
else
|
|
memset(&(o->point.v), 0, sizeof(o->point.v));
|
|
}
|
|
|
|
|
|
|
|
/* Animation methods */
|
|
void cs_obj_anim(cs_obj_t *o, int bank,
|
|
int first, int num, int start, int spd)
|
|
{
|
|
o->anim.bank = bank;
|
|
o->anim.frame = first;
|
|
o->anim.fframe = first;
|
|
o->anim.aframes = num;
|
|
o->anim.cframe = start << 4;
|
|
o->anim.aspeed = spd;
|
|
o->flags |= CS_OBJ_DOANIM;
|
|
}
|
|
|
|
|
|
void cs_obj_image(cs_obj_t *o, int bank, int frame)
|
|
{
|
|
o->anim.bank = bank;
|
|
o->anim.frame = frame;
|
|
o->flags &= ~CS_OBJ_DOANIM;
|
|
if((bank >= 0) && (bank < o->owner->nimageinfo))
|
|
{
|
|
o->w = o->owner->imageinfo[bank].w;
|
|
o->h = o->owner->imageinfo[bank].h;
|
|
}
|
|
else
|
|
o->w = o->h = 1;
|
|
}
|
|
|
|
|
|
/*Start death sequence*/
|
|
void cs_obj_explode(cs_obj_t *o)
|
|
{
|
|
o->flags |= CS_OBJ_DYING;
|
|
/*
|
|
FIXME: Hardcoded bank for explosion!!!
|
|
*/
|
|
cs_obj_anim(o, 4, 0, 15, 0, 12);
|
|
if(o->on_dying)
|
|
o->on_dying(o);
|
|
}
|
|
|
|
void cs_obj_shoot(cs_obj_t *o, int xo, int yo, int xs,
|
|
int ys, int rate, int delay, int ammo)
|
|
{
|
|
o->fire.v.x = xo;
|
|
o->fire.v.y = yo;
|
|
o->fire.v.xv = xs;
|
|
o->fire.v.yv = ys;
|
|
o->fire.rate = rate;
|
|
o->fire.timer = delay;
|
|
o->fire.ammo = ammo;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* cs_engine_t
|
|
*/
|
|
|
|
static cs_obj_t *__new_obj(cs_engine_t *e)
|
|
{
|
|
cs_obj_t *o = calloc(1, sizeof(cs_obj_t));
|
|
if(!o)
|
|
return NULL;
|
|
|
|
o->owner = e;
|
|
o->prev = NULL;
|
|
o->next = NULL;
|
|
cs_obj_clear(o);
|
|
++e->pool_total;
|
|
return o;
|
|
}
|
|
|
|
cs_engine_t *cs_engine_create(int w, int h, int objects)
|
|
{
|
|
cs_engine_t *e = calloc(1, sizeof(cs_engine_t));
|
|
if(!e)
|
|
return NULL;
|
|
|
|
cs_engine_set_size(e, w, h);
|
|
cs_engine_set_wrap(e, 0, 0);
|
|
|
|
/*
|
|
* Create some objects and throw into the pool.
|
|
* Note that "pool_free" gets initialized by
|
|
* cs_obj_die(), objects are initialized and so
|
|
* on, this way - automatically!
|
|
*/
|
|
while(objects--)
|
|
{
|
|
cs_obj_t *o = __new_obj(e);
|
|
if(o)
|
|
cs_obj_free(o);
|
|
else
|
|
{
|
|
/* Oops, no memory... */
|
|
cs_engine_delete(e);
|
|
return NULL;
|
|
}
|
|
}
|
|
return e;
|
|
}
|
|
|
|
|
|
cs_obj_t *cs_engine_get_obj(cs_engine_t *e)
|
|
{
|
|
cs_obj_t *o = e->pool;
|
|
if(o)
|
|
{
|
|
e->pool = o->next;
|
|
o->head = NULL;
|
|
o->prev = NULL;
|
|
o->next = NULL;
|
|
--e->pool_free;
|
|
}
|
|
return o;
|
|
}
|
|
|
|
|
|
void cs_engine_set_size(cs_engine_t *e, int w, int h)
|
|
{
|
|
e->w = w;
|
|
e->h = h;
|
|
}
|
|
|
|
|
|
void cs_engine_set_image_size(cs_engine_t *e, int bank, int w, int h)
|
|
{
|
|
if(bank < 0)
|
|
return;
|
|
|
|
if(bank >= e->nimageinfo)
|
|
{
|
|
int nnii = bank + 16;
|
|
image_info_t *nii = realloc(e->imageinfo,
|
|
sizeof(image_info_t) * nnii);
|
|
if(!nii)
|
|
return;
|
|
|
|
e->nimageinfo = nnii;
|
|
e->imageinfo = nii;
|
|
}
|
|
|
|
e->imageinfo[bank].w = w;
|
|
e->imageinfo[bank].h = h;
|
|
}
|
|
|
|
|
|
void cs_engine_set_wrap(cs_engine_t *e, int x, int y)
|
|
{
|
|
e->wx = PIXEL2CS(x);
|
|
e->wy = PIXEL2CS(y);
|
|
}
|
|
|
|
|
|
void cs_engine_delete(cs_engine_t *e)
|
|
{
|
|
cs_obj_t *o;
|
|
/* First get all objects to the pool... */
|
|
cs_engine_reset(e);
|
|
/* ...then empty the pool. */
|
|
while((o = cs_engine_get_obj(e)))
|
|
free(o);
|
|
free(e->imageinfo);
|
|
free(e);
|
|
}
|
|
|
|
|
|
void cs_engine_reset(cs_engine_t *e)
|
|
{
|
|
int i;
|
|
for(i = 0; i < CS_LAYERS; ++i)
|
|
while(e->objects[i])
|
|
cs_obj_free(e->objects[i]);
|
|
e->time = 0.0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
----------------------------------------------------------------------
|
|
* The actual *engine* :-)
|
|
----------------------------------------------------------------------
|
|
*/
|
|
|
|
void __enemy_fire(cs_obj_t *o)
|
|
{
|
|
log_printf(DLOG, "fire!\n");
|
|
}
|
|
|
|
void __make_move(cs_obj_t *o, int wx, int wy)
|
|
{
|
|
/*
|
|
* Movement
|
|
*/
|
|
__move_point(&o->point, wx, wy);
|
|
|
|
/*
|
|
* Animation
|
|
*/
|
|
if(o->flags & CS_OBJ_DOANIM)
|
|
{
|
|
/*
|
|
FIXME: Dirty hack here... Should play *any* death animation one-shot and then die.
|
|
*/
|
|
if((o->flags & CS_OBJ_DYING)
|
|
&& (o->anim.cframe >> 4 == 14))
|
|
cs_obj_free(o);
|
|
|
|
o->anim.cframe += o->anim.aspeed;
|
|
o->anim.cframe %= o->anim.aframes << 4;
|
|
o->anim.frame = (o->anim.cframe >> 4) + o->anim.fframe;
|
|
}
|
|
|
|
/*
|
|
* Fire / spawning
|
|
*/
|
|
if(!(o->flags & CS_OBJ_DYING) && o->fire.rate)
|
|
{
|
|
if(o->fire.timer > 0)
|
|
--o->fire.timer;
|
|
else
|
|
{
|
|
o->fire.timer = o->fire.rate;
|
|
__enemy_fire(o);
|
|
if(o->on_fire)
|
|
o->on_fire(o);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void __update_points(cs_engine_t *e, float frac_frame)
|
|
{
|
|
cs_obj_t *o;
|
|
int i;
|
|
int ff = frac_frame * 256.0;
|
|
|
|
if(ff < 0)
|
|
ff = 0;
|
|
else if(ff > 256)
|
|
ff = 256;
|
|
|
|
if(e->filter)
|
|
for(i = 0; i < CS_USER_POINTS; ++i)
|
|
__update_point_f(&e->points[i], ff, e->wx, e->wy);
|
|
else
|
|
for(i = 0; i < CS_USER_POINTS; ++i)
|
|
__update_point(&e->points[i], ff);
|
|
|
|
for(i = 0; i < CS_LAYERS; ++i)
|
|
{
|
|
if(e->filter)
|
|
__update_point_f(&e->offsets[i], ff, e->wx, e->wy);
|
|
else
|
|
__update_point(&e->offsets[i], ff);
|
|
e->changed[i] = 0;
|
|
o = e->objects[i];
|
|
while(o)
|
|
{
|
|
if(e->filter)
|
|
__update_point_f(&o->point, ff, e->wx,
|
|
e->wy);
|
|
else
|
|
__update_point(&o->point, ff);
|
|
o->point.gx -= e->offsets[i].gx;
|
|
o->point.gy -= e->offsets[i].gy;
|
|
e->changed[i] |= o->point.changed;
|
|
__fix_wrap(e, o);
|
|
o = o->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void __run_all(cs_engine_t *e)
|
|
{
|
|
int i;
|
|
cs_obj_t *o;
|
|
|
|
for(i = 0; i < CS_USER_POINTS; ++i)
|
|
__move_point(&e->points[i], e->wx, e->wy);
|
|
|
|
for(i = 0; i < CS_LAYERS; ++i)
|
|
{
|
|
__move_point(&e->offsets[i], e->wx, e->wy);
|
|
o = e->objects[i];
|
|
while(o)
|
|
{
|
|
__make_move(o, e->wx, e->wy);
|
|
o = o->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void __wrap_all(cs_engine_t *e)
|
|
{
|
|
int i;
|
|
cs_obj_t *o;
|
|
|
|
for(i = 0; i < CS_USER_POINTS; ++i)
|
|
__wrap_point(&e->points[i], e->wx, e->wy);
|
|
|
|
for(i = 0; i < CS_LAYERS; ++i)
|
|
{
|
|
__wrap_point(&e->offsets[i], e->wx, e->wy);
|
|
o = e->objects[i];
|
|
while(o)
|
|
{
|
|
__wrap_point(&o->point, e->wx, e->wy);
|
|
o = o->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void cs_engine_advance(cs_engine_t *e, double to_frame)
|
|
{
|
|
if(to_frame > 0)
|
|
{
|
|
int frames = floor(to_frame) - floor(e->time);
|
|
if(frames > 0)
|
|
{
|
|
while(frames--)
|
|
{
|
|
__run_all(e);
|
|
e->on_frame(e);
|
|
}
|
|
}
|
|
}
|
|
e->time = to_frame;
|
|
if(e->wx || e->wy)
|
|
__wrap_all(e);
|
|
__update_points(e, to_frame - floor(to_frame));
|
|
}
|
|
|
|
|
|
void cs_engine_render(cs_engine_t *e)
|
|
{
|
|
cs_obj_t *o;
|
|
int i;
|
|
|
|
for(i = CS_LAYERS - 1; i >= 0; --i)
|
|
{
|
|
o = e->objects[i];
|
|
while(o)
|
|
{
|
|
if(o->flags & CS_OBJ_VISIBLE)
|
|
if(o->render)
|
|
if(__onscreen(e, o))
|
|
o->render(o);
|
|
o = o->next;
|
|
}
|
|
}
|
|
}
|