[Scummvm-git-logs] scummvm master -> aa1d21fd5db56dbb0dc72ab9f4c0bbb69d06b096

tag2015 noreply at scummvm.org
Wed Nov 27 15:51:44 UTC 2024


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

Summary:
2e2cf19168 AGS: Engine: do character loop fixup after move update
1244fe78e9 AGS: Engine: also move frame fixup to FixupCurrentLoop()
e79f840cba AGS: Common: fix AssetManager construct with initialized lib sorter
6a0da1f9c6 AGS: Engine: fix SetCharacterViewEx alignment value
6ec09a5e56 AGS: Engine: fixed default InvWindow not redrawn when player char changes
0c90054ead AGS: Engine: fixed playing transition-out effect while skipping cutscene
49b7374dfe AGS: Engine: fixed FadeIn/Out() not marking screen faded if fast-forwarding
bd66bd7e7f AGS: Add MatchesFormat method
a0cc706ddd AGS: Updated build version (3.6.1.30 P8)
b0cbe21288 AGS: Engine: fixed Wait function's negative time case for pre-3.6.0 API
f1dcf66ad3 AGS: Engine: added AudioChannel's playback state to saves
8d96ff3966 AGS: Engine: perform SetTextOverlay through new Overlay_SetText algorithm
69ba09c5fd AGS: Add two methods for handling multiscreen
aa1d21fd5d AGS: Engine: fix Character.Animate in case called during idling


Commit: 2e2cf1916858964a475ddd894022438062f6d8bc
    https://github.com/scummvm/scummvm/commit/2e2cf1916858964a475ddd894022438062f6d8bc
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:49+01:00

Commit Message:
AGS: Engine: do character loop fixup after move update

Loop fixup was introduced earlier to avoid crashes in certain old games,
and also in case users forgot to unlock a custom animation View.
But apparently I missed a case when a loop may be adjusted later on during walking update.
>From upstream f04c4d7d9adff5134e48c1062b0214251797efdc

Changed paths:
    engines/ags/engine/ac/character_info_engine.cpp
    engines/ags/shared/ac/character_info.h
    engines/ags/shared/ac/view.h


diff --git a/engines/ags/engine/ac/character_info_engine.cpp b/engines/ags/engine/ac/character_info_engine.cpp
index 45ea2c433b8..6c44ee0f3bf 100644
--- a/engines/ags/engine/ac/character_info_engine.cpp
+++ b/engines/ags/engine/ac/character_info_engine.cpp
@@ -63,19 +63,12 @@ int CharacterInfo::get_blocking_bottom() const {
 	return y + 3;
 }
 
