kobodl/myship.cpp
Ville Lindholm 869056c5d2
Phase 0: profiling scaffolding for DOD refactor
Adds a tiny zone-based profiler (prof.{h,cpp}) enabled via KOBO_PROF=1.
Instruments run_game with zones for myship.move, enemies.move (split into
realize and move_pass), hit_structure, and hit_bolt. Adds KOBO_STRESS=N
to keep the enemy pool topped up with rocks for stable per-frame timing.

Why: the entity/render pipeline refactor in BENCH.md needs a comparable
baseline before any layout changes. Without a stress workload the natural
gameplay enemy count is too noisy to compare across commits.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-29 13:31:31 +03:00

578 lines
11 KiB
C++

/*
------------------------------------------------------------
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 "kobo.h"
#include "screen.h"
#include "myship.h"
#include "enemies.h"
#include "gamectl.h"
#include "manage.h"
#include "random.h"
#include "sound.h"
#include "prof.h"
#define WING_GUN_OFFSET 4
// Unit-vector tables indexed by direction (1=N, 2=NE, ..., 8=NW). Index 0 unused.
// Odd directions are cardinals; even directions are diagonals.
static const int dir_dx[9] = { 0, 0, +1, +1, +1, 0, -1, -1, -1 };
static const int dir_dy[9] = { 0, -1, -1, 0, +1, +1, +1, 0, -1 };
_myship_state _myship::_state;
int _myship::di;
int _myship::virtx;
int _myship::virty;
int _myship::x;
int _myship::y;
int _myship::_health;
int _myship::health_time;
int _myship::explo_time;
int _myship::nose_reload_timer;
int _myship::nose_temperature;
int _myship::nose_alt = 0;
int _myship::tail_reload_timer;
int _myship::tail_temperature;
int _myship::tail_alt = WING_GUN_OFFSET;
int _myship::lapx;
int _myship::lapy;
int _myship::boltx[MAX_BOLTS];
int _myship::bolty[MAX_BOLTS];
int _myship::boltdx[MAX_BOLTS];
int _myship::boltdy[MAX_BOLTS];
int _myship::boltst[MAX_BOLTS];
cs_obj_t *_myship::object;
cs_obj_t *_myship::bolt_objects[MAX_BOLTS];
cs_obj_t *_myship::crosshair;
_myship::_myship()
{
object = NULL;
memset(bolt_objects, 0, sizeof(bolt_objects));
crosshair = NULL;
}
void _myship::state(_myship_state s)
{
switch (s)
{
case dead:
if(object)
gengine->free_obj(object);
object = NULL;
if(crosshair)
gengine->free_obj(crosshair);
crosshair = NULL;
break;
case normal:
if(!object)
object = gengine->get_obj(LAYER_PLAYER);
if(object)
cs_obj_show(object);
break;
}
_state = s;
}
void _myship::free_bolt(int i)
{
boltst[i] = 0;
if(bolt_objects[i])
{
gengine->free_obj(bolt_objects[i]);
bolt_objects[i] = NULL;
}
}
void _myship::off()
{
state(dead);
for(int i = 0; i < MAX_BOLTS; i++)
free_bolt(i);
}
int _myship::init()
{
nose_reload_timer = 0;
nose_temperature = 0;
tail_reload_timer = 0;
tail_temperature = 0;
x = WORLD_SIZEX >> 1;
y = (WORLD_SIZEY >> 2) * 3;
virtx = x - (WSIZE >> 1);
virty = y - (WSIZE >> 1);
lapx = 0;
lapy = 0;
di = 1;
state(normal);
apply_position();
for(int i = 0; i < MAX_BOLTS; i++)
{
boltx[i] = 0;
bolty[i] = 0;
boltdx[i] = 0;
boltdy[i] = 0;
free_bolt(i);
}
return 0;
}
void _myship::explode(int x, int y)
{
if(explo_time == 56)
manage.noise_out(1000);
else if(explo_time > 64)
return;
int d = 4096 - (64 - explo_time)*(64 - explo_time);
int i;
for(i = 0; i < 2; ++i)
{
int dx = (int)(pubrand.get(6)) - 32;
int dy = (int)(pubrand.get(6)) - 32;
int vx = dx * (4096 - d) >> 8;
int vy = dy * (4096 - d) >> 8;
dx = dx * d >> 12;
dy = dy * d >> 12;
enemies.make(enemies.randexp(), x + dx, y + dy, vx, vy,
explo_time >> 4);
}
++explo_time;
}
#define BEAMV1 12
#define BEAMV2 (BEAMV1*2/3)
int _myship::move()
{
int i;
di = gamecontrol.dir();
if(++health_time >= game.health_fade)
{
health_time = 0;
if(_health > game.health)
--_health;
}
virtx = x - (WSIZE >> 1);
virty = y - (WSIZE >> 1);
if(_state == normal)
{
int vd, vo;
if(!prefs->cmd_pushmove)
{
vd = 2;
vo = 3;
}
else if(gamecontrol.dir_push())
{
vd = 1;
vo = 1;
}
else
{
vd = 0;
vo = 0;
}
int spd = (di & 1) ? vo : vd;
virtx += dir_dx[di] * spd;
virty += dir_dy[di] * spd;
explo_time = 0;
}
else if(_state == dead)
explode(x, y);
lapx = 0;
lapy = 0;
if(virtx < 0)
{
virtx += WORLD_SIZEX;
lapx = WORLD_SIZEX;
}
if(virtx >= WORLD_SIZEX)
{
virtx -= WORLD_SIZEX;
lapx = -WORLD_SIZEX;
}
if(virty < 0)
{
virty += WORLD_SIZEY;
lapy = WORLD_SIZEY;
}
if(virty >= WORLD_SIZEY)
{
virty -= WORLD_SIZEY;
lapy = -WORLD_SIZEY;
}
x = virtx + (WSIZE >> 1);
y = virty + (WSIZE >> 1);
nose_temperature -= game.nosecooling;
if(nose_temperature < 0)
nose_temperature = 0;
tail_temperature -= game.tailcooling;
if(tail_temperature < 0)
tail_temperature = 0;
if((_state == normal) && gamecontrol.get_shot())
{
if(game.skill == SKILL_CLASSIC)
{
if(nose_reload_timer > 0)
--nose_reload_timer;
else
{
_myship::xkobo_shot();
nose_reload_timer = game.noseloadtime - 1;
}
}
else
{
int fired = 0;
if(tail_reload_timer > 0)
--tail_reload_timer;
else if(!_myship::tail_fire())
fired = 1; // Ok!
else
fired = 2; // Overheat!
if(nose_reload_timer > 0)
--nose_reload_timer;
else if(!_myship::nose_fire())
fired = 1; // Ok!
else
fired = 2; // Overheat!
if(fired == 1)
sound.g_player_fire();
else if(fired == 2)
sound.g_player_overheat();
}
}
else
{
if(!gamecontrol.get_shot())
{
nose_alt = 1;
sound.g_player_fire_off();
}
if(nose_reload_timer > 0)
--nose_reload_timer;
if(tail_reload_timer > 0)
--tail_reload_timer;
}
const char animtab[8] = { 3, 2, 1, 0, 1, 2, 1, 2 };
for(i = 0; i < MAX_BOLTS; i++)
{
if(!boltst[i])
continue;
++boltst[i];
boltx[i] += lapx;
bolty[i] += lapy;
boltx[i] += boltdx[i];
bolty[i] += boltdy[i];
if((ABS(boltx[i] - x) >= (VIEWLIMIT >> 1) + 16) ||
(ABS(bolty[i] - y) >= (VIEWLIMIT >> 1) + 16))
free_bolt(i);
else if(bolt_objects[i])
cs_obj_image(bolt_objects[i], B_BOLT,
(bolt_objects[i]->anim.frame & 0xfffffffc) +
animtab[boltst[i] & 7]);
}
return 0;
}
void _myship::hit(int dmg)
{
if(_state != normal)
return;
if(!dmg)
dmg = 1000;
if(_health && (dmg < _health))
manage.noise_damage((float)dmg / _health);
else
manage.noise_damage(1.0f);
_health -= dmg;
if(_health > 0)
sound.g_player_damage();
else
{
_health = 0;
manage.lost_myship();
state(dead);
sound.g_player_explo_start();
}
}
void _myship::health_bonus(int h)
{
if(game.skill == SKILL_CLASSIC)
return;
_health += game.health * h / 100;
health_time = 0;
if(_health > game.health * 2)
_health = game.health * 2;
}
int _myship::hit_structure()
{
int x1, y1;
int i, ch;
// Check bolts/objects
for(i = 0; i < MAX_BOLTS; i++)
{
if(!boltst[i])
continue;
x1 = (boltx[i] & (WORLD_SIZEX - 1)) >> 4;
y1 = (bolty[i] & (WORLD_SIZEY - 1)) >> 4;
ch = screen.get_map(x1, y1);
if(!IS_SPACE(ch) && (ch & HIT_MASK))
{
sound.g_bolt_hit(x1<<12, y1<<12);
enemies.make(&boltexpl, boltx[i], bolty[i]);
free_bolt(i);
}
}
// Check player/bases
x1 = (x & (WORLD_SIZEX - 1)) >> 4;
y1 = (y & (WORLD_SIZEY - 1)) >> 4;
ch = screen.get_map(x1, y1);
if(!IS_SPACE(ch) && (ch & HIT_MASK))
{
if(prefs->cmd_indicator)
sound.g_player_damage();
else
_myship::hit(1000);
}
return 0;
}
int _myship::hit_bolt(int ex, int ey, int hitsize, int health)
{
PROF_ZONE("hit_bolt");
int dmg = 0;
int i;
for(i = 0; i < MAX_BOLTS; i++)
{
if(boltst[i] == 0)
continue;
if(ABS(ex - boltx[i]) >= hitsize)
continue;
if(ABS(ey - bolty[i]) >= hitsize)
continue;
if(!prefs->cmd_cheat)
free_bolt(i);
enemies.make(&boltexpl, boltx[i], bolty[i]);
dmg += game.bolt_damage;
if(dmg >= health)
break;
}
return dmg;
}
void _myship::put_crosshair()
{
if(_state != normal)
return;
if(!crosshair)
{
crosshair = gengine->get_obj(LAYER_OVERLAY);
if(crosshair)
cs_obj_show(crosshair);
}
if(crosshair)
{
cs_obj_image(crosshair, B_CROSSHAIR, 0);
crosshair->point.v.x = PIXEL2CS(mouse_x - 8 - MARGIN);
crosshair->point.v.y = PIXEL2CS(mouse_y - MARGIN);
}
}
int _myship::put()
{
/* Player */
apply_position();
if(object)
cs_obj_image(object, B_PLAYER, (di - 1) * 2);
/* Bullets */
int i;
for(i = 0; i < MAX_BOLTS; i++)
{
if(!bolt_objects[i])
continue;
if(boltst[i])
{
bolt_objects[i]->point.v.x = PIXEL2CS(boltx[i]);
bolt_objects[i]->point.v.y = PIXEL2CS(bolty[i]);
}
}
return 0;
}
void _myship::shot_single(int i, int dir, int offset)
{
int doffset = (offset * 7071 + (offset > 0 ? 5000 : -5000)) / 10000;
boltst[i] = 1;
boltx[i] = x;
bolty[i] = y;
// Wing-gun position offset is perpendicular to the firing direction.
int pd = (dir + 1) % 8 + 1;
int poff = (pd & 1) ? offset : doffset;
boltx[i] += dir_dx[pd] * poff;
bolty[i] += dir_dy[pd] * poff;
int v = (dir & 1) ? BEAMV1 : BEAMV2;
boltdx[i] = dir_dx[dir] * v;
boltdy[i] = dir_dy[dir] * v;
if(!bolt_objects[i])
bolt_objects[i] = gengine->get_obj(LAYER_PLAYER);
if(bolt_objects[i])
{
bolt_objects[i]->point.v.x = PIXEL2CS(boltx[i]);
bolt_objects[i]->point.v.y = PIXEL2CS(bolty[i]);
cs_obj_image(bolt_objects[i], B_BOLT, (((dir - 1) & 0x3) << 2) + 3);
cs_obj_show(bolt_objects[i]);
}
}
int _myship::xkobo_shot()
{
int i, j;
for(i = 0; i < game.bolts && boltst[i]; i++)
;
for(j = i + 1; j < game.bolts && boltst[j]; j++)
;
if(j >= game.bolts)
{
sound.g_player_overheat(1);
return 1;
}
shot_single(i, di, 0);
shot_single(j, (di > 4) ? (di - 4) : (di + 4), 0);
sound.g_player_fire();
return 0;
}
int _myship::nose_fire()
{
int i, j;
// Logic: If firing would cause overheat, wait!
if(255 - nose_temperature < game.noseheatup)
return 1;
for(i = 0; i < game.bolts && boltst[i]; i++)
;
if(!game.altfire)
{
if(i >= game.bolts)
return 1;
shot_single(i, di, 0);
nose_temperature += game.noseheatup;
nose_reload_timer = game.noseloadtime;
return 0;
}
for(j = i + 1; j < game.bolts && boltst[j]; j++)
;
if(j >= game.bolts)
return 1;
if(++nose_alt > 2)
nose_alt = 0;
shot_single(i, di, (nose_alt - 1) * WING_GUN_OFFSET);
if(++nose_alt > 2)
nose_alt = 0;
shot_single(j, di, (nose_alt - 1) * WING_GUN_OFFSET);
nose_temperature += game.noseheatup;
nose_reload_timer = game.noseloadtime;
return 0;
}
int _myship::tail_fire()
{
int i;
if(255 - tail_temperature < game.tailheatup)
return 1;
for(i = 0; i < game.bolts && boltst[i]; i++)
;
if(i >= game.bolts)
return 1;
if(game.altfire)
{
tail_alt = -tail_alt;
shot_single(i, (di > 4) ? (di - 4) : (di + 4), tail_alt);
}
else
shot_single(i, (di > 4) ? (di - 4) : (di + 4), 0);
tail_temperature += game.tailheatup;
tail_reload_timer = game.tailloadtime;
return 0;
}
void _myship::set_position(int px, int py)
{
x = px;
y = py;
virtx = x - (WSIZE >> 1);
virty = y - (WSIZE >> 1);
if(object)
{
apply_position();
cs_point_force(&object->point);
}
}
void _myship::apply_position()
{
if(object)
{
object->point.v.x = PIXEL2CS(x);
object->point.v.y = PIXEL2CS(y);
}
sound.g_position(x, y);
}