[Scummvm-git-logs] scummvm master -> 10a54c2696126a81ee2b2f7f10b1a8be90ed7711

sev- noreply at scummvm.org
Mon Aug 24 23:05:06 UTC 2026


This automated email contains information about 17 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
38bc73531c AVALANCHE: Restore screen resolution in Outro::run to fix crash on exit
34520412fd AVALANCHE: Support returning to launcher on game exit
2dd01922b8 AVALANCHE: Fix toolbar not rendering in game
91ef88814c AVALANCHE: Handle Alt keyboard shortcuts for Boss key, quit, and easter egg
285e72a0d4 AVALANCHE: Add bounds checking for character IDs to prevent out-of-bounds array access
c020475724 AVALANCHE: Fix Spludwick presence in laboratory and dynamic room calculation
ae737c7161 AVALANCHE: Fix password character matching out-of-bounds crash in parser
f179e1dade AVALANCHE: Implement Golden Slumbers cutscene when giving lute to Geida
7f8982f98c AVALANCHE: Add playMod and stopMod methods to SoundHandler for MOD audio playback
2c6a22a7e5 AVALANCHE: Create TitleScreen class
4e457efa90 AVALANCHE: Implement EGA title screen with scrolling credits banner
4ef76e75e0 AVALANCHE: Refactor intro music playback to use SoundHandler methods
1c7d1154aa AVALANCHE: Use local variable in TitleScreen font loading to prevent in-game font corruption
969ccbd937 AVALANCHE: Implement MainMenu Option 3 teaser preview
5cd66a540e AVALANCHE: Implement MainMenu Option 5 documentation reader for avalot.doc
5390f634f0 AVALANCHE: Implement MainMenu Option 4: documentation index and section jumping
10a54c2696 AVALANCHE: Remove stray arrow character from documentation viewer status text


Commit: 38bc73531cf703e5014150ec85c68ab34225da7a
    https://github.com/scummvm/scummvm/commit/38bc73531cf703e5014150ec85c68ab34225da7a
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Restore screen resolution in Outro::run to fix crash on exit

Changed paths:
    engines/avalanche/outro.cpp


