feat: Enhance intro splash screen with retail warning and dynamic slide management
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -184,6 +184,7 @@ class WolfEngine {
|
||||
initialEpisodeIndex: _currentEpisodeIndex,
|
||||
initialDifficulty: difficulty,
|
||||
hasResumableGame: false,
|
||||
initialGameIsRetail: data.version == GameVersion.retail,
|
||||
);
|
||||
|
||||
if (_availableGames.length == 1) {
|
||||
@@ -373,7 +374,9 @@ class WolfEngine {
|
||||
_currentEpisodeIndex = 0;
|
||||
onEpisodeSelected?.call(null);
|
||||
menuManager.clearEpisodeSelection();
|
||||
menuManager.beginIntroSplash();
|
||||
menuManager.beginIntroSplash(
|
||||
includeRetailWarning: data.version == GameVersion.retail,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ enum WolfMenuScreen {
|
||||
difficultySelect,
|
||||
}
|
||||
|
||||
enum WolfIntroSlide { retailWarning, pg13, title }
|
||||
|
||||
enum _WolfIntroPhase { fadeIn, hold, fadeOut }
|
||||
|
||||
enum WolfMenuMainAction {
|
||||
@@ -61,7 +63,7 @@ bool _isWiredMainMenuAction(WolfMenuMainAction action) {
|
||||
class MenuManager {
|
||||
static const int transitionDurationMs = 280;
|
||||
static const int introFadeDurationMs = 280;
|
||||
static const int _introSlideCount = 2;
|
||||
static const int _introRetailBackgroundRgb = 0xA00000;
|
||||
static const int _introPg13BackgroundRgb = 0x33A2E8;
|
||||
static const int _introTitleBackgroundRgb = 0x000000;
|
||||
|
||||
@@ -74,6 +76,10 @@ class MenuManager {
|
||||
int _introElapsedMs = 0;
|
||||
_WolfIntroPhase _introPhase = _WolfIntroPhase.fadeIn;
|
||||
bool _introAdvanceRequested = false;
|
||||
List<WolfIntroSlide> _introSlides = <WolfIntroSlide>[
|
||||
WolfIntroSlide.pg13,
|
||||
WolfIntroSlide.title,
|
||||
];
|
||||
|
||||
int _selectedMainIndex = 0;
|
||||
int _selectedGameIndex = 0;
|
||||
@@ -93,10 +99,31 @@ class MenuManager {
|
||||
|
||||
bool get isIntroSplashActive => _activeMenu == WolfMenuScreen.introSplash;
|
||||
|
||||
bool get isIntroPg13Slide => _introSlideIndex == 0;
|
||||
WolfIntroSlide get currentIntroSlide {
|
||||
if (_introSlides.isEmpty) {
|
||||
return WolfIntroSlide.title;
|
||||
}
|
||||
final int index = _introSlideIndex.clamp(0, _introSlides.length - 1);
|
||||
return _introSlides[index];
|
||||
}
|
||||
|
||||
int get introBackgroundRgb =>
|
||||
isIntroPg13Slide ? _introPg13BackgroundRgb : _introTitleBackgroundRgb;
|
||||
bool get isIntroRetailWarningSlide =>
|
||||
currentIntroSlide == WolfIntroSlide.retailWarning;
|
||||
|
||||
bool get isIntroPg13Slide => currentIntroSlide == WolfIntroSlide.pg13;
|
||||
|
||||
bool get isIntroTitleSlide => currentIntroSlide == WolfIntroSlide.title;
|
||||
|
||||
int get introBackgroundRgb {
|
||||
switch (currentIntroSlide) {
|
||||
case WolfIntroSlide.retailWarning:
|
||||
return _introRetailBackgroundRgb;
|
||||
case WolfIntroSlide.pg13:
|
||||
return _introPg13BackgroundRgb;
|
||||
case WolfIntroSlide.title:
|
||||
return _introTitleBackgroundRgb;
|
||||
}
|
||||
}
|
||||
|
||||
double get introOverlayAlpha {
|
||||
if (!isIntroSplashActive) {
|
||||
@@ -189,6 +216,7 @@ class MenuManager {
|
||||
int initialEpisodeIndex = 0,
|
||||
Difficulty? initialDifficulty,
|
||||
bool hasResumableGame = false,
|
||||
bool initialGameIsRetail = false,
|
||||
}) {
|
||||
_gameCount = gameCount;
|
||||
_showResumeOption = hasResumableGame;
|
||||
@@ -206,8 +234,12 @@ class MenuManager {
|
||||
_introElapsedMs = 0;
|
||||
_introPhase = _WolfIntroPhase.fadeIn;
|
||||
_introSlideIndex = 0;
|
||||
_introSlides = <WolfIntroSlide>[
|
||||
WolfIntroSlide.pg13,
|
||||
WolfIntroSlide.title,
|
||||
];
|
||||
} else {
|
||||
_startIntroSequence();
|
||||
_startIntroSequence(includeRetailWarning: initialGameIsRetail);
|
||||
}
|
||||
_transitionTarget = null;
|
||||
_transitionElapsedMs = 0;
|
||||
@@ -218,12 +250,13 @@ class MenuManager {
|
||||
/// Starts the intro splash sequence and lands on [landingMenu] when done.
|
||||
void beginIntroSplash({
|
||||
WolfMenuScreen landingMenu = WolfMenuScreen.mainMenu,
|
||||
bool includeRetailWarning = false,
|
||||
}) {
|
||||
_introLandingMenu = landingMenu;
|
||||
_transitionTarget = null;
|
||||
_transitionElapsedMs = 0;
|
||||
_transitionSwappedMenu = false;
|
||||
_startIntroSequence();
|
||||
_startIntroSequence(includeRetailWarning: includeRetailWarning);
|
||||
_resetEdgeState();
|
||||
}
|
||||
|
||||
@@ -312,8 +345,15 @@ class MenuManager {
|
||||
_consumeEdgeState(input);
|
||||
}
|
||||
|
||||
void _startIntroSequence() {
|
||||
void _startIntroSequence({required bool includeRetailWarning}) {
|
||||
_activeMenu = WolfMenuScreen.introSplash;
|
||||
_introSlides = includeRetailWarning
|
||||
? <WolfIntroSlide>[
|
||||
WolfIntroSlide.retailWarning,
|
||||
WolfIntroSlide.pg13,
|
||||
WolfIntroSlide.title,
|
||||
]
|
||||
: <WolfIntroSlide>[WolfIntroSlide.pg13, WolfIntroSlide.title];
|
||||
_introSlideIndex = 0;
|
||||
_introElapsedMs = 0;
|
||||
_introPhase = _WolfIntroPhase.fadeIn;
|
||||
@@ -356,7 +396,7 @@ class MenuManager {
|
||||
}
|
||||
|
||||
void _advanceIntroSlide() {
|
||||
if (_introSlideIndex < _introSlideCount - 1) {
|
||||
if (_introSlideIndex < _introSlides.length - 1) {
|
||||
_introSlideIndex += 1;
|
||||
_introElapsedMs = 0;
|
||||
_introPhase = _WolfIntroPhase.fadeIn;
|
||||
|
||||
@@ -420,7 +420,7 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
final art = WolfClassicMenuArt(engine.data);
|
||||
|
||||
if (engine.menuManager.activeMenu == WolfMenuScreen.introSplash) {
|
||||
_drawIntroSplash(engine, art);
|
||||
_drawIntroSplash(engine, art, menuTypography);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -708,10 +708,16 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
}
|
||||
}
|
||||
|
||||
void _drawIntroSplash(WolfEngine engine, WolfClassicMenuArt art) {
|
||||
final image = engine.menuManager.isIntroPg13Slide
|
||||
? art.mappedPic(WolfMenuPic.pg13)
|
||||
: art.mappedPic(WolfMenuPic.title);
|
||||
void _drawIntroSplash(
|
||||
WolfEngine engine,
|
||||
WolfClassicMenuArt art,
|
||||
_AsciiMenuTypography menuTypography,
|
||||
) {
|
||||
final image = switch (engine.menuManager.currentIntroSlide) {
|
||||
WolfIntroSlide.retailWarning => null,
|
||||
WolfIntroSlide.pg13 => art.mappedPic(WolfMenuPic.pg13),
|
||||
WolfIntroSlide.title => art.mappedPic(WolfMenuPic.title),
|
||||
};
|
||||
|
||||
int splashBg = _rgbToPaletteColor(engine.menuManager.introBackgroundRgb);
|
||||
if (engine.menuManager.isIntroPg13Slide &&
|
||||
@@ -726,6 +732,10 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
_fillRect(0, 0, width, height, activeTheme.solid, splashBg);
|
||||
}
|
||||
|
||||
if (engine.menuManager.isIntroRetailWarningSlide) {
|
||||
_drawRetailWarningIntro(splashBg, menuTypography);
|
||||
}
|
||||
|
||||
if (image != null) {
|
||||
final int x = engine.menuManager.isIntroPg13Slide
|
||||
? (320 - image.width).clamp(0, 319)
|
||||
@@ -742,6 +752,58 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
);
|
||||
}
|
||||
|
||||
void _drawRetailWarningIntro(
|
||||
int backgroundColor,
|
||||
_AsciiMenuTypography menuTypography,
|
||||
) {
|
||||
final int black = ColorPalette.vga32Bit[0];
|
||||
final int yellow = ColorPalette.vga32Bit[14];
|
||||
final int white = ColorPalette.vga32Bit[15];
|
||||
final int lineColor = ColorPalette.vga32Bit[4];
|
||||
|
||||
_fillRect320(0, 0, 320, 22, black);
|
||||
_drawMenuTextCentered(
|
||||
'Attention',
|
||||
6,
|
||||
yellow,
|
||||
scale: menuTypography.headingScale,
|
||||
);
|
||||
_fillRect320(0, 23, 320, 1, lineColor);
|
||||
|
||||
if (menuTypography.usesCompactRows) {
|
||||
final int textLeft = _menuX320ToColumn(40);
|
||||
final int textRight = _menuX320ToColumn(280);
|
||||
final int textWidth = math.max(1, textRight - textLeft);
|
||||
|
||||
void writeCenteredLine(int y200, String text) {
|
||||
final String clipped = _clipWithEllipsis(text, textWidth);
|
||||
final int centeredLeft =
|
||||
textLeft + math.max(0, ((textWidth - clipped.length) ~/ 2));
|
||||
_writeLeftClipped(
|
||||
_menuY200ToRow(y200),
|
||||
clipped,
|
||||
white,
|
||||
backgroundColor,
|
||||
clipped.length,
|
||||
centeredLeft,
|
||||
);
|
||||
}
|
||||
|
||||
writeCenteredLine(62, 'THIS GAME IS NOT SHAREWARE.');
|
||||
writeCenteredLine(74, 'PLEASE DO NOT DISTRIBUTE IT.');
|
||||
writeCenteredLine(86, 'THANKS.');
|
||||
|
||||
writeCenteredLine(112, 'ID SOFTWARE');
|
||||
} else {
|
||||
_drawMenuText('This game is NOT shareware.', 40, 56, white, scale: 1);
|
||||
_drawMenuText('Please do not distribute it.', 40, 68, white, scale: 1);
|
||||
_drawMenuText('Thanks.', 40, 80, white, scale: 1);
|
||||
_drawMenuTextCentered('Id Software', 106, white, scale: 1);
|
||||
}
|
||||
|
||||
_fillRect320(0, 196, 320, 4, backgroundColor);
|
||||
}
|
||||
|
||||
int _dominantPaletteIndex(VgaImage image) {
|
||||
final List<int> histogram = List<int>.filled(256, 0);
|
||||
for (final int colorIndex in image.pixels) {
|
||||
@@ -767,6 +829,18 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
}
|
||||
|
||||
final int threshold = (alpha * 3).round().clamp(1, 3);
|
||||
|
||||
if (_usesTerminalLayout) {
|
||||
for (int y = 0; y < _terminalPixelHeight; y++) {
|
||||
for (int x = 0; x < _terminalSceneWidth; x++) {
|
||||
if (((x + y) % 3) < threshold) {
|
||||
_scenePixels[y][x] = fadeColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int y = 0; y < _screen.length; y++) {
|
||||
final row = _screen[y];
|
||||
for (int x = 0; x < row.length; x++) {
|
||||
|
||||
@@ -561,9 +561,11 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
}
|
||||
|
||||
void _drawIntroSplash(WolfEngine engine, WolfClassicMenuArt art) {
|
||||
final image = engine.menuManager.isIntroPg13Slide
|
||||
? art.mappedPic(WolfMenuPic.pg13)
|
||||
: art.mappedPic(WolfMenuPic.title);
|
||||
final image = switch (engine.menuManager.currentIntroSlide) {
|
||||
WolfIntroSlide.retailWarning => null,
|
||||
WolfIntroSlide.pg13 => art.mappedPic(WolfMenuPic.pg13),
|
||||
WolfIntroSlide.title => art.mappedPic(WolfMenuPic.title),
|
||||
};
|
||||
|
||||
int splashBg = _rgbToPaletteIndex(engine.menuManager.introBackgroundRgb);
|
||||
if (engine.menuManager.isIntroPg13Slide &&
|
||||
@@ -576,6 +578,10 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
_screen[i] = splashBg;
|
||||
}
|
||||
|
||||
if (engine.menuManager.isIntroRetailWarningSlide) {
|
||||
_drawRetailWarningIntro(splashBg);
|
||||
}
|
||||
|
||||
if (image != null) {
|
||||
final int x = engine.menuManager.isIntroPg13Slide
|
||||
? (320 - image.width).clamp(0, 319)
|
||||
@@ -592,6 +598,24 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
);
|
||||
}
|
||||
|
||||
void _drawRetailWarningIntro(int backgroundColor) {
|
||||
const int black = 0;
|
||||
const int yellow = 14;
|
||||
const int white = 15;
|
||||
const int lineColor = 4;
|
||||
|
||||
_fillRect320(0, 0, 320, 22, black);
|
||||
_drawMenuTextCentered('Attention', 6, yellow, scale: 2);
|
||||
_fillRect320(0, 23, 320, 1, lineColor);
|
||||
|
||||
_drawMenuText('This game is NOT shareware.', 40, 56, white, scale: 1);
|
||||
_drawMenuText('Please do not distribute it.', 40, 68, white, scale: 1);
|
||||
_drawMenuText('Thanks.', 40, 80, white, scale: 1);
|
||||
_drawMenuTextCentered('Id Software', 106, white, scale: 1);
|
||||
|
||||
_fillRect320(0, 196, 320, 4, backgroundColor);
|
||||
}
|
||||
|
||||
int _dominantPaletteIndex(VgaImage image) {
|
||||
final List<int> histogram = List<int>.filled(256, 0);
|
||||
for (final int colorIndex in image.pixels) {
|
||||
|
||||
@@ -209,9 +209,11 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
}
|
||||
|
||||
void _drawIntroSplash(WolfEngine engine, WolfClassicMenuArt art) {
|
||||
final image = engine.menuManager.isIntroPg13Slide
|
||||
? art.mappedPic(WolfMenuPic.pg13)
|
||||
: art.mappedPic(WolfMenuPic.title);
|
||||
final image = switch (engine.menuManager.currentIntroSlide) {
|
||||
WolfIntroSlide.retailWarning => null,
|
||||
WolfIntroSlide.pg13 => art.mappedPic(WolfMenuPic.pg13),
|
||||
WolfIntroSlide.title => art.mappedPic(WolfMenuPic.title),
|
||||
};
|
||||
|
||||
int splashBgColor = _rgbToFrameColor(engine.menuManager.introBackgroundRgb);
|
||||
int? matteIndex;
|
||||
@@ -227,6 +229,10 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
_buffer.pixels[i] = splashBgColor;
|
||||
}
|
||||
|
||||
if (engine.menuManager.isIntroRetailWarningSlide) {
|
||||
_drawRetailWarningIntro(splashBgColor);
|
||||
}
|
||||
|
||||
if (image != null) {
|
||||
final int x = engine.menuManager.isIntroPg13Slide
|
||||
? (320 - image.width).clamp(0, 319)
|
||||
@@ -247,6 +253,36 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
);
|
||||
}
|
||||
|
||||
void _drawRetailWarningIntro(int backgroundColor) {
|
||||
final int black = ColorPalette.vga32Bit[0];
|
||||
final int yellow = ColorPalette.vga32Bit[14];
|
||||
final int white = ColorPalette.vga32Bit[15];
|
||||
final int lineColor = ColorPalette.vga32Bit[4];
|
||||
|
||||
_fillCanonicalRect(0, 0, 320, 22, black);
|
||||
_drawCanonicalMenuTextCentered('Attention', 6, yellow, scale: 2);
|
||||
_fillCanonicalRect(0, 23, 320, 1, lineColor);
|
||||
|
||||
_drawCanonicalMenuText(
|
||||
'This game is NOT shareware.',
|
||||
40,
|
||||
56,
|
||||
white,
|
||||
scale: 1,
|
||||
);
|
||||
_drawCanonicalMenuText(
|
||||
'Please do not distribute it.',
|
||||
40,
|
||||
68,
|
||||
white,
|
||||
scale: 1,
|
||||
);
|
||||
_drawCanonicalMenuText('Thanks.', 40, 80, white, scale: 1);
|
||||
_drawCanonicalMenuTextCentered('Id Software', 106, white, scale: 1);
|
||||
|
||||
_fillCanonicalRect(0, 196, 320, 4, backgroundColor);
|
||||
}
|
||||
|
||||
int _dominantPaletteIndex(VgaImage image) {
|
||||
final List<int> histogram = List<int>.filled(256, 0);
|
||||
for (final int colorIndex in image.pixels) {
|
||||
|
||||
Reference in New Issue
Block a user