ref: 86318ae338e606247cfa057d141e6a974f4898a9
dir: /game.c/
#include <u.h>
#include <libc.h>
#include "dat.h"
#include "fns.h"
/* FIXME:
* - selectmove/selectspawnmove: manage corpses again
* - db: get rid of void types
* - fix selection/showmoverange
* - tchs: global rule: only one move per turn, no more (and no less?)
* - tchs: implement custom move/attack ranges and checks
* - tchs: implement no attack (attack -> move onto unit)
* - tchs: moving onto existing unit makes it disappear (cleanly)
* - resupply cost (default 0)
* - ratio, repair, destroy, launch, food, ammo, cloak lists
* - food/fuel consumption rate list
* - alternate attack mode ammo/...
* - bless, curse/slow, raise lists
* - terrain bonuses lists: atk, def, vis
* -> tatkm=water elemental=15
* -> tdefm=water elemental=3
* -> tvism=mountain heavy=2 (and inf=2? or any unit?)
* - tbs2: unit and terrain sprites
* - tbs1: sprites for rest of units
* - manpages: tbs(1), tbs(6)?
* - proper victory screen
* - win condition: last team left standing (next turn -> current team)
* - quit: confirm with changed cursor
* - move validation and networking model
* - ai: optional plugin for an artificial networked player
* - savegames
* - fow
* - weather
* - diplomacy/trading
* - superteams (factions/groups), shared economies
*/
Team team[Nteam];
int nteam, curteam;
int turn, gameover;
int initmoney, unitcap, firstturnnoinc, nocorpse;
Map *saved, *unique;
typedef struct Fn Fn;
struct Fn{
char *name;
int (*check)(void);
void (*fn)(void);
};
static Munit *old, *new, *savedcorpse;
static void selectmenu(void);
static void selecttile(void);
static void selectaction(void);
static void selectspawnmove(void);
static void selectmove(void);
static void confirmmove(void);
static void
freeunit(Map *m)
{
free(m->u);
m->u = nil;
}
static void
updatecorpses(void)
{
Map *m;
for(m=map; m<mape; m++)
if(m->u != nil && m->u->decaydt && --m->u->decaydt == 0)
freeunit(m);
}
static int
iscorpse(Map *m)
{
Munit *mu;
mu = m->u;
if(mu != nil && strcmp(mu->u->name, "corpse") == 0)
return 1;
return 0;
}
static void
canmove_(Map *m, Map *ref, Unit *u, int mp)
{
if(m->movep >= mp)
return;
if(m != ref){
if(m->u != nil && m->u->team != curteam && !iscorpse(m))
return;
if(mp < u->move[m->t->move] + 1)
return;
mp -= u->move[m->t->move] + 1;
}
m->canmove = 1;
if(mp <= 0)
return;
m->movep = mp;
if((m+1-map) % mapwidth != 0)
canmove_(m+1, ref, u, mp);
if(m+mapwidth < mape)
canmove_(m+mapwidth, ref, u, mp);
if(m-mapwidth >= map)
canmove_(m-mapwidth, ref, u, mp);
if((m-map) % mapwidth != 0)
canmove_(m-1, ref, u, mp);
}
static void
canmove(void)
{
canmove_(selected2, selected2, selected2->u->u, selected2->u->u->mp);
}
static void
canattack_(Map *m, int r1, int r2)
{
if(m->canatk)
return;
if(r1-- <= 0 && m != selected2){
m->canatk = 1;
if(m->u != nil
&& m->u->team != curteam
&& !iscorpse(m)
&& selected2->u->team == curteam)
m->atkp = 1;
}
if(r2-- == 0)
return;
if((m+1-map) % mapwidth != 0)
canattack_(m+1, r1, r2);
if(m+mapwidth < mape)
canattack_(m+mapwidth, r1, r2);
if(m-mapwidth >= map)
canattack_(m-mapwidth, r1, r2);
if((m-map) % mapwidth != 0)
canattack_(m-1, r1, r2);
}
static void
canattack(void)
{
canattack_(selected2, selected2->u->u->rmin, selected2->u->u->rmax);
}
static void
clrmoverange(int domove)
{
Map *m;
for(m=map; m<mape; m++){
if(domove){
m->movep = 0;
m->canmove = 0;
}
m->atkp = 0;
m->canatk = 0;
}
}
static void
showmoverange(int domove)
{
clrmoverange(domove);
if(selected2->u == nil)
return;
if(domove)
canmove();
canattack();
}
static void
exitmenu(int domove)
{
selected2 = saved;
menuon = 0;
if(domove)
selectfn = selectspawnmove;
else{
saved = nil;
selectfn = selecttile;
}
}
static void
spawn(void)
{
Unit *u;
u = saved->t->spawn.u[selected2 - map];
old = saved->u;
spawnunit(saved, u, curteam);
new = saved->u;
team[curteam].money -= unique != nil && team[curteam].unique->u == u ? team[curteam].unique->ecost : u->cost;
exitmenu(1);
}
static void
selectmenu(void)
{
int cost;
char *i[] = {"spawn", nil};
void (*fn[])(void) = {spawn};
Tunit *tu;
Unit *u;
Map *m;
tu = &saved->t->spawn;
if(tu->u + (selected2 - map) >= tu->e){
exitmenu(0);
return;
}
if(selected2 != selected)
return;
u = tu->u[selected2 - map];
if(u->unique){
if(unique != nil)
return;
else
cost = team[curteam].unique->ecost;
}else
cost = u->cost;
selectfn = selectmenu;
clrmoverange(1);
canmove_(saved, saved, u, u->mp);
for(m=map; m<mape; m++)
if(m->canmove)
break;
if(m == mape || team[curteam].money < cost)
clrmoverange(1);
else
lmenu(i, fn);
}
static void
spawnlist(void)
{
Map *m;
unique = nil;
if(team[curteam].unique != nil)
for(m=map; m<mape; m++)
if(m->u == team[curteam].unique){
unique = m;
break;
}
saved = selected2;
selected2 = nil;
selectfn = selectmenu;
menuon = 1;
}
static int
checkspawn(void)
{
if(saved != nil
|| team[curteam].nunit >= unitcap
|| selected2->team != curteam
|| selected2->t->spawn.u == nil)
return 0;
return 1;
}
static void
endmove(void)
{
clrmoverange(1);
selected2->u->done = 1;
selected2->u->decaydt = 0;
selected2 = nil;
if(old != nil){
saved->u = old;
old = nil;
}
new = nil;
saved = nil;
selectfn = selecttile;
}
static int
checkend(void)
{
if(selected2->u == nil
|| selected2->u->team != curteam)
return 0;
return 1;
}
static void
selectspawnmove(void)
{
if(selected2 == nil || selected2 == saved && old != nil)
goto again;
showmoverange(0);
redraw(0);
if(selected2 == selected){
selectaction();
return;
}else if(selected2->canmove
&& (selected2->u == nil || iscorpse(selected2))
&& (old == nil || selected2 != saved)){
if(iscorpse(selected2))
savedcorpse = selected2->u;
selected2->u = selected->u;
selected->u = nil;
return;
}
again:
if(selected != nil && selected->u == new)
selected->u = nil;
saved->u = new;
selected2 = saved;
}
static void
commitmove(void)
{
if(saved == nil){
selected2->u = selected->u;
selected->u = nil;
saved = selected;
}else{
saved->u = selected->u;
selected->u = nil;
saved = nil;
}
}
static void
cancelmove(void)
{
if(saved != nil){
commitmove();
if(savedcorpse != nil){
selected2->u = savedcorpse;
savedcorpse = nil;
}
if(old != nil){
saved->u = old;
old = nil;
}
saved = nil;
selectfn = selecttile;
}else
selectfn = selectmove;
selected2 = nil;
clrmoverange(1);
}
static void
confirmmove(void)
{
if(selected2 == selected){
showmoverange(1);
redraw(0);
selectaction();
}else
cancelmove();
}
static void
selectmove(void)
{
if(selected2 == nil)
return;
/* FIXME: manage corpses */
if(selected2->canmove && selected2->u == nil){
commitmove();
selectfn = confirmmove;
}else{
selectfn = selecttile;
selected2 = nil;
}
}
static void
move(void)
{
selectfn = selectmove;
}
static int
checkmove(void)
{
if(saved != nil
|| selected2->u == nil
|| selected2->u->team != curteam)
return 0;
return 1;
}
static void
die(Map *m)
{
Munit *mu;
Unit *u;
mu = m->u;
team[mu->team].nunit--;
if(mu == team[mu->team].unique){
mu->ecost += mu->u->cost;
m->u = nil;
return;
}
if(!nocorpse)
for(u=unit; u<unit+nunit; u++)
if(strcmp(u->name, "corpse") == 0){
mu->u = u;
mu->decaydt = 2;
}
}
static void
rankup(Munit *mu)
{
mu->eatk += 2;
mu->edef += 2;
}
static int
attackunit(Map *to, Map *from)
{
int a, d, r, n;
Munit *mu, *mv;
mu = from->u;
mv = to->u;
r = 100; /* FIXME: damage ratio */
a = (mu->eatk + mu->atkm + nrand(mu->u->Δatk)) * r / 100 * mu->hp / 100;
if(a <= 0)
return 0;
d = mv->edef + mu->defm + to->t->def * 5;
n = a - d;
if(n < 0)
n = 0;
if(mv->hp < n)
n = mv->hp;
mv->hp -= n;
if(r = mv->hp == 0)
die(to);
n = mu->xp / 100;
mu->xp += a;
if(mu->xp / 100 > n)
rankup(mu);
return r;
}
static void
selectattack(void)
{
if(!selected2->atkp)
goto nope;
if(attackunit(selected2, selected) == 0
&& selected2->u->u->rmin == 1
&& (selected2 - selected == 1
|| selected2 - selected == -1
|| selected2 - selected == mapwidth
|| selected2 - selected == -mapwidth))
attackunit(selected, selected2);
selected2 = selected;
endmove();
return;
nope:
selected2 = nil;
selectfn = selecttile;
}
static void
attack(void)
{
selectfn = selectattack;
}
static int
checkattack(void)
{
Map *m;
if(saved != nil && selected->u->u->rmin > 1
|| selected2->u == nil
|| selected2->u->team != curteam)
return 0;
for(m=map; m<mape; m++)
if(m->atkp)
return 1;
return 0;
}
static void
occupy(void)
{
team[selected2->team].nbuild--;
team[selected2->u->team].nbuild++;
if(selected2->t->spawn.u != nil){
team[selected2->team].nprod--;
team[selected2->u->team].nprod++;
}
selected2->team = selected2->u->team;
team[selected2->team].income += selected2->t->income;
endmove();
}
static int
checkoccupy(void)
{
Unit **u;
Tunit *tu;
if(selected2->u == nil
|| selected2->team == curteam)
return 0;
tu = &selected2->t->occupy;
if(selected2->u->team == curteam){
for(u=tu->u; u<tu->e; u++)
if(*u == selected2->u->u)
return 1;
}
return 0;
}
static Fn fn[] = {
{"spawn", checkspawn, spawnlist},
{"occupy", checkoccupy, occupy},
{"attack", checkattack, attack},
{"move", checkmove, move},
{"end", checkend, endmove},
};
static void
selectaction(void)
{
char *i[nelem(fn)+1] = {nil}, **ip = i;
void (*fns[nelem(fn)])(void) = {nil}, (**fp)(void) = fns;
Fn *f;
if(selected2 != selected)
goto end;
for(f=fn; f<fn+nelem(fn); f++)
if(f->check()){
*ip++ = f->name;
*fp++ = f->fn;
}
if(ip > i)
lmenu(i, fns);
else{
end:
clrmoverange(1);
selectfn = selecttile;
}
}
static void
selecttile(void)
{
selectfn = selectaction;
showmoverange(1);
}
void
spawnunit(Map *m, Unit *u, int t)
{
Munit *mu;
if(t < 0 || t > nelem(team))
t = 0;
if(u->unique && curteam != 0 && team[curteam].unique != nil){
mu = team[curteam].unique;
}else{
mu = emalloc(sizeof *m->u);
if(t > nteam)
nteam = t;
if(u->unique){
if(team[t].unique != nil)
sysfatal("spawnunit: team %d uniqueness violation\n", t);
team[t].unique = mu;
}
mu->u = u;
mu->team = t;
mu->ecost = u->cost;
mu->eatk = u->atk;
mu->edef = u->def;
}
mu->hp = 100;
team[t].nunit++;
m->u = mu;
}
static void
resupply(void)
{
Map *m;
Unit **u;
Munit *mu;
Terrain *t;
Tunit *tu;
for(m=map; m<mape; m++){
mu = m->u;
t = m->t;
tu = t->resupply;
if(mu == nil
|| mu->team != curteam
|| mu->hp == 100
|| tu == nil)
continue;
for(u=tu->u; u<tu->e; u++)
if(mu->u == *u)
break;
if(u == tu->e)
continue;
mu->hp += 20;
if(mu->hp > 100)
mu->hp = 100;
}
}
static void
newturn(void)
{
if(turn > 0 || !firstturnnoinc)
team[curteam].money += team[curteam].income;
resupply();
}
void
endturn(void)
{
Team *t;
Map *m;
if(saved != nil)
cancelmove();
menuon = 0;
selectfn = selecttile;
selected = selected2 = nil;
for(m=map; m<mape; m++)
if(m->u != nil)
m->u->done = 0;
for(t=team+curteam+1; t<=team+nteam; t++)
if(t->nunit > 0 || t->nprod > 0)
break;
if(t == team+nteam+1){
turn++;
updatecorpses();
for(t=team+1; t<team+curteam; t++)
if(t->nunit > 0 || t->nprod > 0)
break;
}
if(t == team+curteam)
gameover = 1;
curteam = t - team;
newturn();
redraw(0);
}
void
initgame(void)
{
Team *t;
for(t=team; t<=team+nteam; t++){
t->money = initmoney;
if(t > team && curteam == 0
&& (t->nunit > 0 || t->nprod > 0))
curteam = t - team;
}
if(curteam == 0)
sysfatal("initgame: the only winning move is not to play");
newturn();
selectfn = selecttile;
}