Ville Lindholm dbc223eb84
Initial commit
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>
2026-05-28 16:35:31 +03:00

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