Builds a browser-playable .html/.js/.wasm/.data via `./build-web.sh` (no
new CMake top-level target — Emscripten is detected at configure time and
takes the existing kobodl through a web-flavored pipeline).
Key pieces:
- CMakeLists.txt: route SDL3 through `--use-port=sdl3`; substitute
`--use-port=libpng` for the missing sdl3_image port (Emscripten 5.0.7
ships only sdl3 + sdl3_ttf). pthread (atomics+bulk-memory) lifted to
kobo_deps so every TU agrees with wasm-ld's --shared-memory. Link with
-sASYNCIFY (avoids rewriting kobo's blocking main loop around
emscripten_set_main_loop), -sALLOW_MEMORY_GROWTH/-sMAXIMUM_MEMORY=1gb,
-sPTHREAD_POOL_SIZE=4, -lidbfs.js, and --preload-file data@/data.
- graphics/img_compat.{h,c}: libpng-backed IMG_Load shim, used in
sprite.c / gfxengine.cpp / filters.c via #include "img_compat.h".
Native builds passthrough to <SDL3_image/SDL_image.h> unchanged.
- web/shell.html: HTML wrapper that mounts /home as IDBFS for persistent
scores/config. Startup gated on addRunDependency until syncfs(true)
finishes, then /home/scores is mkdir'd post-load (the game's addPlayer
doesn't auto-create the score dir; see TODO in score.cpp).
beforeunload + visibilitychange flushes /home back to IndexedDB.
- build-web.sh: emcmake wrapper + a static server that sets the
COOP/COEP headers SharedArrayBuffer/pthreads require.
Source fixes needed to build under Emscripten's libc/libcxx:
- Renamed enemy_kind `pipe2` -> `pipe2k` (collided with libc pipe2(2)
always declared in Emscripten's <unistd.h>).
- graphics/toolkit.cpp: added missing <stdio.h> for snprintf (Emscripten's
libcxx headers don't pull it in transitively).
- states.cpp: gated the "Quit Kobo Deluxe" main-menu button on
!__EMSCRIPTEN__; exit() on the web leaves a dead canvas.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
1430 lines
27 KiB
C++
1430 lines
27 KiB
C++
/*(GPL)
|
|
------------------------------------------------------------
|
|
Kobo Deluxe - An enhanced SDL port of XKobo
|
|
------------------------------------------------------------
|
|
* Copyright (C) 1995, 1996 Akira Higuchi
|
|
* 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 General Public License as published by the
|
|
* Free Software Foundation; either version 2 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
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include "screen.h"
|
|
#include "manage.h"
|
|
#include "enemies.h"
|
|
#include "myship.h"
|
|
#include "random.h"
|
|
#include <math.h>
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Fast integer atan() approximation. Input is 24:8 fixed point.
|
|
// Output is 0..64 for 0..45 deg, accurate accurate down to LSB.
|
|
// In: q = 256 * minv / maxv (0..256 <==> 0..1)
|
|
// Out: atan(q / 256) * 256 (0..64 <==> 0..45 deg)
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// atan() approximations for 0..9 deg:
|
|
// a = 83 * q / 256 - q*q / 2844 (+/- 2.1%)
|
|
// a = 82 * q / 256 - k*k / 8500 - x*x*x / 1600000 (+/- 0.6%)
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
static inline int fastatan(int q)
|
|
{
|
|
int q2 = q * q;
|
|
int q3 = q2 * q;
|
|
return (82 * q >> 8) - (62 * q2 >> 19) - (10 * q3 >> 24);
|
|
}
|
|
|
|
static int speed2dir(int sx, int sy, int frames)
|
|
{
|
|
if(!sx && !sy)
|
|
return 0;
|
|
int at2;
|
|
if(sx > 0)
|
|
{
|
|
// Right
|
|
if(sy < 0)
|
|
{
|
|
// Top-right quadrant
|
|
sy = -sy;
|
|
if(sy > sx)
|
|
at2 = fastatan(sx * 256 / sy);
|
|
else
|
|
at2 = 128 - fastatan(sy * 256 / sx);
|
|
}
|
|
else
|
|
{
|
|
// Bottom-right quadrant
|
|
if(sx > sy)
|
|
at2 = 128 + fastatan(sy * 256 / sx);
|
|
else
|
|
at2 = 256 - fastatan(sx * 256 / sy);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Left
|
|
sx = -sx;
|
|
if(sy > 0)
|
|
{
|
|
// Bottom-left quadrant
|
|
if(sy > sx)
|
|
at2 = 256 + fastatan(sx * 256 / sy);
|
|
else
|
|
at2 = 384 - fastatan(sy * 256 / sx);
|
|
}
|
|
else
|
|
{
|
|
// Top-left quadrant
|
|
sy = -sy;
|
|
if(sx > sy)
|
|
at2 = 384 + fastatan(sy * 256 / sx);
|
|
else
|
|
at2 = 512 - fastatan(sx * 256 / sy);
|
|
}
|
|
}
|
|
at2 = (at2 * frames + 256) >> 9;
|
|
return at2 > frames - 1 ? 1 : at2 + 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* (template)
|
|
* ===========================================================================
|
|
*/
|
|
|
|
inline void _enemy::move_enemy_m(int quick, int maxspeed)
|
|
{
|
|
if(diffx > 0)
|
|
{
|
|
if(h > -maxspeed)
|
|
h -= quick;
|
|
}
|
|
else if(diffx < 0)
|
|
{
|
|
if(h < maxspeed)
|
|
h += quick;
|
|
}
|
|
if(diffy > 0)
|
|
{
|
|
if(v > -maxspeed)
|
|
v -= quick;
|
|
}
|
|
else if(diffy < 0)
|
|
{
|
|
if(v < maxspeed)
|
|
v += quick;
|
|
}
|
|
}
|
|
|
|
inline void _enemy::move_enemy_template(int quick, int maxspeed)
|
|
{
|
|
if(diffx > 0)
|
|
{
|
|
if(h > -maxspeed)
|
|
h -= quick;
|
|
}
|
|
else if(diffx < 0)
|
|
{
|
|
if(h < maxspeed)
|
|
h += quick;
|
|
}
|
|
if(diffy > 0)
|
|
{
|
|
if(v > -maxspeed)
|
|
v -= quick;
|
|
}
|
|
else if(diffy < 0)
|
|
{
|
|
if(v < maxspeed)
|
|
v += quick;
|
|
}
|
|
int ndi = speed2dir(h, v, 16);
|
|
if(ndi > 0)
|
|
di = ndi;
|
|
}
|
|
|
|
inline void _enemy::move_enemy_template_2(int quick, int maxspeed)
|
|
{
|
|
h = -PIXEL2CS(diffy) / (1<<quick);
|
|
v = PIXEL2CS(diffx) / (1<<quick);
|
|
|
|
if(diffx > 0)
|
|
{
|
|
if(h > -maxspeed)
|
|
h -= quick;
|
|
}
|
|
else if(diffx < 0)
|
|
{
|
|
if(h < maxspeed)
|
|
h += quick;
|
|
}
|
|
if(diffy > 0)
|
|
{
|
|
if(v > -maxspeed)
|
|
v -= quick;
|
|
}
|
|
else if(diffy < 0)
|
|
{
|
|
if(v < maxspeed)
|
|
v += quick;
|
|
}
|
|
int ndi = speed2dir(h, v, 16);
|
|
if(ndi > 0)
|
|
di = ndi;
|
|
}
|
|
|
|
inline void _enemy::move_enemy_template_3(int quick, int maxspeed)
|
|
{
|
|
h = PIXEL2CS(diffy) / (1<<quick);
|
|
v = -PIXEL2CS(diffx) / (1<<quick);
|
|
|
|
if(diffx > 0)
|
|
{
|
|
if(h > -maxspeed)
|
|
h -= quick;
|
|
}
|
|
else if(diffx < 0)
|
|
{
|
|
if(h < maxspeed)
|
|
h += quick;
|
|
}
|
|
if(diffy > 0)
|
|
{
|
|
if(v > -maxspeed)
|
|
v -= quick;
|
|
}
|
|
else if(diffy < 0)
|
|
{
|
|
if(v < maxspeed)
|
|
v += quick;
|
|
}
|
|
int ndi = speed2dir(h, v, 16);
|
|
if(ndi > 0)
|
|
di = ndi;
|
|
}
|
|
|
|
inline void _enemy::shot_template(const enemy_kind * ekp,
|
|
int shift, int rnd, int maxspeed)
|
|
{
|
|
int vx = -diffx;
|
|
int vy = -diffy;
|
|
if(rnd)
|
|
{
|
|
vx += gamerand.get() & (rnd - 1) - (rnd >> 1);
|
|
vy += gamerand.get() & (rnd - 1) - (rnd >> 1);
|
|
}
|
|
vx = PIXEL2CS(vx) / (1<<shift);
|
|
vy = PIXEL2CS(vy) / (1<<shift);
|
|
if(maxspeed > 0)
|
|
{
|
|
if(vx > maxspeed)
|
|
vx = maxspeed;
|
|
else if(vx < -maxspeed)
|
|
vx = -maxspeed;
|
|
if(vy > maxspeed)
|
|
vy = maxspeed;
|
|
else if(vy < -maxspeed)
|
|
vy = -maxspeed;
|
|
}
|
|
enemies.make(ekp, CS2PIXEL(x + vx), CS2PIXEL(y + vy), vx,
|
|
vy);
|
|
if(enemies.is_intro)
|
|
return;
|
|
if(&ring == ekp)
|
|
sound.g_launch_ring(x, y);
|
|
else if(&beam == ekp)
|
|
sound.g_launch_beam(x, y);
|
|
else if(&bomb1 == ekp || &bomb2 == ekp)
|
|
sound.g_launch_bomb(x, y);
|
|
else
|
|
sound.g_launch(x, y);
|
|
}
|
|
|
|
void _enemy::shot_template_8_dir(const enemy_kind * ekp)
|
|
{
|
|
static int vx[] = { 0, 200, 300, 200, 0, -200, -300, -200 };
|
|
static int vy[] = { -300, -200, 0, 200, 300, 200, 0, -200 };
|
|
int i;
|
|
for(i = 0; i < 8; i++)
|
|
enemies.make(ekp, CS2PIXEL(x), CS2PIXEL(y), vx[i],
|
|
vy[i]);
|
|
if(&ring == ekp)
|
|
sound.g_m_launch_ring(x, y);
|
|
else if(&beam == ekp)
|
|
sound.g_m_launch_beam(x, y);
|
|
else if(&bomb1 == ekp || &bomb2 == ekp)
|
|
sound.g_m_launch_bomb(x, y);
|
|
else
|
|
sound.g_m_launch(x, y);
|
|
}
|
|
|
|
void _enemy::kill_default()
|
|
{
|
|
enemies.make(enemies.randexp(), CS2PIXEL(x), CS2PIXEL(y),
|
|
h >> 1, v >> 1);
|
|
sound.g_enemy_explo(x, y);
|
|
release();
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* beam
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_beam()
|
|
{
|
|
di = 1 + pubrand.get(4);
|
|
health = 1;
|
|
shootable = 0;
|
|
damage = 20;
|
|
}
|
|
|
|
void _enemy::move_beam()
|
|
{
|
|
if(norm >= ((VIEWLIMIT >> 1) + 32))
|
|
release();
|
|
di += pubrand.get(1) + 1;
|
|
if(di > 16)
|
|
di = 1;
|
|
}
|
|
|
|
void _enemy::kill_beam()
|
|
{
|
|
enemies.make(&beamexpl, CS2PIXEL(x), CS2PIXEL(y));
|
|
release();
|
|
}
|
|
|
|
const enemy_kind beam = {
|
|
0,
|
|
&_enemy::make_beam,
|
|
&_enemy::move_beam,
|
|
&_enemy::kill_beam,
|
|
2,
|
|
B_BULLETS, 0,
|
|
LAYER_BULLETS
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* rock
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_rock()
|
|
{
|
|
count = 500;
|
|
health = game.rock_health;
|
|
damage = game.rock_damage;
|
|
di = gamerand.get(5) + 1;
|
|
a = gamerand.get(1) ? 1 : -1;
|
|
switch(gamerand.get() % 3)
|
|
{
|
|
case 0:
|
|
bank = B_ROCK1;
|
|
break;
|
|
case 1:
|
|
bank = B_ROCK2;
|
|
break;
|
|
case 2:
|
|
bank = B_ROCK3;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void _enemy::move_rock()
|
|
{
|
|
if(bank == B_ROCK3)
|
|
{
|
|
di += a;
|
|
if(di < 1)
|
|
di += 48;
|
|
else if(di > 48)
|
|
di -= 48;
|
|
}
|
|
else
|
|
di = ((di + a - 1) & 31) + 1;
|
|
}
|
|
|
|
void _enemy::kill_rock()
|
|
{
|
|
enemies.make(&rockexpl, CS2PIXEL(x), CS2PIXEL(y), h >> 1, v >> 1);
|
|
sound.g_rock_explo(x, y);
|
|
release();
|
|
}
|
|
|
|
const enemy_kind rock = {
|
|
10,
|
|
&_enemy::make_rock,
|
|
&_enemy::move_rock,
|
|
&_enemy::kill_rock,
|
|
4,
|
|
B_ROCK1, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* ring
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_ring()
|
|
{
|
|
count = 500;
|
|
health = 20;
|
|
damage = 30;
|
|
di = 1;
|
|
}
|
|
|
|
void _enemy::move_ring()
|
|
{
|
|
di += 1 + pubrand.get(1);
|
|
if(di > 16)
|
|
di = 1;
|
|
}
|
|
|
|
void _enemy::kill_ring()
|
|
{
|
|
enemies.make(&ringexpl, CS2PIXEL(x), CS2PIXEL(y), h >> 1, v >> 1);
|
|
sound.g_ring_explo(x, y);
|
|
release();
|
|
}
|
|
|
|
const enemy_kind ring = {
|
|
1,
|
|
&_enemy::make_ring,
|
|
&_enemy::move_ring,
|
|
&_enemy::kill_ring,
|
|
4,
|
|
B_RING, 0,
|
|
LAYER_BULLETS
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* bomb
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_bomb()
|
|
{
|
|
count = 500;
|
|
health = 20;
|
|
damage = 70;
|
|
di = 1;
|
|
}
|
|
|
|
void _enemy::move_bomb1()
|
|
{
|
|
int h1 = ABS(diffx);
|
|
int v1 = ABS(diffy);
|
|
if(((h1 < 100) && (v1 < 30)) || (h1 < 30) && (v1 < 100))
|
|
{
|
|
int vx1 = PIXEL2CS(-diffx) / (3*8);
|
|
int vy1 = PIXEL2CS(-diffy) / (3*8);
|
|
int vx2 = vx1, vx3 = vx1;
|
|
int vy2 = vy1, vy3 = vy1;
|
|
int i;
|
|
for(i = 0; i < 4; i++)
|
|
{
|
|
int tmp = vx2;
|
|
vx2 += (vy2 >> 4);
|
|
vy2 -= (tmp >> 4);
|
|
tmp = vx3;
|
|
vx3 -= (vy3 >> 4);
|
|
vy3 += (tmp >> 4);
|
|
}
|
|
enemies.make(&beam, CS2PIXEL(x), CS2PIXEL(y), vx2, vy2);
|
|
enemies.make(&beam, CS2PIXEL(x), CS2PIXEL(y), vx3, vy3);
|
|
enemies.make(&bombdeto, CS2PIXEL(x), CS2PIXEL(y), vx1, vy1);
|
|
sound.g_bomb_deto(x, y);
|
|
release();
|
|
}
|
|
if(++di > 16)
|
|
di = 1;
|
|
}
|
|
|
|
const enemy_kind bomb1 = {
|
|
5,
|
|
&_enemy::make_bomb,
|
|
&_enemy::move_bomb1,
|
|
&_enemy::kill_default,
|
|
5,
|
|
B_BOMB, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* bomb2
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::move_bomb2()
|
|
{
|
|
int h1 = ABS(diffx);
|
|
int v1 = ABS(diffy);
|
|
if(((h1 < 100) && (v1 < 20)) || (h1 < 20) && (v1 < 100))
|
|
{
|
|
int vx1 = PIXEL2CS(-diffx) / (3*8);
|
|
int vy1 = PIXEL2CS(-diffy) / (3*8);
|
|
int vx2 = vx1, vx3 = vx1;
|
|
int vy2 = vy1, vy3 = vy1;
|
|
int i;
|
|
for(i = 0; i < 6; i++)
|
|
{
|
|
int tmp = vx2;
|
|
vx2 += (vy2 >> 4);
|
|
vy2 -= (tmp >> 4);
|
|
tmp = vx3;
|
|
vx3 -= (vy3 >> 4);
|
|
vy3 += (tmp >> 4);
|
|
}
|
|
int vx4 = vx2, vx5 = vx3;
|
|
int vy4 = vy2, vy5 = vy3;
|
|
for(i = 0; i < 6; i++)
|
|
{
|
|
int tmp = vx2;
|
|
vx2 += (vy2 >> 4);
|
|
vy2 -= (tmp >> 4);
|
|
tmp = vx3;
|
|
vx3 -= (vy3 >> 4);
|
|
vy3 += (tmp >> 4);
|
|
}
|
|
enemies.make(&beam, CS2PIXEL(x), CS2PIXEL(y), vx1, vy1);
|
|
enemies.make(&beam, CS2PIXEL(x), CS2PIXEL(y), vx2, vy2);
|
|
enemies.make(&beam, CS2PIXEL(x), CS2PIXEL(y), vx3, vy3);
|
|
enemies.make(&beam, CS2PIXEL(x), CS2PIXEL(y), vx4, vy4);
|
|
enemies.make(&beam, CS2PIXEL(x), CS2PIXEL(y), vx5, vy5);
|
|
enemies.make(&bombdeto, CS2PIXEL(x), CS2PIXEL(y), vx1, vy1);
|
|
sound.g_bomb_deto(x, y);
|
|
release();
|
|
}
|
|
if(--di < 1)
|
|
di = 16;
|
|
}
|
|
|
|
const enemy_kind bomb2 = {
|
|
20,
|
|
&_enemy::make_bomb,
|
|
&_enemy::move_bomb2,
|
|
&_enemy::kill_default,
|
|
5,
|
|
B_BOMB, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* explosionX
|
|
* Various explosions
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_expl()
|
|
{
|
|
health = 1;
|
|
damage = 0;
|
|
shootable = 0;
|
|
switch(bank)
|
|
{
|
|
case B_EXPLO1:
|
|
case B_BOMBDETO:
|
|
a = 8;
|
|
break;
|
|
case B_EXPLO3:
|
|
case B_EXPLO4:
|
|
case B_EXPLO5:
|
|
case B_ROCKEXPL:
|
|
a = 12;
|
|
break;
|
|
case B_RINGEXPL:
|
|
frame = 8 * pubrand.get(1);
|
|
a = 8;
|
|
break;
|
|
case B_BULLETEXPL:
|
|
a = 6;
|
|
break;
|
|
case B_BOLTEXPL:
|
|
frame = 6 * pubrand.get(1);
|
|
di = pubrand.get(2);
|
|
a = 6;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void _enemy::move_expl()
|
|
{
|
|
if(++di > a)
|
|
release();
|
|
}
|
|
|
|
const enemy_kind explosion = {
|
|
0,
|
|
&_enemy::make_expl,
|
|
&_enemy::move_expl,
|
|
&_enemy::kill_default,
|
|
-1,
|
|
B_EXPLO1, 0,
|
|
LAYER_FX
|
|
};
|
|
|
|
const enemy_kind explosion3 = {
|
|
0,
|
|
&_enemy::make_expl,
|
|
&_enemy::move_expl,
|
|
&_enemy::kill_default,
|
|
-1,
|
|
B_EXPLO3, 0,
|
|
LAYER_FX
|
|
};
|
|
|
|
const enemy_kind explosion4 = {
|
|
0,
|
|
&_enemy::make_expl,
|
|
&_enemy::move_expl,
|
|
&_enemy::kill_default,
|
|
-1,
|
|
B_EXPLO4, 0,
|
|
LAYER_FX
|
|
};
|
|
|
|
const enemy_kind explosion5 = {
|
|
0,
|
|
&_enemy::make_expl,
|
|
&_enemy::move_expl,
|
|
&_enemy::kill_default,
|
|
-1,
|
|
B_EXPLO5, 0,
|
|
LAYER_FX
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* ringexpl
|
|
* Ring dies in a flash
|
|
* ===========================================================================
|
|
*/
|
|
const enemy_kind ringexpl = {
|
|
0,
|
|
&_enemy::make_expl,
|
|
&_enemy::move_expl,
|
|
&_enemy::kill_default,
|
|
-1,
|
|
B_RINGEXPL, 0,
|
|
LAYER_FX
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* beamexpl
|
|
* Enemy "bullet" discharges
|
|
* ===========================================================================
|
|
*/
|
|
const enemy_kind beamexpl = {
|
|
0,
|
|
&_enemy::make_expl,
|
|
&_enemy::move_expl,
|
|
&_enemy::kill_default,
|
|
-1,
|
|
B_BULLETEXPL, 0,
|
|
LAYER_FX
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* boltexpl
|
|
* Player bolt discharges
|
|
* ===========================================================================
|
|
*/
|
|
const enemy_kind boltexpl = {
|
|
0,
|
|
&_enemy::make_expl,
|
|
&_enemy::move_expl,
|
|
&_enemy::kill_default,
|
|
-1,
|
|
B_BOLTEXPL, 0,
|
|
LAYER_FX
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* rockexpl
|
|
* Rock *finally* explodes
|
|
* ===========================================================================
|
|
*/
|
|
const enemy_kind rockexpl = {
|
|
0,
|
|
&_enemy::make_expl,
|
|
&_enemy::move_expl,
|
|
&_enemy::kill_default,
|
|
-1,
|
|
B_ROCKEXPL, 0,
|
|
LAYER_FX
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* bombdeto
|
|
* Bomb detonation (not really an explosion!)
|
|
* ===========================================================================
|
|
*/
|
|
const enemy_kind bombdeto = {
|
|
0,
|
|
&_enemy::make_expl,
|
|
&_enemy::move_expl,
|
|
&_enemy::kill_default,
|
|
-1,
|
|
B_BOMBDETO, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* cannon
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_cannon()
|
|
{
|
|
count = 0;
|
|
health = 20;
|
|
damage = 75;
|
|
b = enemies.eint1() - 1;
|
|
a = gamerand.get() & b;
|
|
}
|
|
|
|
void _enemy::move_cannon()
|
|
{
|
|
(count)++;
|
|
(count) &= (b);
|
|
if(count == a && norm < ((VIEWLIMIT >> 1) + 8))
|
|
{
|
|
int shift = (enemies.ek1() == &beam) ? 6 : 5;
|
|
this->shot_template(enemies.ek1(), shift, 32, 0);
|
|
}
|
|
}
|
|
|
|
void _enemy::kill_cannon()
|
|
{
|
|
enemies.make(&pipe1, CS2PIXEL(x), CS2PIXEL(y));
|
|
sound.g_base_node_explo(x, y);
|
|
release();
|
|
}
|
|
|
|
const enemy_kind cannon = {
|
|
10,
|
|
&_enemy::make_cannon,
|
|
&_enemy::move_cannon,
|
|
&_enemy::kill_cannon,
|
|
4,
|
|
-1, 0,
|
|
LAYER_BASES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* core
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_core()
|
|
{
|
|
count = 0;
|
|
health = 20;
|
|
damage = 150;
|
|
b = enemies.eint2() - 1;
|
|
a = gamerand.get() & b;
|
|
}
|
|
|
|
void _enemy::move_core()
|
|
{
|
|
(count)++;
|
|
(count) &= (b);
|
|
if(count == a && norm < ((VIEWLIMIT >> 1) + 8))
|
|
{
|
|
int shift = (enemies.ek2() == &beam) ? 6 : 5;
|
|
this->shot_template(enemies.ek2(), shift, 0, 0);
|
|
}
|
|
}
|
|
|
|
void _enemy::kill_core()
|
|
{
|
|
enemies.make(&pipe2k, CS2PIXEL(x), CS2PIXEL(y), 0, 0, 3);
|
|
enemies.make(&pipe2k, CS2PIXEL(x), CS2PIXEL(y), 0, 0, 7);
|
|
enemies.make(&pipe2k, CS2PIXEL(x), CS2PIXEL(y), 0, 0, 1);
|
|
enemies.make(&pipe2k, CS2PIXEL(x), CS2PIXEL(y), 0, 0, 5);
|
|
enemies.make(&explosion4, CS2PIXEL(x), CS2PIXEL(y));
|
|
sound.g_base_core_explo(x, y);
|
|
release();
|
|
manage.destroyed_a_core();
|
|
}
|
|
|
|
const enemy_kind core = {
|
|
200,
|
|
&_enemy::make_core,
|
|
&_enemy::move_core,
|
|
&_enemy::kill_core,
|
|
4,
|
|
-1, 0,
|
|
LAYER_BASES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* pipe1
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_pipe1()
|
|
{
|
|
health = 1;
|
|
damage = 0;
|
|
shootable = 0;
|
|
count = 4;
|
|
a = 0;
|
|
b = 0;
|
|
}
|
|
|
|
void _enemy::move_pipe1()
|
|
{
|
|
sound.g_pipe_rumble(x, y);
|
|
if((norm < ((VIEWLIMIT >> 1) + 32)) && (count == 1))
|
|
enemies.make(&explosion,
|
|
CS2PIXEL(x) + pubrand.get(4) - 8,
|
|
CS2PIXEL(y) + pubrand.get(4) - 8,
|
|
0, 0, 1);
|
|
|
|
if(++count < 4)
|
|
return;
|
|
count = 0;
|
|
|
|
int x1 = (CS2PIXEL(x) & (WORLD_SIZEX - 1)) >> 4;
|
|
int y1 = (CS2PIXEL(y) & (WORLD_SIZEY - 1)) >> 4;
|
|
int a_next = 0;
|
|
int x_next = 0;
|
|
int y_next = 0;
|
|
int m = screen.get_map(x1, y1);
|
|
int p = MAP_BITS(m);
|
|
if(IS_SPACE(p))
|
|
{
|
|
release();
|
|
return;
|
|
}
|
|
if(norm < ((VIEWLIMIT >> 1) + 32))
|
|
enemies.make(enemies.randexp(), CS2PIXEL(x), CS2PIXEL(y),
|
|
0, 0, b);
|
|
b = 1;
|
|
if((p ^ a) == U_MASK)
|
|
{
|
|
a_next = D_MASK;
|
|
y_next = -PIXEL2CS(16);
|
|
}
|
|
if((p ^ a) == R_MASK)
|
|
{
|
|
a_next = L_MASK;
|
|
x_next = PIXEL2CS(16);
|
|
}
|
|
if((p ^ a) == D_MASK)
|
|
{
|
|
a_next = U_MASK;
|
|
y_next = PIXEL2CS(16);
|
|
}
|
|
if((p ^ a) == L_MASK)
|
|
{
|
|
a_next = R_MASK;
|
|
x_next = -PIXEL2CS(16);
|
|
}
|
|
if(a_next)
|
|
{
|
|
screen.set_map(x1, y1, 0);
|
|
x += x_next;
|
|
y += y_next;
|
|
a = a_next;
|
|
return;
|
|
}
|
|
if(!(p & CORE))
|
|
screen.set_map(x1, y1, m ^ a);
|
|
release();
|
|
}
|
|
const enemy_kind pipe1 = {
|
|
0,
|
|
&_enemy::make_pipe1,
|
|
&_enemy::move_pipe1,
|
|
&_enemy::kill_default,
|
|
-1,
|
|
-1, 0,
|
|
LAYER_BASES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* pipe2
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_pipe2()
|
|
{
|
|
int x1 = (CS2PIXEL(x) & (WORLD_SIZEX - 1)) >> 4;
|
|
int y1 = (CS2PIXEL(y) & (WORLD_SIZEY - 1)) >> 4;
|
|
|
|
screen.set_map(x1, y1, 0);
|
|
health = 1;
|
|
damage = 0;
|
|
shootable = 0;
|
|
count = 4;
|
|
switch (di)
|
|
{
|
|
case 1:
|
|
a = D_MASK;
|
|
y -= PIXEL2CS(16);
|
|
break;
|
|
case 3:
|
|
a = L_MASK;
|
|
x += PIXEL2CS(16);
|
|
break;
|
|
case 5:
|
|
a = U_MASK;
|
|
y += PIXEL2CS(16);
|
|
break;
|
|
case 7:
|
|
a = R_MASK;
|
|
x -= PIXEL2CS(16);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void _enemy::move_pipe2()
|
|
{
|
|
if((norm < ((VIEWLIMIT >> 1) + 32)) && (count == 1))
|
|
enemies.make(&explosion,
|
|
CS2PIXEL(x) + pubrand.get(4) - 8,
|
|
CS2PIXEL(y) + pubrand.get(4) - 8,
|
|
0, 0, 1);
|
|
if(++count < 4)
|
|
{
|
|
sound.g_pipe_rumble(x, y);
|
|
return;
|
|
}
|
|
count = 0;
|
|
|
|
int x1 = (CS2PIXEL(x) & (WORLD_SIZEX - 1)) >> 4;
|
|
int y1 = (CS2PIXEL(y) & (WORLD_SIZEY - 1)) >> 4;
|
|
int a_next = 0;
|
|
int x_next = 0;
|
|
int y_next = 0;
|
|
int p = MAP_BITS(screen.get_map(x1, y1));
|
|
if(IS_SPACE(p))
|
|
{
|
|
release();
|
|
return;
|
|
}
|
|
if((p ^ a) == 0)
|
|
{
|
|
manage.add_score(30);
|
|
release();
|
|
enemies.erase_cannon(x1, y1);
|
|
screen.set_map(x1, y1, 0);
|
|
sound.g_base_node_explo(x, y);
|
|
if(norm < ((VIEWLIMIT >> 1) + 32))
|
|
enemies.make(enemies.randexp(), CS2PIXEL(x), CS2PIXEL(y));
|
|
return;
|
|
}
|
|
if((p ^ a) == HARD)
|
|
{
|
|
release();
|
|
screen.set_map(x1, y1, 0);
|
|
sound.g_pipe_rumble(x, y);
|
|
if(norm < ((VIEWLIMIT >> 1) + 32))
|
|
enemies.make(enemies.randexp(), CS2PIXEL(x), CS2PIXEL(y),
|
|
0, 0, 1);
|
|
return;
|
|
}
|
|
if((p ^ a) == U_MASK)
|
|
{
|
|
a_next = D_MASK;
|
|
y_next = -PIXEL2CS(16);
|
|
}
|
|
if((p ^ a) == R_MASK)
|
|
{
|
|
a_next = L_MASK;
|
|
x_next = PIXEL2CS(16);
|
|
}
|
|
if((p ^ a) == D_MASK)
|
|
{
|
|
a_next = U_MASK;
|
|
y_next = PIXEL2CS(16);
|
|
}
|
|
if((p ^ a) == L_MASK)
|
|
{
|
|
a_next = R_MASK;
|
|
x_next = -PIXEL2CS(16);
|
|
}
|
|
screen.set_map(x1, y1, 0);
|
|
sound.g_pipe_rumble(x, y);
|
|
if(norm < ((VIEWLIMIT >> 1) + 32))
|
|
enemies.make(enemies.randexp(), CS2PIXEL(x), CS2PIXEL(y),
|
|
0, 0, 1);
|
|
if(a_next)
|
|
{
|
|
x += x_next;
|
|
y += y_next;
|
|
a = a_next;
|
|
return;
|
|
}
|
|
p ^= a;
|
|
if(p & U_MASK)
|
|
enemies.make(&pipe2k, CS2PIXEL(x), CS2PIXEL(y), 0, 0, 1);
|
|
if(p & R_MASK)
|
|
enemies.make(&pipe2k, CS2PIXEL(x), CS2PIXEL(y), 0, 0, 3);
|
|
if(p & D_MASK)
|
|
enemies.make(&pipe2k, CS2PIXEL(x), CS2PIXEL(y), 0, 0, 5);
|
|
if(p & L_MASK)
|
|
enemies.make(&pipe2k, CS2PIXEL(x), CS2PIXEL(y), 0, 0, 7);
|
|
manage.add_score(10);
|
|
release();
|
|
}
|
|
|
|
|
|
const enemy_kind pipe2k = {
|
|
0,
|
|
&_enemy::make_pipe2,
|
|
&_enemy::move_pipe2,
|
|
&_enemy::kill_default,
|
|
-1,
|
|
-1, 0,
|
|
LAYER_BASES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* enemy1
|
|
* Gray Dumb Missile
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_enemy1()
|
|
{
|
|
di = 1;
|
|
health = 20;
|
|
}
|
|
|
|
void _enemy::move_enemy1()
|
|
{
|
|
this->move_enemy_template(2, 256);
|
|
}
|
|
const enemy_kind enemy1 = {
|
|
2,
|
|
&_enemy::make_enemy1,
|
|
&_enemy::move_enemy1,
|
|
&_enemy::kill_default,
|
|
6,
|
|
B_MISSILE1, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* enemy2
|
|
* Teal Firing Fighter
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_enemy2()
|
|
{
|
|
di = 1;
|
|
health = 20;
|
|
count = gamerand.get() & 63;
|
|
}
|
|
|
|
void _enemy::move_enemy2()
|
|
{
|
|
this->move_enemy_template(4, 192);
|
|
if(--(count) <= 0)
|
|
{
|
|
if(norm < ((VIEWLIMIT >> 1) + 8))
|
|
{
|
|
this->shot_template(&beam, 5, 0, 0);
|
|
}
|
|
count = 32;
|
|
}
|
|
}
|
|
const enemy_kind enemy2 = {
|
|
10,
|
|
&_enemy::make_enemy2,
|
|
&_enemy::move_enemy2,
|
|
&_enemy::kill_default,
|
|
6,
|
|
B_FIGHTER, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* enemy3
|
|
* Maroon Homing Missile
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_enemy3()
|
|
{
|
|
di = 1;
|
|
health = 20;
|
|
}
|
|
|
|
void _enemy::move_enemy3()
|
|
{
|
|
this->move_enemy_template(32, 96);
|
|
}
|
|
const enemy_kind enemy3 = {
|
|
1,
|
|
&_enemy::make_enemy3,
|
|
&_enemy::move_enemy3,
|
|
&_enemy::kill_default,
|
|
6,
|
|
B_MISSILE2, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* enemy4
|
|
* Blue Homing Missile
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_enemy4()
|
|
{
|
|
di = 1;
|
|
health = 20;
|
|
}
|
|
|
|
void _enemy::move_enemy4()
|
|
{
|
|
this->move_enemy_template(4, 96);
|
|
}
|
|
const enemy_kind enemy4 = {
|
|
1,
|
|
&_enemy::make_enemy4,
|
|
&_enemy::move_enemy4,
|
|
&_enemy::kill_default,
|
|
6,
|
|
B_MISSILE3, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* enemy5
|
|
* Green Boomerang Fighter
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_enemy()
|
|
{
|
|
count = gamerand.get() & 127;
|
|
di = 1;
|
|
health = 20;
|
|
a = 0;
|
|
}
|
|
|
|
void _enemy::move_enemy5()
|
|
{
|
|
if(a == 0)
|
|
{
|
|
if(norm > ((VIEWLIMIT >> 1) - 32))
|
|
this->move_enemy_template(6, 192);
|
|
else
|
|
a = 1;
|
|
}
|
|
else
|
|
{
|
|
if(norm < VIEWLIMIT)
|
|
this->move_enemy_template_2(4, 192);
|
|
else
|
|
a = 0;
|
|
}
|
|
if(enemies.is_intro)
|
|
{
|
|
if(gamerand.get(7) > 110)
|
|
hit(1);
|
|
}
|
|
else if((--count) <= 0)
|
|
{
|
|
count = 8;
|
|
if(norm > ((VIEWLIMIT >> 1) - 32))
|
|
this->shot_template(&beam, 6, 0, 0);
|
|
}
|
|
}
|
|
const enemy_kind enemy5 = {
|
|
5,
|
|
&_enemy::make_enemy,
|
|
&_enemy::move_enemy5,
|
|
&_enemy::kill_default,
|
|
6,
|
|
B_BMR_GREEN, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* enemy6
|
|
* Purple Boomerang Fighter
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::move_enemy6()
|
|
{
|
|
if(a == 0)
|
|
{
|
|
if(norm > ((VIEWLIMIT >> 1) - 0))
|
|
this->move_enemy_template(6, 192);
|
|
else
|
|
a = 1;
|
|
}
|
|
else
|
|
{
|
|
if(norm < VIEWLIMIT)
|
|
this->move_enemy_template_2(5, 192);
|
|
else
|
|
a = 0;
|
|
}
|
|
if((--count) <= 0)
|
|
{
|
|
count = 128;
|
|
if(norm > ((VIEWLIMIT >> 1) - 32))
|
|
this->shot_template(&beam, 6, 0, 0);
|
|
}
|
|
}
|
|
const enemy_kind enemy6 = {
|
|
2,
|
|
&_enemy::make_enemy,
|
|
&_enemy::move_enemy6,
|
|
&_enemy::kill_default,
|
|
6,
|
|
B_BMR_PURPLE, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* enemy7
|
|
* Pink Boomerang Fighter
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::move_enemy7()
|
|
{
|
|
if(a == 0)
|
|
{
|
|
if(norm > ((VIEWLIMIT >> 1) - 32))
|
|
this->move_enemy_template(6, 192);
|
|
else
|
|
a = 1;
|
|
}
|
|
else
|
|
{
|
|
if(norm < VIEWLIMIT)
|
|
this->move_enemy_template_3(4, 192);
|
|
else
|
|
a = 0;
|
|
}
|
|
if(enemies.is_intro)
|
|
{
|
|
if(gamerand.get(7) > 110)
|
|
hit(1);
|
|
}
|
|
else if((--count) <= 0)
|
|
{
|
|
count = 8;
|
|
if(norm > ((VIEWLIMIT >> 1) - 32))
|
|
this->shot_template(&beam, 6, 0, 0);
|
|
}
|
|
}
|
|
const enemy_kind enemy7 = {
|
|
5,
|
|
&_enemy::make_enemy,
|
|
&_enemy::move_enemy7,
|
|
&_enemy::kill_default,
|
|
6,
|
|
B_BMR_PINK, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
/*
|
|
* ===========================================================================
|
|
* enemy_m1
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_enemy_m1()
|
|
{
|
|
di = 1;
|
|
health = 20 * 26;
|
|
count = gamerand.get() & 15;
|
|
}
|
|
|
|
void _enemy::move_enemy_m1()
|
|
{
|
|
this->move_enemy_m(3, 128);
|
|
if(++di > 16)
|
|
di = 1;
|
|
if((count--) <= 0)
|
|
{
|
|
count = 4;
|
|
if(norm < ((VIEWLIMIT >> 1) - 16))
|
|
{
|
|
this->shot_template(&enemy1, 4, 0, 0);
|
|
}
|
|
}
|
|
if(health < 200)
|
|
{
|
|
this->shot_template_8_dir(&enemy2);
|
|
release();
|
|
}
|
|
}
|
|
const enemy_kind enemy_m1 = {
|
|
50,
|
|
&_enemy::make_enemy_m1,
|
|
&_enemy::move_enemy_m1,
|
|
&_enemy::kill_default,
|
|
12,
|
|
B_BIGSHIP, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* enemy_m2
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_enemy_m2()
|
|
{
|
|
di = 1;
|
|
health = 20 * 26;
|
|
count = gamerand.get() & 15;
|
|
}
|
|
|
|
void _enemy::move_enemy_m2()
|
|
{
|
|
this->move_enemy_m(3, 128);
|
|
if(++di > 16)
|
|
di = 1;
|
|
if((count--) <= 0)
|
|
{
|
|
count = 8;
|
|
if(norm < ((VIEWLIMIT >> 1) + 8))
|
|
{
|
|
this->shot_template(&enemy2, 4, 128, 192);
|
|
}
|
|
}
|
|
if(health < 200)
|
|
{
|
|
this->shot_template_8_dir(&bomb2);
|
|
release();
|
|
}
|
|
}
|
|
const enemy_kind enemy_m2 = {
|
|
50,
|
|
&_enemy::make_enemy_m2,
|
|
&_enemy::move_enemy_m2,
|
|
&_enemy::kill_default,
|
|
12,
|
|
B_BIGSHIP, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* enemy_m3
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_enemy_m3()
|
|
{
|
|
di = 1;
|
|
health = 20 * 26;
|
|
count = gamerand.get() & 15;
|
|
}
|
|
|
|
void _enemy::move_enemy_m3()
|
|
{
|
|
this->move_enemy_m(3, 128);
|
|
if(--di < 1)
|
|
di = 16;
|
|
if((count--) <= 0)
|
|
{
|
|
count = 64;
|
|
if(norm < ((VIEWLIMIT >> 1) + 8))
|
|
{
|
|
this->shot_template_8_dir(&bomb2);
|
|
}
|
|
}
|
|
if(health < 200)
|
|
{
|
|
this->shot_template_8_dir(&rock);
|
|
release();
|
|
}
|
|
}
|
|
const enemy_kind enemy_m3 = {
|
|
50,
|
|
&_enemy::make_enemy_m3,
|
|
&_enemy::move_enemy_m3,
|
|
&_enemy::kill_default,
|
|
12,
|
|
B_BIGSHIP, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* enemy_m4
|
|
* ===========================================================================
|
|
*/
|
|
void _enemy::make_enemy_m4()
|
|
{
|
|
di = 1;
|
|
health = 20 * 26;
|
|
count = gamerand.get() & 15;
|
|
}
|
|
|
|
void _enemy::move_enemy_m4()
|
|
{
|
|
this->move_enemy_m(2, 96);
|
|
if(--di < 1)
|
|
di = 16;
|
|
static const enemy_kind *shot[8] = {
|
|
&enemy1, &enemy2, &bomb2, &ring, &enemy1, &enemy2, &ring,
|
|
&enemy1
|
|
};
|
|
if((count--) <= 0)
|
|
{
|
|
count = 64;
|
|
if(norm < ((VIEWLIMIT >> 1) + 8))
|
|
{
|
|
this->shot_template_8_dir(shot[gamerand.get() & 7]);
|
|
}
|
|
}
|
|
if(health < 200)
|
|
{
|
|
this->shot_template_8_dir(&rock);
|
|
release();
|
|
}
|
|
}
|
|
const enemy_kind enemy_m4 = {
|
|
100,
|
|
&_enemy::make_enemy_m4,
|
|
&_enemy::move_enemy_m4,
|
|
&_enemy::kill_default,
|
|
12,
|
|
B_BIGSHIP, 0,
|
|
LAYER_ENEMIES
|
|
};
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
/* void _enemy::make_xxxx(){}
|
|
* void _enemy::move_xxxx(){}
|
|
* void _enemy::die_xxxx(){}
|
|
* enemy_kind xxxxx = {
|
|
* score,
|
|
* make_xxxx, move_xxxx, die_xxx,
|
|
* hitsize,
|
|
* bank, frame, tilesize
|
|
* };
|
|
*/
|