-void CharacterInfo::UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, std::vector<int> &followingAsSheep) {
-	int res;
-
-	if (on != 1) return;
-
-	// walking
-	res = update_character_walking(chex);
-	// [IKM] Yes, it should return! upon getting RETURN_CONTINUE here
-	if (res == RETURN_CONTINUE) { // [IKM] now, this is one of those places...
-		return;                   //  must be careful not to screw things up
-	}
+void CharacterInfo::FixupCurrentLoop() {
+	// If current loop property exceeds number of loops,
+	// or if selected loop has no frames, then try select any first loop that has frames.
+	// NOTE: although this may seem like a weird solution to a problem,
+	// we do so for backwards compatibility; this approximately emulates older games behavior.
 
-	// Fixup character's view when possible
 	if (view >= 0 &&
 		(loop >= _GP(views)[view].numLoops || _GP(views)[view].loops[loop].numFrames == 0)) {
 		for (loop = 0;
@@ -89,9 +82,26 @@ void CharacterInfo::UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, st
 			loop = 0;
 		}
 	}
+}
 
-	int doing_nothing = 1;
+void CharacterInfo::UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, std::vector<int> &followingAsSheep) {
+	int res;
 
+	if (on != 1)
+		return;
+
+	// Turn around during walk
+	res = update_character_walkturning(chex);
+	// Fixup character's loop prior to any further logic updates
+	FixupCurrentLoop();
+
+	// FIXME: refactor this nonsense!
+	// [IKM] Yes, it should return! upon getting RETURN_CONTINUE here
+	if (res == RETURN_CONTINUE) { // [IKM] now, this is one of those places...
+		return;                   //  must be careful not to screw things up
+	}
+
+	int doing_nothing = 1;
 	update_character_moving(char_index, chex, doing_nothing);
 
 	// [IKM] 2012-06-28:
@@ -124,7 +134,7 @@ void CharacterInfo::UpdateFollowingExactlyCharacter() {
 		baseline = usebase + 1;
 }
 
-int CharacterInfo::update_character_walking(CharacterExtras *chex) {
+int CharacterInfo::update_character_walkturning(CharacterExtras *chex) {
 	if (walking >= TURNING_AROUND) {
 		// Currently rotating to correct direction
 		if (walkwait > 0) walkwait--;
@@ -207,19 +217,14 @@ void CharacterInfo::update_character_moving(int &char_index, CharacterExtras *ch
 				walkwaitcounter++;
 		}
 
-		if (loop >= _GP(views)[view].numLoops)
-			quitprintf("Unable to render character %d (%s) because loop %d does not exist in view %d", index_id, scrname, loop, view + 1);
-
-		// check don't overflow loop
-		int framesInLoop = _GP(views)[view].loops[loop].numFrames;
-		if (frame > framesInLoop) {
-			frame = 1;
-
-			if (framesInLoop < 2)
-				frame = 0;
+		// Fixup character's loop, it may be changed when making a walk-move
+		FixupCurrentLoop();
 
-			if (framesInLoop < 1)
-				quitprintf("Unable to render character %d (%s) because there are no frames in loop %d", index_id, scrname, loop);
+		// If last saved frame exceeds new loop, then switch to frame 1
+		// (first walking frame) or frame 0 if there's less than 2 frames.
+		int frames_in_loop = _GP(views)[view].loops[loop].numFrames;
+		if (frame >= frames_in_loop) {
+			frame = (frames_in_loop >= 2) ? 1 : 0;
 		}
 
 		doing_nothing = 0; // still walking?
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index 8b396fd4599..5ab73e201c9 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -199,7 +199,7 @@ struct CharacterInfo {
 	void UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, std::vector<int> &followingAsSheep);
 	void UpdateFollowingExactlyCharacter();
 
-	int  update_character_walking(CharacterExtras *chex);
+	int  update_character_walkturning(CharacterExtras *chex);
 	void update_character_moving(int &char_index, CharacterExtras *chex, int &doing_nothing);
 	int  update_character_animating(int &char_index, int &doing_nothing);
 	void update_character_idle(CharacterExtras *chex, int &doing_nothing);
@@ -212,6 +212,9 @@ struct CharacterInfo {
 	void WriteToSavegame(Shared::Stream *out, const CharacterInfo2 &chinfo2) const;
 
 private:
+	// Fixups loop value, in case it's set to an invalid loop which cannot be used with the current view
+	void FixupCurrentLoop();
+
 	// Helper functions that read and write first data fields,
 	// common for both game file and save.
 	void ReadBaseFields(Shared::Stream *in);
diff --git a/engines/ags/shared/ac/view.h b/engines/ags/shared/ac/view.h
index 2ec12605c2e..bb8ae99c2fe 100644
--- a/engines/ags/shared/ac/view.h
+++ b/engines/ags/shared/ac/view.h
@@ -59,8 +59,8 @@ struct ViewLoopNew {
 	int numFrames;
 	int   flags;
 	std::vector<ViewFrame> frames;
-	// NOTE: we still need numFrames for backward compatibility:
-	// some older versions could allocate extra frame(s) for safety,
+	// NOTE: we still need numFrames:
+	// as we always allocate at least 1 frame for safety, to avoid crashes,
 	// but have to report "logical" number of frames for the engine API.
 
 	ViewLoopNew();


Commit: 1244fe78e900ff01ae7c514130873678b2b1a143
    https://github.com/scummvm/scummvm/commit/1244fe78e900ff01ae7c514130873678b2b1a143
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:49+01:00

Commit Message:
AGS: Engine: also move frame fixup to FixupCurrentLoop()

>From upstream ecf6fe02d73686651f4123fb59f8a0c1b821bea8

Changed paths:
    engines/ags/engine/ac/character_info_engine.cpp
    engines/ags/shared/ac/character_info.h


diff --git a/engines/ags/engine/ac/character_info_engine.cpp b/engines/ags/engine/ac/character_info_engine.cpp
index 6c44ee0f3bf..d6d99c75ae6 100644
--- a/engines/ags/engine/ac/character_info_engine.cpp
+++ b/engines/ags/engine/ac/character_info_engine.cpp
@@ -63,7 +63,7 @@ int CharacterInfo::get_blocking_bottom() const {
 	return y + 3;
 }
 
-void CharacterInfo::FixupCurrentLoop() {
+void CharacterInfo::FixupCurrentLoopAndFrame() {
 	// If current loop property exceeds number of loops,
 	// or if selected loop has no frames, then try select any first loop that has frames.
 	// NOTE: although this may seem like a weird solution to a problem,
@@ -82,6 +82,13 @@ void CharacterInfo::FixupCurrentLoop() {
 			loop = 0;
 		}
 	}
+
+	// If the last saved frame exceeds a new loop, then switch to frame 1
+	// (first walking frame) if walking, or frame 0 otherwise or if there's less than 2 frames.
+	int frames_in_loop = _GP(views)[view].loops[loop].numFrames;
+	if (frame >= frames_in_loop) {
+		frame = (walking > 0 && frames_in_loop > 1) ? 1 : 0;
+	}
 }
 
 void CharacterInfo::UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, std::vector<int> &followingAsSheep) {
@@ -93,7 +100,7 @@ void CharacterInfo::UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, st
 	// Turn around during walk
 	res = update_character_walkturning(chex);
 	// Fixup character's loop prior to any further logic updates
-	FixupCurrentLoop();
+	FixupCurrentLoopAndFrame();
 
 	// FIXME: refactor this nonsense!
 	// [IKM] Yes, it should return! upon getting RETURN_CONTINUE here
@@ -159,9 +166,6 @@ int CharacterInfo::update_character_walkturning(CharacterExtras *chex) {
 				} else break;
 			}
 			loop = turnlooporder[wantloop];
-			if (frame >= _GP(views)[view].loops[loop].numFrames)
-				frame = 0; // AVD: make sure the loop always has a valid frame
-			if (frame >= _GP(views)[view].loops[loop].numFrames) frame = 0; // AVD: make sure the loop always has a valid frame
 			walking -= TURNING_AROUND;
 			// if still turning, wait for next frame
 			if (walking % TURNING_BACKWARDS >= TURNING_AROUND)
@@ -218,14 +222,7 @@ void CharacterInfo::update_character_moving(int &char_index, CharacterExtras *ch
 		}
 
 		// Fixup character's loop, it may be changed when making a walk-move
-		FixupCurrentLoop();
-
-		// If last saved frame exceeds new loop, then switch to frame 1
-		// (first walking frame) or frame 0 if there's less than 2 frames.
-		int frames_in_loop = _GP(views)[view].loops[loop].numFrames;
-		if (frame >= frames_in_loop) {
-			frame = (frames_in_loop >= 2) ? 1 : 0;
-		}
+		FixupCurrentLoopAndFrame();
 
 		doing_nothing = 0; // still walking?
 
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index 5ab73e201c9..e94cfa61373 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -212,8 +212,8 @@ struct CharacterInfo {
 	void WriteToSavegame(Shared::Stream *out, const CharacterInfo2 &chinfo2) const;
 
 private:
-	// Fixups loop value, in case it's set to an invalid loop which cannot be used with the current view
-	void FixupCurrentLoop();
+	// Fixups loop and frame values, in case any of them are set to a value out of the valid range
+	void FixupCurrentLoopAndFrame();
 
 	// Helper functions that read and write first data fields,
 	// common for both game file and save.


Commit: e79f840cba243b8bfdc18ff0f6ff671cf8917e97
    https://github.com/scummvm/scummvm/commit/e79f840cba243b8bfdc18ff0f6ff671cf8917e97
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:49+01:00

Commit Message:
AGS: Common: fix AssetManager construct with initialized lib sorter

>From upstream 814fada7bd7f851a08f5f9c2171bd1541e6a9be5

Changed paths:
    engines/ags/shared/core/asset_manager.cpp
    engines/ags/shared/core/asset_manager.h


diff --git a/engines/ags/shared/core/asset_manager.cpp b/engines/ags/shared/core/asset_manager.cpp
index d4a76fa3072..45d8913b227 100644
--- a/engines/ags/shared/core/asset_manager.cpp
+++ b/engines/ags/shared/core/asset_manager.cpp
@@ -54,6 +54,10 @@ bool SortLibsPriorityLib(const AssetLibInfo *lib1, const AssetLibInfo *lib2) {
 	return !IsAssetLibDir(lib1) && IsAssetLibDir(lib2);
 }
 
+AssetManager::AssetManager() {
+	SetSearchPriority(kAssetPriorityDir); // ensure lib sorter is initialized
+}
+
 /* static */ bool AssetManager::IsDataFile(const String &data_file) {
 	Stream *in = File::OpenFileCI(data_file.GetCStr(), Shared::kFile_Open, Shared::kFile_Read);
 	if (in) {
diff --git a/engines/ags/shared/core/asset_manager.h b/engines/ags/shared/core/asset_manager.h
index ffb528d8bc5..e3a31c0b11e 100644
--- a/engines/ags/shared/core/asset_manager.h
+++ b/engines/ags/shared/core/asset_manager.h
@@ -77,7 +77,7 @@ struct AssetPath {
 
 class AssetManager {
 public:
-	AssetManager() = default;
+	AssetManager();
 	~AssetManager() {
 		RemoveAllLibraries();
 	}


Commit: 6a0da1f9c6f01501d588213247096076497d8724
    https://github.com/scummvm/scummvm/commit/6a0da1f9c6f01501d588213247096076497d8724
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:49+01:00

Commit Message:
AGS: Engine: fix SetCharacterViewEx alignment value

>From upstream e9c1c7e0477c8fe156d7e3b86e70b925535c9e2b

Changed paths:
    engines/ags/engine/ac/global_character.cpp


diff --git a/engines/ags/engine/ac/global_character.cpp b/engines/ags/engine/ac/global_character.cpp
index eb4bc181119..a6db3a0a8c3 100644
--- a/engines/ags/engine/ac/global_character.cpp
+++ b/engines/ags/engine/ac/global_character.cpp
@@ -281,7 +281,7 @@ void SetCharacterFrame(int chaa, int view, int loop, int frame) {
 // similar to SetCharView, but aligns the frame to make it line up
 void SetCharacterViewEx(int chaa, int vii, int loop, int align) {
 
-	Character_LockViewAligned(&_GP(game).chars[chaa], vii, loop, align);
+	Character_LockViewAligned(&_GP(game).chars[chaa], vii, loop, ConvertLegacyScriptAlignment((LegacyScriptAlignment)align));
 }
 
 void SetCharacterViewOffset(int chaa, int vii, int xoffs, int yoffs) {


Commit: 6ec09a5e5629bd3f7d8f3cdfd2775b1abc751a84
    https://github.com/scummvm/scummvm/commit/6ec09a5e5629bd3f7d8f3cdfd2775b1abc751a84
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:49+01:00

Commit Message:
AGS: Engine: fixed default InvWindow not redrawn when player char changes

This is a regression since around 3.6.0, but apparently Inventory Window had always some problem
updating after SetAsPlayer is called.
In much older versions of AGS, even prior to GUI optimizations, it also did not update until player
moved mouse cursor around some interactive elements. Which probably made an impression
that it's not working without explicitly assigning InvWindow.CharacterToUse.
>From upstream 0b758225a7801700a03e48fa72b733fd122ca1c9

Changed paths:
    engines/ags/engine/ac/character.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index e388604e734..6e17be05ba1 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -735,9 +735,7 @@ void Character_SetAsPlayer(CharacterInfo *chaa) {
 		return;
 
 	setup_player_character(chaa->index_id);
-
-	//update_invorder();
-
+	GUI::MarkInventoryForUpdate(_GP(game).playercharacter, true);
 	debug_script_log("%s is new player character", _G(playerchar)->scrname);
 
 	// Within game_start, return now


Commit: 0c90054ead17e3f69ead768a9bc292d4cc9e6502
    https://github.com/scummvm/scummvm/commit/0c90054ead17e3f69ead768a9bc292d4cc9e6502
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:49+01:00

Commit Message:
AGS: Engine: fixed playing transition-out effect while skipping cutscene

This is a regression since 3.6.0.
>From upstream a18d94a61d698aeb4fba39330d4194ca5410d696

Changed paths:
    engines/ags/engine/ac/screen.cpp


diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp
index 666aa57c101..68aa4892cac 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -68,6 +68,10 @@ void current_fade_out_effect() {
 		theTransition = _GP(play).next_screen_transition;
 	const bool instant_transition = (theTransition == FADE_INSTANT) ||
 									_GP(play).screen_tint > 0; // for some reason we do not play fade if screen is tinted
+	if (_GP(play).fast_forward) {
+		_GP(play).screen_is_faded_out |= (!instant_transition);
+		return;
+	}
 	if (instant_transition) {
 		if (!_GP(play).keep_screen_during_instant_transition)
 			set_palette_range(_G(black_palette), 0, 255, 0);


Commit: 49b7374dfe41cca15a36c660ab0cbb8f5ff360c9
    https://github.com/scummvm/scummvm/commit/49b7374dfe41cca15a36c660ab0cbb8f5ff360c9
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:49+01:00

Commit Message:
AGS: Engine: fixed FadeIn/Out() not marking screen faded if fast-forwarding

This results in post-cutscene state being different if it's skipped compared to
a normal run if a pairing FadeOut or FadeIn was not inside StartCutscene/EndCutscene too.
>From upstream 14bc225fb64bef8aae56c2f791194d515d7a664b

Changed paths:
    engines/ags/engine/ac/global_screen.cpp


diff --git a/engines/ags/engine/ac/global_screen.cpp b/engines/ags/engine/ac/global_screen.cpp
index 09ac8d4e33c..f38af739cce 100644
--- a/engines/ags/engine/ac/global_screen.cpp
+++ b/engines/ags/engine/ac/global_screen.cpp
@@ -133,8 +133,10 @@ void TintScreen(int red, int grn, int blu) {
 void FadeOut(int sppd) {
 	EndSkippingUntilCharStops();
 
-	if (_GP(play).fast_forward)
+	if (_GP(play).fast_forward) {
+		_GP(play).screen_is_faded_out = 1;
 		return;
+	}
 
 	// FIXME: we have to sync audio here explicitly, because FadeOut
 	// does not call any game update function while it works
@@ -181,8 +183,10 @@ void SetFadeColor(int red, int green, int blue) {
 void FadeIn(int sppd) {
 	EndSkippingUntilCharStops();
 
-	if (_GP(play).fast_forward)
+	if (_GP(play).fast_forward) {
+		_GP(play).screen_is_faded_out = 0;
 		return;
+	}
 
 	// Update drawables, prepare them for the transition-in
 	// in case this is called after the game state change but before any update was run


Commit: bd66bd7e7f3ad53a94477caa1f8af3056badbfe2
    https://github.com/scummvm/scummvm/commit/bd66bd7e7f3ad53a94477caa1f8af3056badbfe2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:49+01:00

Commit Message:
AGS: Add MatchesFormat method

Probably just for hardware renders, but added for completeness
Partially from upstream dcf3c2cc304f67a5f7f46259bb3dd53b4b369e5b

Changed paths:
    engines/ags/engine/gfx/ddb.h
    engines/ags/engine/gfx/gfx_driver_base.h


diff --git a/engines/ags/engine/gfx/ddb.h b/engines/ags/engine/gfx/ddb.h
index c6f1f6865d8..f34e242ef67 100644
--- a/engines/ags/engine/gfx/ddb.h
+++ b/engines/ags/engine/gfx/ddb.h
@@ -51,6 +51,7 @@ public:
 	virtual int GetWidth() const = 0;
 	virtual int GetHeight() const = 0;
 	virtual int GetColorDepth() const = 0;
+	virtual bool MatchesFormat(AGS::Shared::Bitmap *other) const = 0;
 
 protected:
 	IDriverDependantBitmap() {}
diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index fe4d92ac3a6..80fa2851cfc 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -207,6 +207,9 @@ public:
 	int GetColorDepth() const override {
 		return _colDepth;
 	}
+	bool MatchesFormat(AGS::Shared::Bitmap *other) const {
+		return _width == other->GetWidth() && _height == other->GetHeight() && _colDepth == other->GetColorDepth();
+	}
 
 	int _width = 0, _height = 0;
 	int _colDepth = 0;


Commit: a0cc706ddd07a7a750f47d4ee513f89b8eb8231c
    https://github.com/scummvm/scummvm/commit/a0cc706ddd07a7a750f47d4ee513f89b8eb8231c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:50+01:00

Commit Message:
AGS: Updated build version (3.6.1.30 P8)

Partially from upstream f42af3cf884a5aab5ffeb308da9da692eb8a3f03

Changed paths:
    engines/ags/shared/core/def_version.h


diff --git a/engines/ags/shared/core/def_version.h b/engines/ags/shared/core/def_version.h
index cc4795a7d5a..42aca660019 100644
--- a/engines/ags/shared/core/def_version.h
+++ b/engines/ags/shared/core/def_version.h
@@ -22,9 +22,9 @@
 #ifndef AGS_SHARED_CORE_DEFVERSION_H
 #define AGS_SHARED_CORE_DEFVERSION_H
 
-#define ACI_VERSION_STR      "3.6.1.29"
+#define ACI_VERSION_STR      "3.6.1.30"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.29
+#define ACI_VERSION_MSRC_DEF  3.6.1.30
 #endif
 
 #define SPECIAL_VERSION ""


Commit: b0cbe212883d0e89dab8f14ecf453bca554365ee
    https://github.com/scummvm/scummvm/commit/b0cbe212883d0e89dab8f14ecf453bca554365ee
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:50+01:00

Commit Message:
AGS: Engine: fixed Wait function's negative time case for pre-3.6.0 API

Prior to 3.6.0 script API Wait function was treating negative values as "no time",
but since 3.6.0 it treats it as "infinite time".
Also, old engines let overflow happen when assigning > INT16_MAX values to the wait counter
(which is int16).
In the old engine this resulted in immediate timer stop, by pure accident.
>From upstream 6777dad163b3f8c2678e5fc001feb15b78c36849

Changed paths:
    engines/ags/engine/ac/global_game.cpp


diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index 6b254467b75..bc45a58335f 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -812,7 +812,14 @@ int WaitImpl(int skip_type, int nloops) {
 	if (_GP(play).fast_forward && ((skip_type & ~SKIP_AUTOTIMER) != 0))
 		return 0;
 
-	_GP(play).wait_counter = nloops;
+	// < 3.6.0 treated negative nloops as "no time";
+	// also old engine let nloops to overflow into neg when assigned to wait_counter...
+	if (_GP(game).options[OPT_BASESCRIPTAPI] < kScriptAPI_v360) {
+		if (nloops < 0 || nloops > INT16_MAX)
+			nloops = 0;
+	}
+	// clamp to int16
+	_GP(play).wait_counter = static_cast<int16_t>(Math::Clamp<int>(nloops, -1, INT16_MAX));
 	_GP(play).wait_skipped_by = SKIP_NONE;
 	_GP(play).wait_skipped_by = SKIP_AUTOTIMER; // we set timer flag by default to simplify that case
 	_GP(play).wait_skipped_by_data = 0;
@@ -821,7 +828,7 @@ int WaitImpl(int skip_type, int nloops) {
     GameLoopUntilValueIsZero(&_GP(play).wait_counter);
 
 	if (_GP(game).options[OPT_BASESCRIPTAPI] < kScriptAPI_v360) {
-		// < 3.6.0 return 1 is skipped by user input, otherwise 0
+		// < 3.6.0 return 1 if skipped by user input, otherwise 0
 		return ((_GP(play).wait_skipped_by & (SKIP_KEYPRESS | SKIP_MOUSECLICK)) != 0) ? 1 : 0;
 	}
 	// >= 3.6.0 return skip (input) type flags with keycode


Commit: f1dcf66ad31d26d460ef033cecfde240bf819329
    https://github.com/scummvm/scummvm/commit/f1dcf66ad31d26d460ef033cecfde240bf819329
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:50+01:00

Commit Message:
AGS: Engine: added AudioChannel's playback state to saves

AudioChannel.Pause and Resume commands were added back in 3.6.0,
but I forgot to write the paused state to save format...
>From upstream e18091c2f32a057d4c6538b9b26e4f31ef9b6296

Changed paths:
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/game/savegame.h
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_internal.h


diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index d58a926b744..483548f30c0 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -599,6 +599,9 @@ HSaveError DoAfterRestore(const PreservedParams &pp, RestoredData &r_data) {
 			ch->_xSource = chan_info.XSource;
 			ch->_ySource = chan_info.YSource;
 			ch->_maximumPossibleDistanceAway = chan_info.MaxDist;
+
+			if ((chan_info.Flags & kSvgAudioPaused) != 0)
+				ch->pause();
 		}
 	}
 	if ((cf_in_chan > 0) && (AudioChans::GetChannel(cf_in_chan) != nullptr))
diff --git a/engines/ags/engine/game/savegame.h b/engines/ags/engine/game/savegame.h
index 387a9df74c3..40a934f430d 100644
--- a/engines/ags/engine/game/savegame.h
+++ b/engines/ags/engine/game/savegame.h
@@ -65,7 +65,8 @@ enum SavegameVersion {
 	kSvgVersion_360_beta = 3060023,
 	kSvgVersion_360_final = 3060041,
 	kSvgVersion_361 = 3060115,
-	kSvgVersion_Current = kSvgVersion_361,
+	kSvgVersion_361_p8 = 3060130,
+	kSvgVersion_Current = kSvgVersion_361_p8,
 	kSvgVersion_LowestSupported = kSvgVersion_321 // change if support dropped
 };
 
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 7a809d83a45..47c647c9e33 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -326,6 +326,14 @@ HSaveError ReadGameState(Stream *in, int32_t cmp_ver, soff_t cmp_size, const Pre
 	return err;
 }
 
+// Savegame data format for RoomStatus
+enum AudioSvgVersion {
+	kAudioSvgVersion_Initial = 0,
+	kAudioSvgVersion_35026 = 1,       // source position settings
+	kAudioSvgVersion_36009 = 2,       // up number of channels
+	kAudioSvgVersion_36130 = 3060130, // playback state
+};
+
 HSaveError WriteAudio(Stream *out) {
 	// Game content assertion
 	out->WriteInt32(_GP(game).audioClipTypes.size());
@@ -348,14 +356,22 @@ HSaveError WriteAudio(Stream *out) {
 			out->WriteInt32(ch->_priority);
 			out->WriteInt32(ch->_repeat ? 1 : 0);
 			out->WriteInt32(ch->get_volume255());
-			out->WriteInt32(0); // unused
+			out->WriteInt32(0); // was redundant data
 			out->WriteInt32(ch->get_volume100());
 			out->WriteInt32(ch->get_panning());
 			out->WriteInt32(ch->get_speed());
-			// since version 1
+			// since version kAudioSvgVersion_35026
 			out->WriteInt32(ch->_xSource);
 			out->WriteInt32(ch->_ySource);
 			out->WriteInt32(ch->_maximumPossibleDistanceAway);
+			// since version kAudioSvgVersion_36130
+			int playback_flags = 0;
+			if (ch->is_paused())
+				playback_flags |= kSvgAudioPaused;
+			out->WriteInt32(playback_flags);
+			out->WriteInt32(0); // reserved 3 ints
+			out->WriteInt32(0);
+			out->WriteInt32(0);
 		} else {
 			out->WriteInt32(-1);
 		}
@@ -373,13 +389,6 @@ HSaveError WriteAudio(Stream *out) {
 	return HSaveError::None();
 }
 
-// Savegame data format for RoomStatus
-enum AudioSvgVersion {
-	kAudioSvgVersion_Initial = 0,
-	kAudioSvgVersion_35026	 = 1, // source position settings
-	kAudioSvgVersion_36009	 = 2, // up number of channels
-};
-
 HSaveError ReadAudio(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData &r_data) {
 	HSaveError err;
 	// Game content assertion
@@ -417,7 +426,7 @@ HSaveError ReadAudio(Stream *in, int32_t cmp_ver, soff_t cmp_size, const Preserv
 			chan_info.Priority = in->ReadInt32();
 			chan_info.Repeat = in->ReadInt32();
 			chan_info.Vol = in->ReadInt32();
-			in->ReadInt32(); // unused
+			in->ReadInt32(); // was redundant data
 			chan_info.VolAsPercent = in->ReadInt32();
 			chan_info.Pan = in->ReadInt32();
 			chan_info.Speed = 1000;
@@ -427,6 +436,12 @@ HSaveError ReadAudio(Stream *in, int32_t cmp_ver, soff_t cmp_size, const Preserv
 				chan_info.YSource = in->ReadInt32();
 				chan_info.MaxDist = in->ReadInt32();
 			}
+			if (cmp_ver >= kAudioSvgVersion_36130) {
+				chan_info.Flags = in->ReadInt32();
+				in->ReadInt32(); // reserved 3 ints
+				in->ReadInt32();
+				in->ReadInt32();
+			}
 		}
 	}
 	_G(crossFading) = in->ReadInt32();
@@ -1065,7 +1080,7 @@ struct ComponentHandlers {
 		},
 		{
 			"Audio",
-			kAudioSvgVersion_36009,
+			kAudioSvgVersion_36130,
 			kAudioSvgVersion_Initial,
 			WriteAudio,
 			ReadAudio
diff --git a/engines/ags/engine/game/savegame_internal.h b/engines/ags/engine/game/savegame_internal.h
index cf180a598e4..6bb4406bd83 100644
--- a/engines/ags/engine/game/savegame_internal.h
+++ b/engines/ags/engine/game/savegame_internal.h
@@ -56,6 +56,11 @@ struct PreservedParams {
 	PreservedParams();
 };
 
+// Audio playback state flags, used only in serialization
+enum AudioSvgPlaybackFlags {
+	kSvgAudioPaused = 0x01
+};
+
 enum GameViewCamFlags {
 	kSvgGameAutoRoomView = 0x01
 };
@@ -100,6 +105,7 @@ struct RestoredData {
 	// General audio
 	struct ChannelInfo {
 		int ClipID = -1;
+		int Flags = 0;
 		int Pos = 0;
 		int Priority = 0;
 		int Repeat = 0;


Commit: 8d96ff396630582ff65fed7249a64bd258ba968b
    https://github.com/scummvm/scummvm/commit/8d96ff396630582ff65fed7249a64bd258ba968b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:50+01:00

Commit Message:
AGS: Engine: perform SetTextOverlay through new Overlay_SetText algorithm

The implementation of Overlay_SetText() was rewritten in aaf6144 , with a purpose of replacing
only the internal image of overlay, but not going all the way of removing/creating a new overlay
into the same slot.

In addition to the initial reasons, it became even more essential after the overlay storage
optimization (see merge commit 77b8264), as overlay system now records free ids on overlay
removal. As recreating overlay with exact same id would now require extra hacks into overlay
logic.

Unfortunately, an old SetTextOverlay() function was forgotten about, and it still doing this
remove/create way of resetting a text. This results in engine wrongly considering certain ids to
be free, while they are already taken.

This commit fixes this by making SetTextOverlay delegate to the new Overlay_SetText()
algorithm instead.
>From upstream 4caf0765ef36b4ebc2fb609f99a36a56d0736255

Changed paths:
    engines/ags/engine/ac/global_overlay.cpp
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/ac/overlay.h


diff --git a/engines/ags/engine/ac/global_overlay.cpp b/engines/ags/engine/ac/global_overlay.cpp
index 163eaa7dc6b..be77f949466 100644
--- a/engines/ags/engine/ac/global_overlay.cpp
+++ b/engines/ags/engine/ac/global_overlay.cpp
@@ -53,15 +53,15 @@ int CreateTextOverlay(int xx, int yy, int wii, int fontid, int text_color, const
 		allowShrink = 1;
 
 	auto *over = Overlay_CreateTextCore(false, xx, yy, wii, fontid, text_color, text, disp_type, allowShrink);
+	assert((disp_type < OVER_FIRSTFREE) || (disp_type == over->type));
 	return over ? over->type : 0;
 }
 
 void SetTextOverlay(int ovrid, int xx, int yy, int wii, int fontid, int text_color, const char *text) {
-	RemoveOverlay(ovrid);
-	const int disp_type = ovrid;
-	int new_ovrid = CreateTextOverlay(xx, yy, wii, fontid, text_color, text, disp_type);
-	if (new_ovrid != ovrid)
-		quit("SetTextOverlay internal error: inconsistent type ids");
+	auto *over = get_overlay(ovrid);
+	if (!over)
+		quit("!SetTextOverlay: invalid overlay ID specified");
+	Overlay_SetText(*over, xx, yy, wii, fontid, text_color, text);
 }
 
 void MoveOverlay(int ovrid, int newx, int newy) {
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index 2115316501b..08bed1b70d1 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -58,9 +58,10 @@ void Overlay_SetText(ScriptOverlay *scover, int width, int fontid, int text_colo
 	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!Overlay.SetText: invalid overlay ID specified");
-	const int x = over->x;
-	const int y = over->y;
+	Overlay_SetText(*over, over->x, over->y, width, fontid, text_color, text);
+}
 
+void Overlay_SetText(ScreenOverlay &over, int x, int y, int width, int fontid, int text_color, const char *text) {
 	// TODO: find a nice way to refactor and share these code pieces
 	// from CreateTextOverlay
 	width = data_to_game_coord(width);
@@ -81,7 +82,7 @@ void Overlay_SetText(ScriptOverlay *scover, int width, int fontid, int text_colo
 										 width, fontid, allow_shrink, has_alpha);
 
 	// Update overlay properties
-	over->SetImage(std::unique_ptr<Bitmap>(image), has_alpha, adj_x - dummy_x, adj_y - dummy_y);
+	over.SetImage(std::unique_ptr<Bitmap>(image), has_alpha, adj_x - dummy_x, adj_y - dummy_y);
 }
 
 int Overlay_GetX(ScriptOverlay *scover) {
diff --git a/engines/ags/engine/ac/overlay.h b/engines/ags/engine/ac/overlay.h
index 9a1f15b84fb..e92f08a0557 100644
--- a/engines/ags/engine/ac/overlay.h
+++ b/engines/ags/engine/ac/overlay.h
@@ -38,7 +38,8 @@ class Bitmap;
 using namespace AGS; // FIXME later
 
 void Overlay_Remove(ScriptOverlay *sco);
-void Overlay_SetText(ScriptOverlay *scover, int wii, int fontid, int clr, const char *text);
+void Overlay_SetText(ScriptOverlay *scover, int width, int fontid, int text_color, const char *text);
+void Overlay_SetText(ScreenOverlay &over, int x, int y, int width, int fontid, int text_color, const char *text);
 int  Overlay_GetX(ScriptOverlay *scover);
 void Overlay_SetX(ScriptOverlay *scover, int newx);
 int  Overlay_GetY(ScriptOverlay *scover);


Commit: 69ba09c5fd38f98c37e63c32f05dab5f5891202f
    https://github.com/scummvm/scummvm/commit/69ba09c5fd38f98c37e63c32f05dab5f5891202f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:50+01:00

Commit Message:
AGS: Add two methods for handling multiscreen

For completeness, this adds functions sys_window_fit_in_display
and sys_get_window_display_index.
Practically it has no effect as in ScummVM we don't support
multiscreen and the resolution is hardcoded.

Partially from upstream 1afc61df54681ffe2fddc0d30bb2221c38a0fa79 and
b17f58d90061c3b12c9e4c04183dc59b094d66cb

Changed paths:
    engines/ags/engine/main/engine.cpp
    engines/ags/engine/main/graphics_mode.cpp
    engines/ags/engine/main/graphics_mode.h
    engines/ags/engine/platform/base/sys_main.cpp
    engines/ags/engine/platform/base/sys_main.h


diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index ba0c300c819..018ab96764c 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -1208,10 +1208,11 @@ bool engine_try_switch_windowed_gfxmode() {
 	// Apply vsync in case it has been toggled at runtime
 	last_opposite_mode.Vsync = _GP(usetup).Screen.Params.VSync;
 
-	// If there are saved parameters for given mode (fullscreen/windowed)
-	// then use them, if there are not, get default setup for the new mode.
+	// If there are saved parameters for given mode (fullscreen/windowed),
+	// *and* if the window is on the same display where it's been last time,
+	// then use old params, otherwise - get default setup for the new mode.
 	bool res;
-	if (last_opposite_mode.IsValid()) {
+	if (last_opposite_mode.IsValid() && (setting.DisplayIndex == sys_get_window_display_index())) {
 		res = graphics_mode_set_dm(last_opposite_mode);
 	} else {
 		WindowSetup ws = windowed ? _GP(usetup).Screen.WinSetup : _GP(usetup).Screen.FsSetup;
diff --git a/engines/ags/engine/main/graphics_mode.cpp b/engines/ags/engine/main/graphics_mode.cpp
index adb73455e54..f806a14ce11 100644
--- a/engines/ags/engine/main/graphics_mode.cpp
+++ b/engines/ags/engine/main/graphics_mode.cpp
@@ -207,8 +207,8 @@ static Size precalc_screen_size(const Size &game_size, const WindowSetup &ws, co
 bool try_init_compatible_mode(const DisplayMode &dm) {
 	const Size &screen_size = Size(dm.Width, dm.Height);
 	// Find nearest compatible mode and init that
-	Debug::Printf("Attempting to find nearest supported resolution for screen size %d x %d (%d-bit) %s",
-		dm.Width, dm.Height, dm.ColorDepth, dm.IsWindowed() ? "windowed" : "fullscreen");
+	Debug::Printf("Attempt to find nearest supported resolution for screen size %d x %d (%d-bit) %s, on display %d",
+				  dm.Width, dm.Height, dm.ColorDepth, dm.IsWindowed() ? "windowed" : "fullscreen", sys_get_window_display_index());
 	const Size device_size = get_max_display_size(dm.IsWindowed());
 	if (dm.IsWindowed())
 		Debug::Printf("Maximal allowed window size: %d x %d", device_size.Width, device_size.Height);
@@ -453,8 +453,8 @@ bool graphics_mode_set_dm_any(const Size &game_size, const WindowSetup &ws,
 }
 
 bool graphics_mode_set_dm(const DisplayMode &dm) {
-	Debug::Printf("Attempt to switch gfx mode to %d x %d (%d-bit) %s",
-		dm.Width, dm.Height, dm.ColorDepth, dm.IsWindowed() ? "windowed" : "fullscreen");
+	Debug::Printf("Attempt to switch gfx mode to %d x %d (%d-bit) %s, on display %d",
+				  dm.Width, dm.Height, dm.ColorDepth, dm.IsWindowed() ? "windowed" : "fullscreen", sys_get_window_display_index());
 
 	// Tell Allegro new default bitmap color depth (must be done before set_gfx_mode)
 	// TODO: this is also done inside ALSoftwareGraphicsDriver implementation; can remove one?
@@ -466,14 +466,13 @@ bool graphics_mode_set_dm(const DisplayMode &dm) {
 	}
 
 	DisplayMode rdm = _G(gfxDriver)->GetDisplayMode();
-	if (rdm.IsWindowed())
-		_GP(SavedWindowedSetting).Dm = rdm;
-	else
-		_GP(SavedFullscreenSetting).Dm = rdm;
+	ActiveDisplaySetting &setting = rdm.IsWindowed() ? _GP(SavedWindowedSetting) : _GP(SavedFullscreenSetting);
+	setting.Dm = rdm;
+	setting.DisplayIndex = sys_get_window_display_index();
 	Debug::Printf(kDbgMsg_Info, "Graphics driver set: %s", _G(gfxDriver)->GetDriverName());
-	Debug::Printf(kDbgMsg_Info, "Graphics mode set: %d x %d (%d-bit) %s",
+	Debug::Printf(kDbgMsg_Info, "Graphics mode set: %d x %d (%d-bit) %s, on display %d",
 		rdm.Width, rdm.Height, rdm.ColorDepth,
-		rdm.IsWindowed() ? "windowed" : (rdm.IsRealFullscreen() ? "fullscreen" : "fullscreen desktop"));
+		rdm.IsWindowed() ? "windowed" : (rdm.IsRealFullscreen() ? "fullscreen" : "fullscreen desktop"), setting.DisplayIndex);
 	Debug::Printf(kDbgMsg_Info, "Graphics mode set: refresh rate (optional): %d, vsync: %d", rdm.RefreshRate, rdm.Vsync);
 	return true;
 }
diff --git a/engines/ags/engine/main/graphics_mode.h b/engines/ags/engine/main/graphics_mode.h
index 03a74c5e0f2..c7f9806b76e 100644
--- a/engines/ags/engine/main/graphics_mode.h
+++ b/engines/ags/engine/main/graphics_mode.h
@@ -117,8 +117,9 @@ struct ColorDepthOption {
 // ActiveDisplaySetting struct merges DisplayMode and FrameScaleDef,
 // which is useful if you need to save active settings and reapply them later.
 struct ActiveDisplaySetting {
-	DisplayMode     Dm;
-	FrameScaleDef  Frame = kFrame_Undefined;
+	DisplayMode	  Dm;
+	FrameScaleDef Frame = kFrame_Undefined;
+	int			  DisplayIndex = -1;
 };
 
 // Initializes any possible gfx mode, using user config as a recommendation;
diff --git a/engines/ags/engine/platform/base/sys_main.cpp b/engines/ags/engine/platform/base/sys_main.cpp
index e8dd865e316..d38c2421283 100644
--- a/engines/ags/engine/platform/base/sys_main.cpp
+++ b/engines/ags/engine/platform/base/sys_main.cpp
@@ -48,9 +48,20 @@ void sys_set_background_mode(bool /*on*/) {
 // ----------------------------------------------------------------------------
 // DISPLAY UTILS
 // ----------------------------------------------------------------------------
-#ifdef TODO
-const int DEFAULT_DISPLAY_INDEX = 0; // TODO: is this always right?
+
+const int DEFAULT_DISPLAY_INDEX = 0;
+
+int sys_get_window_display_index() {
+#if (AGS_PLATFORM_DESKTOP && !AGS_PLATFORM_SCUMMVM)
+	int index = -1;
+	SDL_Window *window = sys_get_window();
+	if (window)
+		index = SDL_GetWindowDisplayIndex(window);
+	return index >= 0 ? index : DEFAULT_DISPLAY_INDEX;
+#else
+	return DEFAULT_DISPLAY_INDEX;
 #endif
+}
 
 int sys_get_desktop_resolution(int &width, int &height) {
 	// TODO: ScummVM has a hardcoded dummy desktop resolution. See if there's any
@@ -64,7 +75,7 @@ int sys_get_desktop_resolution(int &width, int &height) {
 void sys_get_desktop_modes(std::vector<AGS::Engine::DisplayMode> &dms, int color_depth) {
 #ifdef TODO
 	SDL_DisplayMode mode;
-	const int display_id = DEFAULT_DISPLAY_INDEX;
+	const int display_id = sys_get_window_display_index();
 	const int count = SDL_GetNumDisplayModes(display_id);
 	dms.clear();
 	for (int i = 0; i < count; ++i) {
@@ -240,7 +251,11 @@ bool sys_window_set_size(int w, int h, bool center) {
 	return false;
 }
 
-void sys_window_center() {
+void sys_window_center(int display_index) {
+	// No implementation in ScummVM
+}
+
+void sys_window_fit_in_display(int display_index) {
 	// No implementation in ScummVM
 }
 
diff --git a/engines/ags/engine/platform/base/sys_main.h b/engines/ags/engine/platform/base/sys_main.h
index ac4687a28a9..80e0e0912a6 100644
--- a/engines/ags/engine/platform/base/sys_main.h
+++ b/engines/ags/engine/platform/base/sys_main.h
@@ -47,6 +47,9 @@ void sys_set_background_mode(bool on);
 
 // Display utilities.
 //
+// Queries the display index on which the window is currently positioned.
+// Returns default display index in case window does not exist yet, or on any error.
+int sys_get_window_display_index();
 // Queries current desktop resolution.
 int sys_get_desktop_resolution(int &width, int &height);
 // Queries supported desktop modes.
@@ -72,8 +75,10 @@ SDL_Window *sys_get_window();
 void sys_window_set_style(AGS::Engine::WindowMode mode, int ex_flags = 0);
 // Set new window size; optionally center new window on screen
 bool sys_window_set_size(int w, int h, bool center);
-// Centers the window on screen
-void sys_window_center();
+// Centers the window on screen, optionally choose the display to position on
+void sys_window_center(int display_index = -1);
+// Reduces window's size to fit into the said display bounds, and repositions to the display's center
+void sys_window_fit_in_display(int display_index);
 // Shows or hides system cursor when it's in the game window
 void sys_window_show_cursor(bool on);
 // Locks on unlocks mouse inside the window.


Commit: aa1d21fd5db56dbb0dc72ab9f4c0bbb69d06b096
    https://github.com/scummvm/scummvm/commit/aa1d21fd5db56dbb0dc72ab9f4c0bbb69d06b096
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-11-27T16:45:50+01:00

Commit Message:
AGS: Engine: fix Character.Animate in case called during idling

This fixes a regression after db71d92
in case Character.Animate() called during idling, and Loop number assertion is done
prior to view changing back to normal view, could cause game to error.

Pick out "stop_character_idling" function that checks idle state and releases the locked idle view.
>From upstream 946fa71c9332cae98f31d8b7672a1fe7b76d46c5

Fix #15523

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/character.h
    engines/ags/engine/ac/character_info_engine.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 6e17be05ba1..bec23ee0274 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -83,6 +83,14 @@ bool is_valid_character(int char_id) {
 	return ((char_id >= 0) && (char_id < _GP(game).numcharacters));
 }
 
+// Checks if character is currently playing idle anim, and reset it
+static void stop_character_idling(CharacterInfo *chi) {
+	if (chi->idleleft < 0) {
+		Character_UnlockView(chi);
+		chi->idleleft = chi->idletime;
+	}
+}
+
 bool AssertCharacter(const char *apiname, int char_id) {
 	if ((char_id >= 0) && (char_id < _GP(game).numcharacters))
 		return true;
@@ -181,10 +189,14 @@ void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
 void Character_Animate(CharacterInfo *chaa, int loop, int delay, int repeat,
 	int blocking, int direction, int sframe, int volume) {
 
+	// If idle view in progress for the character, stop the idle anim;
+	// do this prior to the loop check, as the view may switch back to defview here
+	stop_character_idling(chaa);
+
 	ValidateViewAnimVLF("Character.Animate", chaa->view, loop, sframe);
 	ValidateViewAnimParams("Character.Animate", repeat, blocking, direction);
 
-	animate_character(chaa, loop, delay, repeat, 0, direction, sframe, volume);
+	animate_character(chaa, loop, delay, repeat, direction, sframe, volume);
 
 	if (blocking != 0)
 		GameLoopUntilValueIsZero(&chaa->animating);
@@ -275,10 +287,7 @@ void Character_ChangeView(CharacterInfo *chap, int vii) {
 		debug_script_warn("Warning: ChangeCharacterView was used while the view was fixed - call ReleaseCharView first");
 
 	// if the idle animation is playing we should release the view
-	if (chap->idleleft < 0) {
-		Character_UnlockView(chap);
-		chap->idleleft = chap->idletime;
-	}
+	stop_character_idling(chap);
 
 	debug_script_log("%s: Change view to %d", chap->scrname, vii + 1);
 	chap->defview = vii;
@@ -562,11 +571,8 @@ void Character_LockViewEx(CharacterInfo *chap, int vii, int stopMoving) {
 	vii--; // convert to 0-based
 	AssertView("SetCharacterView", vii);
 
-	debug_script_log("%s: View locked to %d", chap->scrname, vii + 1);
-	if (chap->idleleft < 0) {
-		Character_UnlockView(chap);
-		chap->idleleft = chap->idletime;
-	}
+	stop_character_idling(chap);
+
 	if (stopMoving != KEEP_MOVING) {
 		Character_StopMoving(chap);
 	}
@@ -578,6 +584,7 @@ void Character_LockViewEx(CharacterInfo *chap, int vii, int stopMoving) {
 	chap->flags |= CHF_FIXVIEW;
 	chap->pic_xoffs = 0;
 	chap->pic_yoffs = 0;
+	debug_script_log("%s: View locked to %d", chap->scrname, vii + 1);
 }
 
 void Character_LockViewAligned_Old(CharacterInfo *chap, int vii, int loop, int align) {
@@ -773,8 +780,7 @@ void Character_SetIdleView(CharacterInfo *chaa, int iview, int itime) {
 		quit("!SetCharacterIdle: view 1 cannot be used as an idle view, sorry.");
 
 	// if an idle anim is currently playing, release it
-	if (chaa->idleleft < 0)
-		Character_UnlockView(chaa);
+	stop_character_idling(chaa);
 
 	chaa->idleview = iview - 1;
 	// make sure they don't appear idle while idle anim is disabled
@@ -1615,11 +1621,8 @@ void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims)
 
 	if ((chin->animating) && (autoWalkAnims))
 		stop_character_anim(chin);
-
-	if (chin->idleleft < 0) {
-		ReleaseCharacterView(chac);
-		chin->idleleft = chin->idletime;
-	}
+	// Stop idling anim
+	stop_character_idling(chin);
 	// stop them to make sure they're on a walkable area
 	// but save their frame first so that if they're already
 	// moving it looks smoother
@@ -2051,14 +2054,7 @@ void setup_player_character(int charid) {
 // Animate character internal implementation;
 // this function may be called by the game logic too, so we assume
 // the arguments must be correct, and do not fix them up as we do for API functions.
-void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept, int noidleoverride, int direction, int sframe, int volume) {
-	// If idle view in progress for the character (and this is not the
-	// "start idle animation" animate_character call), stop the idle anim
-	if ((chap->idleleft < 0) && (noidleoverride == 0)) {
-		Character_UnlockView(chap);
-		chap->idleleft = chap->idletime;
-	}
-
+void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept, int direction, int sframe, int volume) {
 	if ((chap->view < 0) || (chap->view > _GP(game).numviews) ||
 		(loopn < 0) || (loopn >= _GP(views)[chap->view].numLoops)) {
 		quitprintf("!AnimateCharacter: invalid view and/or loop\n"
@@ -2432,8 +2428,8 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 		allowShrink = 1;
 
 	// If has a valid speech view, and idle anim in progress for the character, then stop it
-	if (useview >= 0 && speakingChar->idleleft < 0) {
-		ReleaseCharacterView(aschar);
+	if (useview >= 0) {
+		stop_character_idling(speakingChar);
 	}
 
 	int tdxp = xx, tdyp = yy;
diff --git a/engines/ags/engine/ac/character.h b/engines/ags/engine/ac/character.h
index 83fd4957934..1fc4f0ec665 100644
--- a/engines/ags/engine/ac/character.h
+++ b/engines/ags/engine/ac/character.h
@@ -185,8 +185,7 @@ class Bitmap;
 using namespace AGS; // FIXME later
 
 // Configures and starts character animation.
-void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept,
-	int noidleoverride = 0, int direction = 0, int sframe = 0, int volume = 100);
+void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept, int direction = 0, int sframe = 0, int volume = 100);
 // Clears up animation parameters
 void stop_character_anim(CharacterInfo *chap);
 void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims);
diff --git a/engines/ags/engine/ac/character_info_engine.cpp b/engines/ags/engine/ac/character_info_engine.cpp
index d6d99c75ae6..aea123a4184 100644
--- a/engines/ags/engine/ac/character_info_engine.cpp
+++ b/engines/ags/engine/ac/character_info_engine.cpp
@@ -457,7 +457,7 @@ void CharacterInfo::update_character_idle(CharacterExtras *chex, int &doing_noth
 			else if (useloop >= maxLoops)
 				useloop = 0;
 
-			animate_character(this, useloop, idle_anim_speed, (idletime == 0) ? 1 : 0, 1);
+			animate_character(this, useloop, idle_anim_speed, (idletime == 0) ? 1 : 0 /* repeat */);
 
 			// don't set Animating while the idle anim plays (TODO: investigate why?)
 			animating = 0;




More information about the Scummvm-git-logs mailing list