diff --git a/engines/avalanche/outro.cpp b/engines/avalanche/outro.cpp
index 14634f65fb1..905ba7e6aef 100644
--- a/engines/avalanche/outro.cpp
+++ b/engines/avalanche/outro.cpp
@@ -38,6 +38,7 @@ namespace Avalanche {
     }
 
     void Outro::run() {
+        _vm->_graphics->menuRestoreScreen();
         CursorMan.showMouse(false);
         _vm->_graphics->blackOutScreen();
         _vm->_graphics->drawQuittingPic();


Commit: 34520412fdf40f7b1eadc498d5d986d43773c670
    https://github.com/scummvm/scummvm/commit/34520412fdf40f7b1eadc498d5d986d43773c670
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Support returning to launcher on game exit

Changed paths:
    engines/avalanche/avalanche.cpp


diff --git a/engines/avalanche/avalanche.cpp b/engines/avalanche/avalanche.cpp
index eaf022f5df2..bc835fd2e71 100644
--- a/engines/avalanche/avalanche.cpp
+++ b/engines/avalanche/avalanche.cpp
@@ -181,7 +181,7 @@ Common::ErrorCode AvalancheEngine::initialize() {
 }
 
 bool AvalancheEngine::hasFeature(EngineFeature f) const {
-	return (f == kSupportsSavingDuringRuntime) || (f == kSupportsLoadingDuringRuntime);
+	return (f == kSupportsSavingDuringRuntime) || (f == kSupportsLoadingDuringRuntime) || (f == kSupportsReturnToLauncher);
 }
 
 const char *AvalancheEngine::getCopyrightString() const {


Commit: 2dd01922b8be5234605255b8c455e2e6d2a3626b
    https://github.com/scummvm/scummvm/commit/2dd01922b8be5234605255b8c455e2e6d2a3626b
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Fix toolbar not rendering in game

Changed paths:
    engines/avalanche/avalanche.cpp
    engines/avalanche/avalot.cpp


diff --git a/engines/avalanche/avalanche.cpp b/engines/avalanche/avalanche.cpp
index bc835fd2e71..2028fc3d824 100644
--- a/engines/avalanche/avalanche.cpp
+++ b/engines/avalanche/avalanche.cpp
@@ -440,7 +440,7 @@ bool AvalancheEngine::loadGame(const int16 slot) {
 	_alive = true;
 	refreshObjectList();
 	_animation->updateSpeed();
-	drawDirection();
+	drawToolbar();
 	_animation->animLink();
 	_background->update();
 
diff --git a/engines/avalanche/avalot.cpp b/engines/avalanche/avalot.cpp
index d5977df067f..6dc4dd94d1d 100644
--- a/engines/avalanche/avalot.cpp
+++ b/engines/avalanche/avalot.cpp
@@ -1466,6 +1466,7 @@ void AvalancheEngine::newGame() {
 	enterRoom(kRoomYours, 1);
 	avvy->_visible = false;
 	drawScore();
+	drawToolbar();
 	_dropdown->setup();
 	_clock->update();
 	spriteRun();


Commit: 91ef88814c8e72e67c7080984f9166462515b8d4
    https://github.com/scummvm/scummvm/commit/91ef88814c8e72e67c7080984f9166462515b8d4
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Handle Alt keyboard shortcuts for Boss key, quit, and easter egg

Changed paths:
    engines/avalanche/avalot.cpp


diff --git a/engines/avalanche/avalot.cpp b/engines/avalanche/avalot.cpp
index 6dc4dd94d1d..4d0f6f33ad5 100644
--- a/engines/avalanche/avalot.cpp
+++ b/engines/avalanche/avalot.cpp
@@ -117,6 +117,22 @@ Room AvalancheEngine::_whereIs[29] = {
 void AvalancheEngine::handleKeyDown(Common::Event &event) {
 	_sound->click();
 
+	if (event.kbd.flags & Common::KBD_ALT) {
+		switch (event.kbd.keycode) {
+		case Common::KEYCODE_b:
+			callVerb(kVerbCodeBoss);
+			return;
+		case Common::KEYCODE_x:
+			callVerb(kVerbCodeQuit);
+			return;
+		case Common::KEYCODE_d:
+			_dialogs->displayText("Wrong game!");
+			return;
+		default:
+			break;
+		}
+	}
+
 	if ((Common::KEYCODE_F1 <= event.kbd.keycode) && (event.kbd.keycode <= Common::KEYCODE_F15))
 		_parser->handleFunctionKey(event);
 	else if ((32 <= event.kbd.ascii) && (event.kbd.ascii <= 128) && (event.kbd.ascii != 47))


Commit: 285e72a0d465dc340d8527c7ad552714d4b451d8
    https://github.com/scummvm/scummvm/commit/285e72a0d465dc340d8527c7ad552714d4b451d8
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Add bounds checking for character IDs to prevent out-of-bounds array access

Changed paths:
    engines/avalanche/avalot.cpp


diff --git a/engines/avalanche/avalot.cpp b/engines/avalanche/avalot.cpp
index 4d0f6f33ad5..6f47476e2c0 100644
--- a/engines/avalanche/avalot.cpp
+++ b/engines/avalanche/avalot.cpp
@@ -1698,10 +1698,14 @@ void AvalancheEngine::openDoor(Room whither, byte ped, byte magicnum) {
 }
 
 void AvalancheEngine::setRoom(People persId, Room roomId) {
+	if (persId < kPeopleAvalot || persId > kPeopleWisewoman)
+		return;
 	_whereIs[persId - kPeopleAvalot] = roomId;
 }
 
 Room AvalancheEngine::getRoom(People persId) {
+	if (persId < kPeopleAvalot || persId > kPeopleWisewoman)
+		return kRoomNowhere;
 	return _whereIs[persId - kPeopleAvalot];
 }
 } // End of namespace Avalanche


Commit: c0204757248342f39c5616113a6de5548e1343ea
    https://github.com/scummvm/scummvm/commit/c0204757248342f39c5616113a6de5548e1343ea
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Fix Spludwick presence in laboratory and dynamic room calculation

Initialized _spludwickAtHome to true in resetVariables() to match original game source, so that by default Spludwick is at the laboratory

Changed paths:
    engines/avalanche/avalot.cpp


diff --git a/engines/avalanche/avalot.cpp b/engines/avalanche/avalot.cpp
index 6f47476e2c0..21bc7ced4af 100644
--- a/engines/avalanche/avalot.cpp
+++ b/engines/avalanche/avalot.cpp
@@ -626,6 +626,11 @@ void AvalancheEngine::enterRoom(Room roomId, byte ped) {
 		break;
 
 	case kRoomSpludwicks:
+		if (!_talkedToCrapulus || _lustieIsAsleep)
+			_spludwickAtHome = true;
+		else
+			_spludwickAtHome = !((_roomCount[kRoomWiseWomans] % 3) == 1);
+
 		if (_spludwickAtHome) {
 			AnimationType *spr1 = _animation->_sprites[1];
 			if (ped > 0) {
@@ -1399,7 +1404,7 @@ void AvalancheEngine::resetVariables() {
 	_totalTime = 0;
 	_jumpStatus = 0;
 	_mushroomGrowing = false;
-	_spludwickAtHome = false;
+	_spludwickAtHome = true;
 	_lastRoom = kRoomDummy;
 	_lastRoomNotMap = kRoomDummy;
 	_crapulusWillTell = false;


Commit: ae737c7161378b517577b46ddcd76e4b8b29d9d8
    https://github.com/scummvm/scummvm/commit/ae737c7161378b517577b46ddcd76e4b8b29d9d8
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Fix password character matching out-of-bounds crash in parser

Changed paths:
    engines/avalanche/parser.cpp


diff --git a/engines/avalanche/parser.cpp b/engines/avalanche/parser.cpp
index ba89fb46e47..2ac014fb6c5 100644
--- a/engines/avalanche/parser.cpp
+++ b/engines/avalanche/parser.cpp
@@ -2304,7 +2304,7 @@ void Parser::doThat() {
 				temp.toUppercase();
 				int pwdId = _vm->_passwordNum + kFirstPassword;
 				for (uint j = 0; j < _vocabulary[pwdId]._word.size(); j++) {
-					if (_vocabulary[pwdId]._word[j] != temp[j])
+					if (_vocabulary[pwdId]._word[j] != ((j < temp.size()) ? temp[j] : '\0'))
 						ok = false;
 				}
 			}


Commit: f179e1dadeaba87706fd66d7f6e2ee9eac4dc11b
    https://github.com/scummvm/scummvm/commit/f179e1dadeaba87706fd66d7f6e2ee9eac4dc11b
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Implement Golden Slumbers cutscene when giving lute to Geida

Changed paths:
    engines/avalanche/avalot.cpp
    engines/avalanche/parser.cpp


diff --git a/engines/avalanche/avalot.cpp b/engines/avalanche/avalot.cpp
index 21bc7ced4af..108ad71ff21 100644
--- a/engines/avalanche/avalot.cpp
+++ b/engines/avalanche/avalot.cpp
@@ -1316,6 +1316,7 @@ void AvalancheEngine::minorRedraw() {
 	for (int i = 0; i < 3; i++)
 		_scoreToDisplay[i] = -1; // impossible digits
 	drawScore();
+	drawToolbar();
 
 	fadeIn();
 }
diff --git a/engines/avalanche/parser.cpp b/engines/avalanche/parser.cpp
index 2ac014fb6c5..9b692d3d13b 100644
--- a/engines/avalanche/parser.cpp
+++ b/engines/avalanche/parser.cpp
@@ -1676,8 +1676,54 @@ void Parser::giveGeidaTheLute() {
 	// She plays it.
 	_vm->_dialogs->displayScrollChain('Q', 64);
 
+	// Golden Slumbers cutscene
+	_vm->_graphics->saveScreen();
+
+	for (int gd = 0; gd <= 320; gd += 32) {
+		_vm->_graphics->drawFilledRectangle(Common::Rect(320 - gd, 100 - gd / 2, 320 + gd, 100 + gd / 2), kColorLightblue);
+		_vm->_graphics->refreshScreen();
+		g_system->delayMillis(15);
+	}
+	_vm->_graphics->drawFilledRectangle(Common::Rect(0, 0, 640, 200), kColorLightblue);
+	_vm->_graphics->refreshScreen();
+
+	static const char *const lullabyLines[] = {
+		"Golden slumbers kiss your eyes",
+		"Smiles awake you when you rise",
+		"Sleep, pretty Baron, do not cry",
+		"And I will sing a lullaby.",
+		"",
+		"Care you know not, therefore sleep",
+		"While I o'er you watch do keep;",
+		"Sleep now, du Lustie, do not cry",
+		"And I will leave the castle. Bye!"
+	};
+
+	int curY = 20;
+	for (int i = 0; i < 9; i++) {
+		if (lullabyLines[i][0] != '\0') {
+			_vm->_graphics->drawNormalText(lullabyLines[i], _vm->_font, 8, 80, curY, kColorWhite);
+			_vm->_graphics->refreshScreen();
+			g_system->delayMillis(1200);
+		} else {
+			g_system->delayMillis(600);
+		}
+		curY += 15;
+	}
+
+	g_system->delayMillis(1500);
+
+	for (int gd = 320; gd >= 0; gd -= 32) {
+		_vm->_graphics->restoreScreen();
+		_vm->_graphics->drawFilledRectangle(Common::Rect(320 - gd, 100 - gd / 2, 320 + gd, 100 + gd / 2), kColorLightblue);
+		_vm->_graphics->refreshScreen();
+		g_system->delayMillis(15);
+	}
+
+	_vm->minorRedraw();
+	_vm->_graphics->refreshScreen();
+
 	_vm->_timer->addTimer(1, Timer::kProcGiveLuteToGeida, Timer::kReasonGeidaSings);
-	//_vm->_enid->backToBootstrap(4); TODO: Replace it with proper ScummVM-friendly function(s)!  Do not remove until then!
 }
 
 void Parser::playHarp() {


Commit: 7f8982f98c70599653f9b5d35a0d15f31ca22129
    https://github.com/scummvm/scummvm/commit/7f8982f98c70599653f9b5d35a0d15f31ca22129
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Add playMod and stopMod methods to SoundHandler for MOD audio playback

Changed paths:
    engines/avalanche/sound.cpp
    engines/avalanche/sound.h


diff --git a/engines/avalanche/sound.cpp b/engines/avalanche/sound.cpp
index 13edf49b030..59dbbbdf10f 100644
--- a/engines/avalanche/sound.cpp
+++ b/engines/avalanche/sound.cpp
@@ -23,6 +23,7 @@
 #include "avalanche/sound.h"
 
 #include "audio/softsynth/pcspk.h"
+#include "audio/mods/protracker.h"
 #include "common/config-manager.h"
 
 namespace Avalanche {
@@ -84,4 +85,26 @@ void SoundHandler::blip() {
 	playNote(177, 77);
 }
 
+void SoundHandler::playMod(const Common::Path &filename) {
+	if (!_soundFl)
+		return;
+
+	Common::File *file = new Common::File();
+	if (!file->open(filename)) {
+		warning("AVALANCHE: SoundHandler: Music file not found: %s", filename.toString(Common::Path::kNativeSeparator).c_str());
+		delete file;
+		return;
+	}
+
+	Audio::AudioStream *stream = Audio::makeProtrackerStream(file);
+	if (stream)
+		_vm->_mixer->playStream(Audio::Mixer::kMusicSoundType, nullptr, stream);
+	else
+		delete file;
+}
+
+void SoundHandler::stopMod() {
+	_vm->_mixer->stopAll();
+}
+
 } // End of namespace Avalanche
diff --git a/engines/avalanche/sound.h b/engines/avalanche/sound.h
index 848beff8b6a..1bea77cc643 100644
--- a/engines/avalanche/sound.h
+++ b/engines/avalanche/sound.h
@@ -43,6 +43,8 @@ public:
 	void blip();
 	void syncVolume();
 	void stopSound();
+	void playMod(const Common::Path &filename);
+	void stopMod();
 
 private:
 	AvalancheEngine *_vm;


Commit: 2c6a22a7e5934610e279dd16e400ab54476a6059
    https://github.com/scummvm/scummvm/commit/2c6a22a7e5934610e279dd16e400ab54476a6059
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Create TitleScreen class

Changed paths:
  A engines/avalanche/titlescreen.cpp
  A engines/avalanche/titlescreen.h
    engines/avalanche/avalanche.cpp
    engines/avalanche/avalanche.h
    engines/avalanche/module.mk


diff --git a/engines/avalanche/avalanche.cpp b/engines/avalanche/avalanche.cpp
index 2028fc3d824..1f3009fa3fd 100644
--- a/engines/avalanche/avalanche.cpp
+++ b/engines/avalanche/avalanche.cpp
@@ -33,6 +33,7 @@
 
 #include "avalanche/avalanche.h"
 #include "avalanche/intro.h"
+#include "avalanche/titlescreen.h"
 
 namespace Avalanche {
 
@@ -59,6 +60,7 @@ AvalancheEngine::AvalancheEngine(OSystem *syst, const AvalancheGameDescription *
 	_highscore = nullptr;
 	_intro = nullptr;
 	_outro = nullptr;
+	_titleScreen = nullptr;
 
 	initVariables();
 }
@@ -84,6 +86,7 @@ AvalancheEngine::~AvalancheEngine() {
 	delete _highscore;
 	delete _intro;
 	delete _outro;
+	delete _titleScreen;
 
 	for (int i = 0; i < 31; i++) {
 		for (int j = 0; j < 2; j++) {
@@ -171,6 +174,7 @@ Common::ErrorCode AvalancheEngine::initialize() {
 	_highscore = new HighScore(this);
 	_intro = new Intro(this);
 	_outro = new Outro(this);
+	_titleScreen = new TitleScreen(this);
 
 	_graphics->init();
 	_dialogs->init();
diff --git a/engines/avalanche/avalanche.h b/engines/avalanche/avalanche.h
index bd07eeaef11..937365f09ab 100644
--- a/engines/avalanche/avalanche.h
+++ b/engines/avalanche/avalanche.h
@@ -61,6 +61,7 @@ class RandomSource;
 namespace Avalanche {
 
 struct AvalancheGameDescription;
+class TitleScreen;
 class Intro;
 
 class Outro;
@@ -95,6 +96,7 @@ public:
 	HighScore *_highscore;
 	Intro *_intro;
 	Outro *_outro;
+	TitleScreen *_titleScreen;
 
 	AvalancheEngine(OSystem *syst, const AvalancheGameDescription *gd);
 	~AvalancheEngine() override;
diff --git a/engines/avalanche/module.mk b/engines/avalanche/module.mk
index 3a1c8259bcc..cdd98b2003d 100644
--- a/engines/avalanche/module.mk
+++ b/engines/avalanche/module.mk
@@ -24,6 +24,7 @@ MODULE_OBJS = \
 	shootemup.o \
 	sound.o \
 	timer.o \
+	titlescreen.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_AVALANCHE), DYNAMIC_PLUGIN)
diff --git a/engines/avalanche/titlescreen.cpp b/engines/avalanche/titlescreen.cpp
new file mode 100644
index 00000000000..11c60b99a4d
--- /dev/null
+++ b/engines/avalanche/titlescreen.cpp
@@ -0,0 +1,37 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "avalanche/avalanche.h"
+#include "avalanche/titlescreen.h"
+
+namespace Avalanche {
+
+TitleScreen::TitleScreen(AvalancheEngine *vm) : _vm(vm) {
+}
+
+TitleScreen::~TitleScreen() {
+}
+
+void TitleScreen::run() {
+	(void)_vm;
+}
+
+} // End of namespace Avalanche
diff --git a/engines/avalanche/titlescreen.h b/engines/avalanche/titlescreen.h
new file mode 100644
index 00000000000..4231e9cd848
--- /dev/null
+++ b/engines/avalanche/titlescreen.h
@@ -0,0 +1,41 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AVALANCHE_TITLESCREEN_H
+#define AVALANCHE_TITLESCREEN_H
+
+namespace Avalanche {
+
+class AvalancheEngine;
+
+class TitleScreen {
+public:
+	TitleScreen(AvalancheEngine *vm);
+	~TitleScreen();
+	void run();
+
+private:
+	AvalancheEngine *_vm;
+};
+
+} // End of namespace Avalanche
+
+#endif // AVALANCHE_TITLESCREEN_H


Commit: 4e457efa90555d60b3244b0e4be78462d01784cc
    https://github.com/scummvm/scummvm/commit/4e457efa90555d60b3244b0e4be78462d01784cc
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Implement EGA title screen with scrolling credits banner

1) Fix drawArc to draw on passed surface instead of Graphics::Surface _scrolls.
2) drawArc was using 361 degrees instead of 360, which caused 361° to wrap down to 0° and swap start and end angles into full closed circles instead of 1° arc.

Changed paths:
    engines/avalanche/graphics.cpp
    engines/avalanche/graphics.h
    engines/avalanche/mainmenu.cpp
    engines/avalanche/titlescreen.cpp
    engines/avalanche/titlescreen.h


diff --git a/engines/avalanche/graphics.cpp b/engines/avalanche/graphics.cpp
index a485e73f95a..5b2fc73fd62 100644
--- a/engines/avalanche/graphics.cpp
+++ b/engines/avalanche/graphics.cpp
@@ -217,16 +217,11 @@ Common::Point GraphicManager::drawArc(Graphics::Surface &surface, int16 x, int16
 		return endPoint;
 	}
 
-	// Check if valid angles.
-	stAngle = stAngle % 361;
-	endAngle = endAngle % 361;
-
-	// If impossible angles, then swap them!
-	if (endAngle < stAngle) {
-		uint16 tmpAngle=endAngle;
-		endAngle=stAngle;
-		stAngle=tmpAngle;
-	}
+	// Normalize angles modulo 360
+	stAngle = stAngle % 360;
+	endAngle = endAngle % 360;
+	if (endAngle == 0 && stAngle > 0)
+		endAngle = 360;
 
 	// Approximate the number of pixels required by using the circumference equation of an ellipse.
 	uint16 numOfPixels = (uint16)floor(sqrt(3.0) * sqrt(pow(double(xRadius), 2) + pow(double(yRadius), 2)) + 0.5);
@@ -264,17 +259,25 @@ Common::Point GraphicManager::drawArc(Graphics::Surface &surface, int16 x, int16
 		int16 yp = y + yTemp;
 		int16 ym = y - yTemp;
 
-		if ((j >= stAngle) && (j <= endAngle))
-			*(byte *)_scrolls.getBasePtr(xp, yp) = color;
+		if ((j >= stAngle) && (j <= endAngle)) {
+			if ((xp >= 0) && (xp < surface.w) && (yp >= 0) && (yp < surface.h))
+				*(byte *)surface.getBasePtr(xp, yp) = color;
+		}
 
-		if (((180 - j) >= stAngle) && ((180 - j) <= endAngle))
-			*(byte *)_scrolls.getBasePtr(xm, yp) = color;
+		if (((180 - j) >= stAngle) && ((180 - j) <= endAngle)) {
+			if ((xm >= 0) && (xm < surface.w) && (yp >= 0) && (yp < surface.h))
+				*(byte *)surface.getBasePtr(xm, yp) = color;
+		}
 
-		if (((j + 180) >= stAngle) && ((j + 180) <= endAngle))
-			*(byte *)_scrolls.getBasePtr(xm, ym) = color;
+		if (((j + 180) >= stAngle) && ((j + 180) <= endAngle)) {
+			if ((xm >= 0) && (xm < surface.w) && (ym >= 0) && (ym < surface.h))
+				*(byte *)surface.getBasePtr(xm, ym) = color;
+		}
 
-		if (((360 - j) >= stAngle) && ((360 - j) <= endAngle))
-			*(byte *)_scrolls.getBasePtr(xp, ym) = color;
+		if (((360 - j) >= stAngle) && ((360 - j) <= endAngle)) {
+			if ((xp >= 0) && (xp < surface.w) && (ym >= 0) && (ym < surface.h))
+				*(byte *)surface.getBasePtr(xp, ym) = color;
+		}
 
 		j += delta;
 	} while (j <= deltaEnd);
@@ -283,7 +286,8 @@ Common::Point GraphicManager::drawArc(Graphics::Surface &surface, int16 x, int16
 }
 
 void GraphicManager::drawDot(int x, int y, Color color) {
-	*(byte *)_surface.getBasePtr(x, y) = color;
+	if ((x >= 0) && (x < _surface.w) && (y >= 0) && (y < _surface.h))
+		*(byte *)_surface.getBasePtr(x, y) = color;
 }
 
 void GraphicManager::drawLine(int x1, int y1, int x2, int y2, int penX, int penY, Color color) {
@@ -342,8 +346,12 @@ void GraphicManager::drawText(Graphics::Surface &surface, const Common::String &
 			byte pixel = font[(byte)text[i]][j];
 			for (int bit = 0; bit < 8; bit++) {
 				byte pixelBit = (pixel >> bit) & 1;
-				if (pixelBit)
-					*(byte *)surface.getBasePtr(x + i * 8 + 7 - bit, y + j) = color;
+				if (pixelBit) {
+					int px = x + i * 8 + 7 - bit;
+					int py = y + j;
+					if (px >= 0 && px < surface.w && py >= 0 && py < surface.h)
+						*(byte *)surface.getBasePtr(px, py) = color;
+				}
 			}
 		}
 	}
diff --git a/engines/avalanche/graphics.h b/engines/avalanche/graphics.h
index e7c68c81976..05d57e5f863 100644
--- a/engines/avalanche/graphics.h
+++ b/engines/avalanche/graphics.h
@@ -166,7 +166,15 @@ public:
 	void restoreScreen();
 	void removeBackup();
 
+	Graphics::Surface loadPictureGraphic(Common::File &file); // Reads Graphic-planar EGA data.
+	void drawPicture(Graphics::Surface &target, const Graphics::Surface &picture, uint16 destX, uint16 destY);
+	// Taken from Free Pascal's Procedure InternalEllipseDefault. Used to replace Pascal's procedure arc.
+	// Returns the end point of the arc. (Needed in Clock.)
+	Common::Point drawArc(Graphics::Surface &surface, int16 x, int16 y, int16 stAngle, int16 endAngle, uint16 radius, Color color);
+	void drawText(Graphics::Surface &surface, const Common::String &text, FontType font, byte fontHeight, int16 x, int16 y, Color color);
+
 	Graphics::Surface &getSurface() { return _surface; }
+	Graphics::Surface &getMenuSurface() { return _menu; }
 
 private:
 	static const int16 kMouseSize = 134;
@@ -201,16 +209,9 @@ private:
 	void skipDifference(int size, const Graphics::Surface &picture, Common::File &file);
 
 	// Further information about these two: https://moddingwiki.shikadi.net/wiki/Raw_EGA_data
-	Graphics::Surface loadPictureGraphic(Common::File &file); // Reads Graphic-planar EGA data.
 	Graphics::Surface loadPictureSign(Common::File &file, uint16 width, uint16 height); // Reads a tricky type of picture used for the "game over"/"about" scrolls and in the mini-game Nim.
 
-	void drawText(Graphics::Surface &surface, const Common::String &text, FontType font, byte fontHeight, int16 x, int16 y, Color color);
 	void drawBigText(Graphics::Surface &surface, const Common::String &text, FontType font, byte fontHeight, int16 x, int16 y, Color color);
-	void drawPicture(Graphics::Surface &target, const Graphics::Surface &picture, uint16 destX, uint16 destY);
-
-	// Taken from Free Pascal's Procedure InternalEllipseDefault. Used to replace Pascal's procedure arc.
-	// Returns the end point of the arc. (Needed in Clock.)
-	Common::Point drawArc(Graphics::Surface &surface, int16 x, int16 y, int16 stAngle, int16 endAngle, uint16 radius, Color color);
 };
 
 } // End of namespace Avalanche
diff --git a/engines/avalanche/mainmenu.cpp b/engines/avalanche/mainmenu.cpp
index ce1eafc1d75..fa02db9701d 100644
--- a/engines/avalanche/mainmenu.cpp
+++ b/engines/avalanche/mainmenu.cpp
@@ -29,6 +29,7 @@
 #include "avalanche/avalanche.h"
 #include "avalanche/mainmenu.h"
 #include "avalanche/intro.h"
+#include "titlescreen.h"
 
 namespace Avalanche {
 
@@ -39,7 +40,7 @@ MainMenu::MainMenu(AvalancheEngine *vm) {
 }
 
 void MainMenu::run() {
-	_vm->_intro->run();
+	_vm->_titleScreen->run();
 	_vm->_graphics->menuInitialize();
 	_vm->_graphics->menuLoadPictures();
 	loadRegiInfo();
diff --git a/engines/avalanche/titlescreen.cpp b/engines/avalanche/titlescreen.cpp
index 11c60b99a4d..b3003309e0d 100644
--- a/engines/avalanche/titlescreen.cpp
+++ b/engines/avalanche/titlescreen.cpp
@@ -30,8 +30,168 @@ TitleScreen::TitleScreen(AvalancheEngine *vm) : _vm(vm) {
 TitleScreen::~TitleScreen() {
 }
 
+void TitleScreen::loadCredits(Common::Array<Common::String> &lines) {
+	Common::File f;
+	if (!f.open("credits.dat")) {
+		warning("AVALANCHE: TitleScreen: File not found: credits.dat");
+		return;
+	}
+
+	while (!f.eos()) {
+		Common::String line;
+		while (!f.eos()) {
+			char ch = f.readByte();
+			if (ch == '\n')
+				break;
+			if (ch != '\r')
+				line += ch;
+		}
+
+		Common::String str;
+		for (uint i = 0; i < line.size(); i++) {
+			char c = line[i];
+			if (c == '%')
+				break;
+			if (c == '@' || c == '^' || c == '>' || c == '*')
+				continue;
+			str += c;
+		}
+		str.trim();
+		if (!str.empty())
+			lines.push_back(str);
+	}
+	f.close();
+}
+
 void TitleScreen::run() {
-	(void)_vm;
+	_vm->_sound->playMod("avalot2.mod");
+
+	Common::File fontFile;
+	if (fontFile.open("avalot.fnt")) {
+		for (int i = 0; i < 256; i++)
+			fontFile.read(_vm->_font[i], 16);
+		fontFile.close();
+	}
+
+	_vm->_graphics->menuInitialize();
+	Graphics::Surface &menu = _vm->_graphics->getMenuSurface();
+	menu.fillRect(Common::Rect(0, 0, 640, 350), kColorBlack);
+
+	CursorMan.showMouse(false);
+
+	// Draw star arcs
+	static const Color arcColors[5] = { kColorBlue, kColorLightblue, kColorCyan, kColorLightcyan, kColorWhite };
+	byte c = 0;
+	for (int gd = 1; gd <= 64; gd++) {
+		for (int gm = 0; gm <= 60; gm++) {
+			c = (c % 12) + 1;
+			if (c > 5)
+				continue;
+			_vm->_graphics->drawArc(menu, 320, 175, gm * 6, gm * 6 + 1, gd * 6, arcColors[c - 1]);
+		}
+	}
+
+	// Load logo.avd — 1bpp monochrome (each bit = white/black, written to all EGA planes simultaneously)
+	// OR-paste at (112, 0): only white pixels land on screen, black are transparent
+	Common::File logoFile;
+	if (logoFile.open("logo.avd")) {
+		Graphics::Surface logo;
+		logo.create(53 * 8, 194 - 7 + 1, Graphics::PixelFormat::createFormatCLUT8());
+		for (int y = 0; y < 194 - 7 + 1; y++) {
+			for (int x = 0; x < 424; x += 8) {
+				byte pixel = logoFile.readByte();
+				for (int bit = 0; bit < 8; bit++) {
+					byte pixelBit = (pixel >> (7 - bit)) & 1;
+					*(byte *)logo.getBasePtr(x + bit, y) = pixelBit ? kColorWhite : kColorBlack;
+				}
+			}
+		}
+		logoFile.close();
+		for (int y = 0; y < logo.h && y < menu.h; y++) {
+			for (int x = 0; x < logo.w && (x + 112) < menu.w; x++) {
+				byte color = *(byte *)logo.getBasePtr(x, y);
+				// White pixels (logo) overwrite background (stars)
+				if (color != kColorBlack)
+					*(byte *)menu.getBasePtr(x + 112, y) = color;
+			}
+		}
+		logo.free();
+	}
+
+	Common::Array<Common::String> credits;
+	loadCredits(credits);
+
+	Common::String scrollStr;
+	for (uint i = 0; i < credits.size(); i++) {
+		if (!scrollStr.empty())
+			scrollStr += "  *  ";
+		scrollStr += credits[i];
+	}
+	scrollStr += "   ";
+
+	const int kBandCenterY = 315;
+	const int kBandHalfH   = (110 - 50) / 3	;
+	const int kBandTop     = kBandCenterY - kBandHalfH;
+	const int kBandBottom     = kBandCenterY + kBandHalfH;
+
+	// Phase 1: Gold/magenta band opening animation
+	for (int spinnum = 50; spinnum <= 110 && !_vm->shouldQuit(); spinnum++) {
+		int expand = (spinnum - 50) / 3;
+		menu.fillRect(Common::Rect(0, kBandCenterY - expand, 640, kBandCenterY + expand + 1), kColorYellow);
+		if (spinnum > 56) {
+			int inner = (spinnum - 56) / 3;
+			menu.fillRect(Common::Rect(0, kBandCenterY - inner, 640, kBandCenterY + inner + 1), kColorLightmagenta);
+		}
+		_vm->_graphics->menuRefreshScreen();
+		g_system->delayMillis(3);
+
+		Common::Event event;
+		while (_vm->getEvent(event)) {
+			if (event.type == Common::EVENT_KEYDOWN || event.type == Common::EVENT_LBUTTONDOWN) {
+				_vm->_sound->stopMod();
+				CursorMan.showMouse(true);
+				return;
+			}
+		}
+	}
+
+	// Phase 2: Scroll credits text left across the band
+	int scrollX = 640;
+	uint32 lastTick = g_system->getMillis();
+
+	while (!_vm->shouldQuit()) {
+		// Draw settled band: gold & green borders + black center
+		menu.fillRect(Common::Rect(0, kBandTop + 2, 640, kBandBottom - 1), kColorBlack);
+		menu.fillRect(Common::Rect(0, kBandTop, 640, kBandTop + 2), kColorYellow);
+		menu.fillRect(Common::Rect(0, kBandBottom - 1, 640, kBandBottom + 1), kColorYellow);
+		menu.fillRect(Common::Rect(0, kBandTop + 2, 640, kBandTop + 4), kColorGreen);
+		menu.fillRect(Common::Rect(0, kBandBottom - 3, 640, kBandBottom - 1), kColorGreen);
+
+		int textY = kBandTop + (kBandHalfH * 2 - 16) / 2;
+		_vm->_graphics->drawText(menu, scrollStr, _vm->_font, 16, scrollX, textY, kColorWhite);
+		_vm->_graphics->menuRefreshScreen();
+
+		uint32 now = g_system->getMillis();
+		if (now - lastTick >= 32) {
+			scrollX -= 8;
+			lastTick = now;
+			if (scrollX < -(int)(scrollStr.size() * 8))
+				break;
+		}
+
+		Common::Event event;
+		while (_vm->getEvent(event)) {
+			if (event.type == Common::EVENT_KEYDOWN || event.type == Common::EVENT_LBUTTONDOWN) {
+				_vm->_sound->stopMod();
+				CursorMan.showMouse(true);
+				return;
+			}
+		}
+		g_system->delayMillis(5);
+	}
+
+	_vm->_sound->stopMod();
+	CursorMan.showMouse(true);
 }
 
 } // End of namespace Avalanche
diff --git a/engines/avalanche/titlescreen.h b/engines/avalanche/titlescreen.h
index 4231e9cd848..66bf1f5d699 100644
--- a/engines/avalanche/titlescreen.h
+++ b/engines/avalanche/titlescreen.h
@@ -22,6 +22,9 @@
 #ifndef AVALANCHE_TITLESCREEN_H
 #define AVALANCHE_TITLESCREEN_H
 
+#include "common/array.h"
+#include "common/str.h"
+
 namespace Avalanche {
 
 class AvalancheEngine;
@@ -34,6 +37,7 @@ public:
 
 private:
 	AvalancheEngine *_vm;
+	void loadCredits(Common::Array<Common::String> &lines);
 };
 
 } // End of namespace Avalanche


Commit: 4ef76e75e01c91a2e92caa6c54c5fbde6d393c86
    https://github.com/scummvm/scummvm/commit/4ef76e75e01c91a2e92caa6c54c5fbde6d393c86
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Refactor intro music playback to use SoundHandler methods

Changed paths:
    engines/avalanche/intro.cpp


diff --git a/engines/avalanche/intro.cpp b/engines/avalanche/intro.cpp
index 0600377324b..e8eac64bc29 100644
--- a/engines/avalanche/intro.cpp
+++ b/engines/avalanche/intro.cpp
@@ -258,13 +258,7 @@ void Intro::run() {
 	_vm->_graphics->menuRestoreScreen();
 
     // Load and play music (glover.mod)
-    Common::File musicFile;
-    if (musicFile.open("glover.mod")) {
-        Audio::AudioStream *stream = Audio::makeProtrackerStream(&musicFile);
-        if (stream) {
-            _vm->_mixer->playStream(Audio::Mixer::kMusicSoundType, _musicHandle, stream);
-        }
-    }
+    _vm->_sound->playMod("glover.mod");
 
     loadText();
     resetPlanes();
@@ -340,7 +334,7 @@ void Intro::run() {
 		Common::Event event;
 		while (_vm->getEvent(event)) {
 			if (event.type == Common::EVENT_KEYDOWN || event.type == Common::EVENT_LBUTTONDOWN) {
-                _vm->_mixer->stopHandle(*_musicHandle);
+				_vm->_sound->stopMod();
 				CursorMan.showMouse(true);
 				return;
 			}
@@ -351,7 +345,7 @@ void Intro::run() {
 			g_system->delayMillis(55 - delay);
 	}
 
-    _vm->_mixer->stopHandle(*_musicHandle);
+	_vm->_sound->stopMod();
 	CursorMan.showMouse(true);
 }
 


Commit: 1c7d1154aa3466c5e998449738faa177fd56fadb
    https://github.com/scummvm/scummvm/commit/1c7d1154aa3466c5e998449738faa177fd56fadb
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Use local variable in TitleScreen font loading to prevent in-game font corruption

Changed paths:
    engines/avalanche/titlescreen.cpp


diff --git a/engines/avalanche/titlescreen.cpp b/engines/avalanche/titlescreen.cpp
index b3003309e0d..44787d4ed99 100644
--- a/engines/avalanche/titlescreen.cpp
+++ b/engines/avalanche/titlescreen.cpp
@@ -66,10 +66,11 @@ void TitleScreen::loadCredits(Common::Array<Common::String> &lines) {
 void TitleScreen::run() {
 	_vm->_sound->playMod("avalot2.mod");
 
+	FontType avalotFont;
 	Common::File fontFile;
 	if (fontFile.open("avalot.fnt")) {
 		for (int i = 0; i < 256; i++)
-			fontFile.read(_vm->_font[i], 16);
+			fontFile.read(avalotFont[i], 16);
 		fontFile.close();
 	}
 
@@ -168,7 +169,7 @@ void TitleScreen::run() {
 		menu.fillRect(Common::Rect(0, kBandBottom - 3, 640, kBandBottom - 1), kColorGreen);
 
 		int textY = kBandTop + (kBandHalfH * 2 - 16) / 2;
-		_vm->_graphics->drawText(menu, scrollStr, _vm->_font, 16, scrollX, textY, kColorWhite);
+		_vm->_graphics->drawText(menu, scrollStr, avalotFont, 16, scrollX, textY, kColorWhite);
 		_vm->_graphics->menuRefreshScreen();
 
 		uint32 now = g_system->getMillis();


Commit: 969ccbd937ae4000f4571fb33c86e5d76e5acab6
    https://github.com/scummvm/scummvm/commit/969ccbd937ae4000f4571fb33c86e5d76e5acab6
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Implement MainMenu Option 3 teaser preview

Changed paths:
    engines/avalanche/mainmenu.cpp
    engines/avalanche/mainmenu.h


diff --git a/engines/avalanche/mainmenu.cpp b/engines/avalanche/mainmenu.cpp
index fa02db9701d..46b377ff331 100644
--- a/engines/avalanche/mainmenu.cpp
+++ b/engines/avalanche/mainmenu.cpp
@@ -111,6 +111,12 @@ void MainMenu::wait() {
 				_vm->_graphics->menuLoadPictures();
 				drawMenu();
 				break;
+			case Common::KEYCODE_3:
+				showPreview();
+				_vm->_graphics->menuInitialize();
+				_vm->_graphics->menuLoadPictures();
+				drawMenu();
+				break;
 			case Common::KEYCODE_ESCAPE:
 			case Common::KEYCODE_6: // Falltroughs are inteded.
 				// Exit back to DOS
@@ -124,4 +130,66 @@ void MainMenu::wait() {
 	}
 }
 
+void MainMenu::showPreview() {
+	Common::File file;
+	if (!file.open("preview2.avd")) {
+		warning("AVALANCHE: File not found: preview2.avd");
+		return;
+	}
+
+	_vm->_graphics->menuInitialize();
+	Graphics::Surface &preview = _vm->_graphics->getMenuSurface();
+	preview.fillRect(Common::Rect(0, 0, 640, 350), kColorBlack);
+
+	file.seek(177);
+
+	byte planes[4][151 * 80];
+	for (int plane = 0; plane < 4; plane++)
+		file.read(planes[plane], 151 * 80);
+	file.close();
+
+	for (int y = 0; y < 151; y++) {
+		int destY1 = 10 + (y * 4) / 3;
+		int destY2 = 10 + ((y + 1) * 4) / 3;
+		for (int x = 0; x < 640; x++) {
+			int byteIdx = y * 80 + (x / 8);
+			int bitIdx = 7 - (x % 8);
+
+			byte color = 0;
+			for (int plane = 0; plane < 4; plane++) {
+				byte bit = (planes[plane][byteIdx] >> bitIdx) & 1;
+				color |= (bit << plane);
+			}
+			for (int dy = destY1; dy < destY2 && dy < preview.h; dy++)
+				*(byte *)preview.getBasePtr(x, dy) = color;
+		}
+	}
+
+	_vm->_graphics->drawText(preview, "...This is a preview of things to come...", _font, 16, 320 - 20 * 8, 225, kColorCyan);
+	_vm->_graphics->drawText(preview, "AVAROID", _font, 16, 320 - 4 * 8, 245, kColorYellow);
+	_vm->_graphics->drawText(preview, "(a space so dizzy)", _font, 16, 320 - 9 * 8, 263, kColorYellow);
+	_vm->_graphics->drawText(preview, "the next Avvy adventure-- in 256 colours.", _font, 16, 320 - 20 * 8, 283, kColorLightblue);
+	_vm->_graphics->drawText(preview, "Any key...", _font, 16, 540, 318, kColorLightgray);
+
+	_vm->_graphics->menuRefreshScreen();
+	CursorMan.showMouse(false);
+
+	uint32 startTime = g_system->getMillis();
+	while (!_vm->shouldQuit()) {
+		if (g_system->getMillis() - startTime >= 15000)
+			break;
+
+		Common::Event event;
+		while (_vm->getEvent(event)) {
+			if (event.type == Common::EVENT_KEYDOWN || event.type == Common::EVENT_LBUTTONDOWN) {
+				CursorMan.showMouse(true);
+				return;
+			}
+		}
+		g_system->delayMillis(10);
+	}
+
+	CursorMan.showMouse(true);
+}
+
 } // End of namespace Avalanche
diff --git a/engines/avalanche/mainmenu.h b/engines/avalanche/mainmenu.h
index 1b76dbc5b0e..2738c75a85e 100644
--- a/engines/avalanche/mainmenu.h
+++ b/engines/avalanche/mainmenu.h
@@ -48,6 +48,7 @@ private:
 	void drawMenu();
 	void centre(int16 y, Common::String text);
 	void wait();
+	void showPreview();
 };
 
 } // End of namespace Avalanche


Commit: 5cd66a540e1df743637e0dff18e005aa0b047afa
    https://github.com/scummvm/scummvm/commit/5cd66a540e1df743637e0dff18e005aa0b047afa
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Implement MainMenu Option 5 documentation reader for avalot.doc

Changed paths:
    engines/avalanche/mainmenu.cpp
    engines/avalanche/mainmenu.h


diff --git a/engines/avalanche/mainmenu.cpp b/engines/avalanche/mainmenu.cpp
index 46b377ff331..7880ac32288 100644
--- a/engines/avalanche/mainmenu.cpp
+++ b/engines/avalanche/mainmenu.cpp
@@ -117,6 +117,12 @@ void MainMenu::wait() {
 				_vm->_graphics->menuLoadPictures();
 				drawMenu();
 				break;
+			case Common::KEYCODE_5:
+				showDoc("avalot.doc", 1094);
+				_vm->_graphics->menuInitialize();
+				_vm->_graphics->menuLoadPictures();
+				drawMenu();
+				break;
 			case Common::KEYCODE_ESCAPE:
 			case Common::KEYCODE_6: // Falltroughs are inteded.
 				// Exit back to DOS
@@ -130,6 +136,121 @@ void MainMenu::wait() {
 	}
 }
 
+void MainMenu::showDoc(const Common::String &filename, int startLine) {
+	Common::File file;
+	if (!file.open(filename.c_str())) {
+		warning("AVALANCHE: File not found: %s", filename.c_str());
+		return;
+	}
+
+	Common::Array<Common::String> lines;
+	while (!file.eos()) {
+		Common::String line;
+		while (!file.eos()) {
+			char ch = file.readByte();
+			if (ch == '\n')
+				break;
+			if (ch == '\t') {
+				int spaces = 8 - (line.size() % 8);
+				for (int s = 0; s < spaces; s++)
+					line += ' ';
+			} else if ((byte)ch >= 32 && (byte)ch <= 126) {
+				line += ch;
+			}
+		}
+		lines.push_back(line);
+	}
+	file.close();
+
+	if (lines.empty())
+		return;
+
+	int currentLine = startLine;
+	if (currentLine >= (int)lines.size())
+		currentLine = (int)lines.size() - 1;
+	if (currentLine < 0)
+		currentLine = 0;
+
+	_vm->_graphics->menuInitialize();
+	Graphics::Surface &menu = _vm->_graphics->getMenuSurface();
+	CursorMan.showMouse(false);
+
+	const int linesPerPage = 23;
+
+	while (!_vm->shouldQuit()) {
+		menu.fillRect(Common::Rect(0, 0, 640, 332), kColorBlue);
+
+		for (int i = 0; i < linesPerPage; i++) {
+			int lineIdx = currentLine + i;
+			if (lineIdx >= (int)lines.size())
+				break;
+
+			Common::String line = lines[lineIdx];
+			if (line.size() > 78)
+				line = Common::String(line.c_str(), 78);
+
+			_vm->_graphics->drawText(menu, line, _font, 14, 8, 4 + i * 14, kColorLightgray);
+		}
+
+		int pct = (currentLine + linesPerPage) * 100 / lines.size();
+		if (pct > 100)
+			pct = 100;
+		else if (pct <= 1)
+			pct = 0;
+		Common::String statusStr = Common::String::format("Doc lister: PgUp, PgDn, Home & End to move. Esc exits to main menu.|%d%% through", pct);
+		menu.fillRect(Common::Rect(0, 332, 640, 350), kColorLightgray);
+		_vm->_graphics->drawText(menu, statusStr, _vm->_font, 8, 4, 336, kColorBlack);
+
+		_vm->_graphics->menuRefreshScreen();
+
+		Common::Event event;
+		bool redraw = false;
+		while (_vm->getEvent(event)) {
+			if (event.type == Common::EVENT_KEYDOWN) {
+				switch (event.kbd.keycode) {
+				case Common::KEYCODE_ESCAPE:
+					CursorMan.showMouse(true);
+					return;
+				case Common::KEYCODE_PAGEUP:
+				case Common::KEYCODE_UP:
+					currentLine -= linesPerPage;
+					if (currentLine < 0)
+						currentLine = 0;
+					redraw = true;
+					break;
+				case Common::KEYCODE_PAGEDOWN:
+				case Common::KEYCODE_DOWN:
+				case Common::KEYCODE_SPACE:
+					if (currentLine + linesPerPage < (int)lines.size())
+						currentLine += linesPerPage;
+					redraw = true;
+					break;
+				case Common::KEYCODE_HOME:
+					currentLine = 0;
+					redraw = true;
+					break;
+				case Common::KEYCODE_END:
+					currentLine = (int)lines.size() - linesPerPage;
+					if (currentLine < 0)
+						currentLine = 0;
+					redraw = true;
+					break;
+				default:
+					break;
+				}
+			} else if (event.type == Common::EVENT_LBUTTONDOWN) {
+				CursorMan.showMouse(true);
+				return;
+			}
+		}
+
+		if (!redraw)
+			g_system->delayMillis(15);
+	}
+
+	CursorMan.showMouse(true);
+}
+
 void MainMenu::showPreview() {
 	Common::File file;
 	if (!file.open("preview2.avd")) {
diff --git a/engines/avalanche/mainmenu.h b/engines/avalanche/mainmenu.h
index 2738c75a85e..fbedcb36c86 100644
--- a/engines/avalanche/mainmenu.h
+++ b/engines/avalanche/mainmenu.h
@@ -49,6 +49,7 @@ private:
 	void centre(int16 y, Common::String text);
 	void wait();
 	void showPreview();
+	void showDoc(const Common::String &filename, int startLine);
 };
 
 } // End of namespace Avalanche


Commit: 5390f634f0665442dd4c3fcc53114cbadcf43669
    https://github.com/scummvm/scummvm/commit/5390f634f0665442dd4c3fcc53114cbadcf43669
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Implement MainMenu Option 4: documentation index and section jumping

Changed paths:
    engines/avalanche/mainmenu.cpp
    engines/avalanche/mainmenu.h


diff --git a/engines/avalanche/mainmenu.cpp b/engines/avalanche/mainmenu.cpp
index 7880ac32288..eb1847f28a9 100644
--- a/engines/avalanche/mainmenu.cpp
+++ b/engines/avalanche/mainmenu.cpp
@@ -117,8 +117,14 @@ void MainMenu::wait() {
 				_vm->_graphics->menuLoadPictures();
 				drawMenu();
 				break;
+			case Common::KEYCODE_4:
+				showDoc("avalot.doc", 0, false);
+				_vm->_graphics->menuInitialize();
+				_vm->_graphics->menuLoadPictures();
+				drawMenu();
+				break;
 			case Common::KEYCODE_5:
-				showDoc("avalot.doc", 1094);
+				showDoc("avalot.doc", 1094, true);
 				_vm->_graphics->menuInitialize();
 				_vm->_graphics->menuLoadPictures();
 				drawMenu();
@@ -136,7 +142,7 @@ void MainMenu::wait() {
 	}
 }
 
-void MainMenu::showDoc(const Common::String &filename, int startLine) {
+void MainMenu::showDoc(const Common::String &filename, int startLine, bool isRegi) {
 	Common::File file;
 	if (!file.open(filename.c_str())) {
 		warning("AVALANCHE: File not found: %s", filename.c_str());
@@ -165,16 +171,39 @@ void MainMenu::showDoc(const Common::String &filename, int startLine) {
 	if (lines.empty())
 		return;
 
-	int currentLine = startLine;
-	if (currentLine >= (int)lines.size())
-		currentLine = (int)lines.size() - 1;
-	if (currentLine < 0)
-		currentLine = 0;
+	Common::Array<DocSection> sections;
+	DocSection startSec;
+	startSec.name = "Start of documentation";
+	startSec.line = 0;
+	sections.push_back(startSec);
+
+	for (int i = 1; i < (int)lines.size(); i++) {
+		Common::String trimmed = lines[i];
+		trimmed.trim();
+		if (trimmed.size() >= 3 && trimmed[0] == '"' && trimmed[1] == '"' && trimmed[2] == '"') {
+			Common::String headerStr = lines[i - 1];
+			headerStr.trim();
+			if (!headerStr.empty()) {
+				DocSection sec;
+				sec.name = headerStr;
+				sec.line = i - 1;
+				sections.push_back(sec);
+			}
+		}
+	}
 
+	int currentLine = startLine;
 	_vm->_graphics->menuInitialize();
 	Graphics::Surface &menu = _vm->_graphics->getMenuSurface();
 	CursorMan.showMouse(false);
 
+	if (!isRegi && startLine == 0) {
+		// Show Table of Contents index screen
+		int tocLine = showDocTOC(lines, sections);
+		if (tocLine >= 0)
+			currentLine = tocLine;
+	}
+
 	const int linesPerPage = 23;
 
 	while (!_vm->shouldQuit()) {
@@ -189,7 +218,7 @@ void MainMenu::showDoc(const Common::String &filename, int startLine) {
 			if (line.size() > 78)
 				line = Common::String(line.c_str(), 78);
 
-			_vm->_graphics->drawText(menu, line, _font, 14, 8, 4 + i * 14, kColorLightgray);
+			_vm->_graphics->drawText(menu, line, _font, 14, 8, 4 + i * 14, kColorCyan);
 		}
 
 		int pct = (currentLine + linesPerPage) * 100 / lines.size();
@@ -197,7 +226,13 @@ void MainMenu::showDoc(const Common::String &filename, int startLine) {
 			pct = 100;
 		else if (pct <= 1)
 			pct = 0;
-		Common::String statusStr = Common::String::format("Doc lister: PgUp, PgDn, Home & End to move. Esc exits to main menu.|%d%% through", pct);
+
+		Common::String statusStr;
+		if (isRegi)
+			statusStr = Common::String::format("Doc lister: PgUp, PgDn, Home & End to move. Esc exits to main menu. | %d %% through", pct);
+		else
+			statusStr = Common::String::format("Doc lister: PgUp, PgDn, Home & End to move. Esc exits. C=→contents |%d %% through", pct);
+
 		menu.fillRect(Common::Rect(0, 332, 640, 350), kColorLightgray);
 		_vm->_graphics->drawText(menu, statusStr, _vm->_font, 8, 4, 336, kColorBlack);
 
@@ -211,6 +246,14 @@ void MainMenu::showDoc(const Common::String &filename, int startLine) {
 				case Common::KEYCODE_ESCAPE:
 					CursorMan.showMouse(true);
 					return;
+				case Common::KEYCODE_c:
+					if (!isRegi) {
+						int tocLine = showDocTOC(lines, sections);
+						if (tocLine >= 0)
+							currentLine = tocLine;
+						redraw = true;
+					}
+					break;
 				case Common::KEYCODE_PAGEUP:
 				case Common::KEYCODE_UP:
 					currentLine -= linesPerPage;
@@ -251,6 +294,54 @@ void MainMenu::showDoc(const Common::String &filename, int startLine) {
 	CursorMan.showMouse(true);
 }
 
+int MainMenu::showDocTOC(const Common::Array<Common::String> &lines, const Common::Array<DocSection> &sections) {
+	Graphics::Surface &menu = _vm->_graphics->getMenuSurface();
+	menu.fillRect(Common::Rect(0, 0, 640, 332), kColorBlue);
+
+	Common::String header = "-=- The contents of the Lord AVALOT D'Argent (version 1.3) documentation -=-";
+	_vm->_graphics->drawText(menu, header, _font, 14, 320 - (header.size() * 4), 4, kColorCyan);
+
+	for (int i = 0; i < (int)sections.size() && i < 26; i++) {
+		char keyChar = (i < 9) ? ('1' + i) : ('A' + (i - 9));
+		Common::String itemStr = Common::String::format("%c. %s", keyChar, sections[i].name.c_str());
+		if (itemStr.size() > 76)
+			itemStr = Common::String(itemStr.c_str(), 76);
+
+		int x = 12;
+		int y = 20 + i * 11;
+
+		_vm->_graphics->drawText(menu, itemStr, _vm->_font, 8, x, y, kColorCyan);
+	}
+
+	Common::String tocStatus = "Esc=to doc lister | Press the key listed next to the section you wish to jump to";
+	menu.fillRect(Common::Rect(0, 332, 640, 350), kColorLightgray);
+	_vm->_graphics->drawText(menu, tocStatus, _vm->_font, 8, 4, 338, kColorBlack);
+
+	_vm->_graphics->menuRefreshScreen();
+
+	while (!_vm->shouldQuit()) {
+		Common::Event event;
+		while (_vm->getEvent(event)) {
+			if (event.type == Common::EVENT_KEYDOWN) {
+				if (event.kbd.keycode == Common::KEYCODE_ESCAPE)
+					return -1;
+
+				char c = toupper(event.kbd.ascii);
+				int targetSec = -1;
+				if (c >= '1' && c <= '9')
+					targetSec = c - '1';
+				else if (c >= 'A' && c <= 'Z')
+					targetSec = 9 + (c - 'A');
+
+				if (targetSec >= 0 && targetSec < (int)sections.size())
+					return sections[targetSec].line;
+			}
+		}
+		g_system->delayMillis(15);
+	}
+	return -1;
+}
+
 void MainMenu::showPreview() {
 	Common::File file;
 	if (!file.open("preview2.avd")) {
diff --git a/engines/avalanche/mainmenu.h b/engines/avalanche/mainmenu.h
index fbedcb36c86..5ef2dff08f3 100644
--- a/engines/avalanche/mainmenu.h
+++ b/engines/avalanche/mainmenu.h
@@ -46,10 +46,16 @@ private:
 	void loadRegiInfo();
 	void option(byte which, Common::String what);
 	void drawMenu();
+	struct DocSection {
+		Common::String name;
+		int line;
+	};
+
 	void centre(int16 y, Common::String text);
 	void wait();
 	void showPreview();
-	void showDoc(const Common::String &filename, int startLine);
+	void showDoc(const Common::String &filename, int startLine, bool isRegi = false);
+	int showDocTOC(const Common::Array<Common::String> &lines, const Common::Array<DocSection> &sections);
 };
 
 } // End of namespace Avalanche


Commit: 10a54c2696126a81ee2b2f7f10b1a8be90ed7711
    https://github.com/scummvm/scummvm/commit/10a54c2696126a81ee2b2f7f10b1a8be90ed7711
Author: Mohit Bankar (mohitbankar1212 at gmail.com)
Date: 2026-08-25T01:04:54+02:00

Commit Message:
AVALANCHE: Remove stray arrow character from documentation viewer status text

Changed paths:
    engines/avalanche/mainmenu.cpp


diff --git a/engines/avalanche/mainmenu.cpp b/engines/avalanche/mainmenu.cpp
index eb1847f28a9..555f6d70140 100644
--- a/engines/avalanche/mainmenu.cpp
+++ b/engines/avalanche/mainmenu.cpp
@@ -231,7 +231,7 @@ void MainMenu::showDoc(const Common::String &filename, int startLine, bool isReg
 		if (isRegi)
 			statusStr = Common::String::format("Doc lister: PgUp, PgDn, Home & End to move. Esc exits to main menu. | %d %% through", pct);
 		else
-			statusStr = Common::String::format("Doc lister: PgUp, PgDn, Home & End to move. Esc exits. C=→contents |%d %% through", pct);
+			statusStr = Common::String::format("Doc lister: PgUp, PgDn, Home & End to move. Esc exits. C=contents |%d %% through", pct);
 
 		menu.fillRect(Common::Rect(0, 332, 640, 350), kColorLightgray);
 		_vm->_graphics->drawText(menu, statusStr, _vm->_font, 8, 4, 336, kColorBlack);




More information about the Scummvm-git-logs mailing list