feat: Add Spear of Destiny demo support with dedicated asset registry and entity definitions

- Introduced SpearDemoAssetRegistry for managing assets specific to the Spear of Destiny demo.
- Created SpearDemoEntityModule to define enemy animations with adjusted sprite ranges.
- Implemented SpearDemoHudModule and SpearDemoMenuPicModule for HUD and menu assets.
- Added SpearDemoSfxModule for sound effect mappings specific to the demo version.
- Updated enemy classes (Guard, Mutant, Officer, SS) to support custom animation sets.
- Modified entity registry to accept a custom AssetRegistry for spawning entities.
- Enhanced rendering with CRT phosphor bloom effect in GLSL shaders.
- Adjusted ASCII and software renderer layouts for improved UI spacing.
- Added tests for SpearDemoAssetRegistry to ensure correct asset resolution and enemy spawning.

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-23 10:37:50 +01:00
parent 528d6276b1
commit a84c677845
28 changed files with 641 additions and 70 deletions
@@ -649,8 +649,8 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
);
final selectedMarker = art.selectedMarker;
final unselectedMarker = art.unselectedMarker;
const int rowYStart = 66;
const int rowStep = 18;
const int rowYStart = 64;
const int rowStep = 16;
const int cursorX = 62;
const int markerX = 92;
const int textX = 122;
@@ -664,11 +664,11 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
? 0
: ((modeCount - 1) * rowStep) + 12;
final int modesPanelHeight = math.max(56, modesContentHeight + 14);
final int sectionHeaderY = modesPanelY + modesPanelHeight + 6;
final int sectionHeaderY = modesPanelY + modesPanelHeight + 4;
final int optionsPanelY = sectionHeaderY + 14;
final int optionsContentHeight = optionCount <= 0
? 0
: ((optionCount - 1) * 15) + 12;
: ((optionCount - 1) * 14) + 10;
final int optionsPanelHeight = math.max(30, optionsContentHeight + 10);
_fillRect320(46, modesPanelY, 228, modesPanelHeight, panelColor);
@@ -706,7 +706,7 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
y200: sectionHeaderY,
);
const int optionsRowStep = 15;
const int optionsRowStep = 14;
final int optionsRowsHeight = optionCount <= 0
? 0
: ((optionCount - 1) * optionsRowStep) + 10;
@@ -450,26 +450,8 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
const int modesPanelX = 46;
const int modesPanelY = 52;
const int modesPanelW = 228;
const int modesPanelH = 74;
_fillCanonicalRect(
modesPanelX,
modesPanelY,
modesPanelW,
modesPanelH,
panelColor,
);
const int optionsPanelX = 46;
const int optionsPanelY = 146;
const int optionsPanelW = 228;
const int optionsPanelH = 42;
_fillCanonicalRect(
optionsPanelX,
optionsPanelY,
optionsPanelW,
optionsPanelH,
panelColor,
);
final VgaImage? heading = art.customizeLabel ?? art.optionsLabel;
if (heading != null) {
@@ -490,8 +472,8 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
engine.menuManager.isCursorAltFrame(engine.timeAliveMs) ? 9 : 8,
);
const int rowYStart = 66;
const int rowStep = 18;
const int rowYStart = 64;
const int rowStep = 16;
const int cursorX = 62;
const int markerX = 92;
const int textX = 122;
@@ -500,6 +482,38 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
final optionEntries = engine.menuManager.rendererOptionEntries;
final int modeCount = entries.length;
final int selectedIndex = engine.menuManager.selectedChangeViewIndex;
final int modesContentHeight = entries.isEmpty
? 0
: ((entries.length - 1) * rowStep) + 12;
final int modesPanelH = math.max(56, modesContentHeight + 14);
final int sectionHeaderY = modesPanelY + modesPanelH + 4;
final int optionsPanelY = sectionHeaderY + 14;
const int optionsRowStep = 14;
final int optionsContentHeight = optionEntries.isEmpty
? 0
: ((optionEntries.length - 1) * optionsRowStep) + 10;
final int optionsPanelH = math.max(
30,
math.min(
_menuFooterY - 6 - optionsPanelY,
optionsContentHeight + 10,
),
);
_fillCanonicalRect(
modesPanelX,
modesPanelY,
modesPanelW,
modesPanelH,
panelColor,
);
_fillCanonicalRect(
optionsPanelX,
optionsPanelY,
optionsPanelW,
optionsPanelH,
panelColor,
);
for (int i = 0; i < entries.length; i++) {
final bool isSelected = i == selectedIndex;
@@ -528,12 +542,15 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
_drawMenuSectionHeader(
text: engine.menuManager.rendererOptionsTitle,
y200: 132,
y200: sectionHeaderY,
textColor: ColorPalette.vga32Bit[8],
);
const int optionsRowStart = 159;
const int optionsRowStep = 15;
final int optionsRowsHeight = optionEntries.isEmpty
? 0
: ((optionEntries.length - 1) * optionsRowStep) + 10;
final int optionsRowStart =
optionsPanelY + ((optionsPanelH - optionsRowsHeight) ~/ 2).clamp(0, 200);
for (int i = 0; i < optionEntries.length; i++) {
final int optionIndex = modeCount + i;
final bool isSelected = optionIndex == selectedIndex;